From 87885dca519c5b3870cb9ef0afe0f903e8ada1cc Mon Sep 17 00:00:00 2001 From: wendellhu Date: Wed, 11 Aug 2021 10:51:30 +0800 Subject: [PATCH 1/5] feat: support ctrl n/p on mac --- src/OptionList.tsx | 13 ++++- src/utils/platformUtil.ts | 3 ++ tests/OptionList.test.tsx | 105 +++++++++++++++++++------------------- 3 files changed, 66 insertions(+), 55 deletions(-) create mode 100644 src/utils/platformUtil.ts diff --git a/src/OptionList.tsx b/src/OptionList.tsx index df6152642..f9c5515f5 100644 --- a/src/OptionList.tsx +++ b/src/OptionList.tsx @@ -17,6 +17,7 @@ import type { } from './interface'; import type { RawValueType, FlattenOptionsType } from './interface/generator'; import { fillFieldNames } from './utils/valueUtil'; +import { isPlatformMac } from './utils/platformUtil'; export interface OptionListProps { prefixCls: string; @@ -183,9 +184,11 @@ const OptionList: React.RefForwardingComponent< // ========================= Keyboard ========================= React.useImperativeHandle(ref, () => ({ onKeyDown: (event) => { - const { which } = event; + const { which, ctrlKey } = event; switch (which) { - // >>> Arrow keys + // >>> Arrow keys & ctrl + n/p on Mac + case KeyCode.N: + case KeyCode.P: case KeyCode.UP: case KeyCode.DOWN: { let offset = 0; @@ -193,6 +196,12 @@ const OptionList: React.RefForwardingComponent< offset = -1; } else if (which === KeyCode.DOWN) { offset = 1; + } else if (isPlatformMac() && ctrlKey) { + if (which === KeyCode.N) { + offset = 1; + } else if (which === KeyCode.P) { + offset = -1; + } } if (offset !== 0) { diff --git a/src/utils/platformUtil.ts b/src/utils/platformUtil.ts new file mode 100644 index 000000000..563b449d8 --- /dev/null +++ b/src/utils/platformUtil.ts @@ -0,0 +1,3 @@ +export function isPlatformMac(): boolean { + return /mac\sos/i.test(navigator.appVersion); +} diff --git a/tests/OptionList.test.tsx b/tests/OptionList.test.tsx index 350ba02f3..19bb6d925 100644 --- a/tests/OptionList.test.tsx +++ b/tests/OptionList.test.tsx @@ -2,9 +2,10 @@ import { mount } from 'enzyme'; import KeyCode from 'rc-util/lib/KeyCode'; import { act } from 'react-dom/test-utils'; import React from 'react'; -import OptionList, { OptionListProps, RefOptionListProps } from '../src/OptionList'; +import type { OptionListProps, RefOptionListProps } from '../src/OptionList'; +import OptionList from '../src/OptionList'; import { injectRunAllTimers } from './utils/common'; -import { OptionsType } from '../src/interface'; +import type { OptionsType } from '../src/interface'; import { flattenOptions } from '../src/utils/valueUtil'; describe('OptionList', () => { @@ -103,6 +104,40 @@ describe('OptionList', () => { ); }); + // this won't pass on test environment which is usually running on Linux + // you can test it with a Mac + xit('special key operation on Mac', () => { + const onActiveValue = jest.fn(); + const listRef = React.createRef(); + mount( + generateList({ + options: [{ value: '1' }, { value: '2' }], + onActiveValue, + ref: listRef, + }), + ); + + onActiveValue.mockReset(); + act(() => { + listRef.current.onKeyDown({ which: KeyCode.N, ctrlKey: true } as any); + }); + expect(onActiveValue).toHaveBeenCalledWith( + '2', + expect.anything(), + expect.objectContaining({ source: 'keyboard' }), + ); + + onActiveValue.mockReset(); + act(() => { + listRef.current.onKeyDown({ which: KeyCode.P, ctrlKey: true } as any); + }); + expect(onActiveValue).toHaveBeenCalledWith( + '1', + expect.anything(), + expect.objectContaining({ source: 'keyboard' }), + ); + }); + it('hover to active', () => { const onActiveValue = jest.fn(); const wrapper = mount( @@ -113,10 +148,7 @@ describe('OptionList', () => { ); onActiveValue.mockReset(); - wrapper - .find('.rc-select-item-option') - .last() - .simulate('mouseMove'); + wrapper.find('.rc-select-item-option').last().simulate('mouseMove'); expect(onActiveValue).toHaveBeenCalledWith( '2', expect.anything(), @@ -125,10 +157,7 @@ describe('OptionList', () => { // Same item not repeat trigger onActiveValue.mockReset(); - wrapper - .find('.rc-select-item-option') - .last() - .simulate('mouseMove'); + wrapper.find('.rc-select-item-option').last().simulate('mouseMove'); expect(onActiveValue).not.toHaveBeenCalled(); }); @@ -140,12 +169,9 @@ describe('OptionList', () => { ); const preventDefault = jest.fn(); - wrapper - .find('.rc-select-item-option') - .last() - .simulate('mouseDown', { - preventDefault, - }); + wrapper.find('.rc-select-item-option').last().simulate('mouseDown', { + preventDefault, + }); expect(preventDefault).toHaveBeenCalled(); }); @@ -153,16 +179,14 @@ describe('OptionList', () => { it('Data attributes should be set correct', () => { const wrapper = mount( generateList({ - options: [{ value: '1', label: 'my-label' }, { value: '2', 'data-num': '123' }], + options: [ + { value: '1', label: 'my-label' }, + { value: '2', 'data-num': '123' }, + ], }), ); - expect( - wrapper - .find('.rc-select-item-option') - .last() - .prop('data-num'), - ).toBe('123'); + expect(wrapper.find('.rc-select-item-option').last().prop('data-num')).toBe('123'); }); it('should render title defaultly', () => { @@ -171,12 +195,7 @@ describe('OptionList', () => { options: [{ value: '1', label: 'my-label' }], }), ); - expect( - wrapper - .find('.rc-select-item-option') - .first() - .prop('title'), - ).toBe('my-label'); + expect(wrapper.find('.rc-select-item-option').first().prop('title')).toBe('my-label'); }); it('should render title', () => { @@ -185,12 +204,7 @@ describe('OptionList', () => { options: [{ value: '1', label: 'my-label', title: 'title' }], }), ); - expect( - wrapper - .find('.rc-select-item-option') - .first() - .prop('title'), - ).toBe('title'); + expect(wrapper.find('.rc-select-item-option').first().prop('title')).toBe('title'); }); it('should not render title when title is empty string', () => { @@ -199,12 +213,7 @@ describe('OptionList', () => { options: [{ value: '1', label: 'my-label', title: '' }], }), ); - expect( - wrapper - .find('.rc-select-item-option') - .first() - .prop('title'), - ).toBe(''); + expect(wrapper.find('.rc-select-item-option').first().prop('title')).toBe(''); }); it('should render title from label when title is undefined', () => { @@ -213,12 +222,7 @@ describe('OptionList', () => { options: [{ value: '1', label: 'my-label', title: undefined }], }), ); - expect( - wrapper - .find('.rc-select-item-option') - .first() - .prop('title'), - ).toBe('my-label'); + expect(wrapper.find('.rc-select-item-option').first().prop('title')).toBe('my-label'); }); it('should not render title defaultly when label is ReactNode', () => { @@ -227,11 +231,6 @@ describe('OptionList', () => { options: [{ value: '1', label:
label
}], }), ); - expect( - wrapper - .find('.rc-select-item-option') - .first() - .prop('title'), - ).toBe(undefined); + expect(wrapper.find('.rc-select-item-option').first().prop('title')).toBe(undefined); }); }); From a810424d9a727d54eb7312b3c39be1b3c0116495 Mon Sep 17 00:00:00 2001 From: wendellhu Date: Thu, 12 Aug 2021 16:38:34 +0800 Subject: [PATCH 2/5] test: mock platform --- src/utils/__mocks__/platformUtil.ts | 3 +++ tests/OptionList.test.tsx | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 src/utils/__mocks__/platformUtil.ts diff --git a/src/utils/__mocks__/platformUtil.ts b/src/utils/__mocks__/platformUtil.ts new file mode 100644 index 000000000..e4b2685df --- /dev/null +++ b/src/utils/__mocks__/platformUtil.ts @@ -0,0 +1,3 @@ +export function isPlatformMac() { + return true; +} diff --git a/tests/OptionList.test.tsx b/tests/OptionList.test.tsx index 19bb6d925..bb2c1f367 100644 --- a/tests/OptionList.test.tsx +++ b/tests/OptionList.test.tsx @@ -8,6 +8,8 @@ import { injectRunAllTimers } from './utils/common'; import type { OptionsType } from '../src/interface'; import { flattenOptions } from '../src/utils/valueUtil'; +jest.mock('../src/utils/platformUtil'); + describe('OptionList', () => { injectRunAllTimers(jest); @@ -106,7 +108,7 @@ describe('OptionList', () => { // this won't pass on test environment which is usually running on Linux // you can test it with a Mac - xit('special key operation on Mac', () => { + it('special key operation on Mac', () => { const onActiveValue = jest.fn(); const listRef = React.createRef(); mount( From 89e62d8e3080b970cec78793c6dc88cc06bce077 Mon Sep 17 00:00:00 2001 From: wendellhu Date: Thu, 12 Aug 2021 16:39:52 +0800 Subject: [PATCH 3/5] chore: change doc --- tests/OptionList.test.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/OptionList.test.tsx b/tests/OptionList.test.tsx index bb2c1f367..b1a469e75 100644 --- a/tests/OptionList.test.tsx +++ b/tests/OptionList.test.tsx @@ -106,8 +106,7 @@ describe('OptionList', () => { ); }); - // this won't pass on test environment which is usually running on Linux - // you can test it with a Mac + // mocked how we detect running platform in test environment it('special key operation on Mac', () => { const onActiveValue = jest.fn(); const listRef = React.createRef(); From 63c31e308a35ddb2fb705046e03ac74ab8a6568f Mon Sep 17 00:00:00 2001 From: wendellhu Date: Thu, 12 Aug 2021 16:44:52 +0800 Subject: [PATCH 4/5] test: ignore file coverage on platformUtil --- src/utils/platformUtil.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/platformUtil.ts b/src/utils/platformUtil.ts index 563b449d8..2d13189ef 100644 --- a/src/utils/platformUtil.ts +++ b/src/utils/platformUtil.ts @@ -1,3 +1,4 @@ +/* istanbul ignore file */ export function isPlatformMac(): boolean { return /mac\sos/i.test(navigator.appVersion); } From 0bdc462e3a0086fb5f279a2f8dce1a3b632d9cc9 Mon Sep 17 00:00:00 2001 From: wendellhu Date: Fri, 13 Aug 2021 13:50:07 +0800 Subject: [PATCH 5/5] fix: fix mac platform on Firefox --- src/utils/platformUtil.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/platformUtil.ts b/src/utils/platformUtil.ts index 2d13189ef..f6bdcc68b 100644 --- a/src/utils/platformUtil.ts +++ b/src/utils/platformUtil.ts @@ -1,4 +1,4 @@ /* istanbul ignore file */ export function isPlatformMac(): boolean { - return /mac\sos/i.test(navigator.appVersion); + return /(mac\sos|macintosh)/i.test(navigator.appVersion); }