diff --git a/src/utils/valueUtil.ts b/src/utils/valueUtil.ts index 4d3d1439d..5a129e09d 100644 --- a/src/utils/valueUtil.ts +++ b/src/utils/valueUtil.ts @@ -67,6 +67,26 @@ export function flattenOptions(options: SelectOptionsType): FlattenOptionData[] return flattenList; } +/** + * Inject `props` into `option` for legacy usage + */ +function injectPropsWithOption(option: T): T { + const newOption = { ...option }; + if (!('props' in newOption)) { + Object.defineProperty(newOption, 'props', { + get() { + warning( + false, + 'Return type is option instead of Option instance. Please read value directly instead of reading from `props`.', + ); + return newOption; + }, + }); + } + + return newOption; +} + export function findValueOption( values: RawValueType[], options: FlattenOptionData[], @@ -81,7 +101,7 @@ export function findValueOption( } }); - return values.map(val => optionMap.get(val)); + return values.map(val => injectPropsWithOption(optionMap.get(val))); } export const getLabeledValue: GetLabeledValue = ( @@ -193,7 +213,7 @@ export function filterOptions( return; } - if (filterFunc(searchValue, item)) { + if (filterFunc(searchValue, injectPropsWithOption(item))) { filteredOptions.push(item); } }); @@ -227,11 +247,7 @@ export function getSeparatedContent(text: string, tokens: string[]): string[] { export function isValueDisabled(value: RawValueType, options: FlattenOptionData[]): boolean { const option = findValueOption([value], options)[0]; - if (option) { - return option.disabled; - } - - return false; + return option.disabled; } /** diff --git a/tests/Combobox.test.tsx b/tests/Combobox.test.tsx index b9529c327..a93d78bf6 100644 --- a/tests/Combobox.test.tsx +++ b/tests/Combobox.test.tsx @@ -6,12 +6,7 @@ import Select, { Option, SelectProps } from '../src'; import focusTest from './shared/focusTest'; import keyDownTest from './shared/keyDownTest'; import openControlledTest from './shared/openControlledTest'; -import { - expectOpen, - toggleOpen, - selectItem, - injectRunAllTimers, -} from './utils/common'; +import { expectOpen, toggleOpen, selectItem, injectRunAllTimers } from './utils/common'; import allowClearTest from './shared/allowClearTest'; import throwOptionValue from './shared/throwOptionValue'; @@ -71,10 +66,10 @@ describe('Select.Combobox', () => { ); wrapper.find('input').simulate('change', { target: { value: '1' } }); - expect(handleChange).toBeCalledWith('1', undefined); + expect(handleChange).toHaveBeenCalledWith('1', {}); wrapper.find('input').simulate('change', { target: { value: '22' } }); - expect(handleChange).toBeCalledWith( + expect(handleChange).toHaveBeenCalledWith( '22', expect.objectContaining({ children: '22', @@ -134,6 +129,7 @@ describe('Select.Combobox', () => { public state = { data: [], }; + public handleChange = () => { setTimeout(() => { this.setState({ @@ -141,6 +137,7 @@ describe('Select.Combobox', () => { }); }, 500); }; + public render() { const options = this.state.data.map(item => ); return ( @@ -171,6 +168,7 @@ describe('Select.Combobox', () => { public state = { data: [{ key: '1', label: '1' }, { key: '2', label: '2' }], }; + public onSelect = () => { setTimeout(() => { this.setState({ @@ -178,6 +176,7 @@ describe('Select.Combobox', () => { }); }, 500); }; + public render() { const options = this.state.data.map(item => ); return ( @@ -208,13 +207,7 @@ describe('Select.Combobox', () => { const handleChange = jest.fn(); const handleSelect = jest.fn(); const wrapper = mount( - , @@ -222,13 +215,13 @@ describe('Select.Combobox', () => { const input = wrapper.find('input'); input.simulate('keyDown', { which: KeyCode.DOWN }); expect(wrapper.find('input').props().value).toEqual('One'); - expect(handleChange).not.toBeCalled(); - expect(handleSelect).not.toBeCalled(); + expect(handleChange).not.toHaveBeenCalled(); + expect(handleSelect).not.toHaveBeenCalled(); input.simulate('keyDown', { which: KeyCode.ENTER }); expect(wrapper.find('input').props().value).toEqual('One'); - expect(handleChange).toBeCalledWith('One', expect.objectContaining({ value: 'One' })); - expect(handleSelect).toBeCalledWith('One', expect.objectContaining({ value: 'One' })); + expect(handleChange).toHaveBeenCalledWith('One', expect.objectContaining({ value: 'One' })); + expect(handleSelect).toHaveBeenCalledWith('One', expect.objectContaining({ value: 'One' })); }); it("should hide clear icon when value is ''", () => { @@ -283,9 +276,9 @@ describe('Select.Combobox', () => { public render() { return ( ); } @@ -328,7 +321,7 @@ describe('Select.Combobox', () => { }); jest.runAllTimers(); wrapper.update(); - expect(onDropdownVisibleChange).toBeCalledWith(false); + expect(onDropdownVisibleChange).toHaveBeenCalledWith(false); jest.useRealTimers(); }); }); diff --git a/tests/Select.test.tsx b/tests/Select.test.tsx index 3a29ab4a6..fc79eb9f1 100644 --- a/tests/Select.test.tsx +++ b/tests/Select.test.tsx @@ -1217,4 +1217,87 @@ describe('Select.Basic', () => { ); errorSpy.mockRestore(); }); + + describe('warning if use `props` to read data', () => { + it('filterOption', () => { + resetWarned(); + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + const wrapper = mount( + , + ); + + wrapper.find('input').simulate('change', { target: { value: 'l' } }); + expect(wrapper.find('List').props().data).toHaveLength(1); + expect(wrapper.find('div.rc-select-item-option-content').text()).toBe('Light'); + + expect(errorSpy).toHaveBeenCalledWith( + 'Warning: Return type is option instead of Option instance. Please read value directly instead of reading from `props`.', + ); + errorSpy.mockRestore(); + }); + + it('Select & Deselect', () => { + resetWarned(); + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + const readPropsFunc = (_, opt) => { + expect(opt.props).toBeTruthy(); + }; + + const wrapper = mount( + , + ); + + toggleOpen(wrapper); + selectItem(wrapper); + expect(errorSpy).toHaveBeenCalledWith( + 'Warning: Return type is option instead of Option instance. Please read value directly instead of reading from `props`.', + ); + + errorSpy.mockReset(); + resetWarned(); + selectItem(wrapper); + expect(errorSpy).toHaveBeenCalledWith( + 'Warning: Return type is option instead of Option instance. Please read value directly instead of reading from `props`.', + ); + + errorSpy.mockRestore(); + }); + + it('onChange', () => { + resetWarned(); + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + const readPropsFunc = (_, opt) => { + expect(opt.props).toBeTruthy(); + }; + + const wrapper = mount( + , + ); + + toggleOpen(wrapper); + selectItem(wrapper); + expect(errorSpy).toHaveBeenCalledWith( + 'Warning: Return type is option instead of Option instance. Please read value directly instead of reading from `props`.', + ); + + errorSpy.mockRestore(); + }); + }); }); diff --git a/tests/Tags.test.tsx b/tests/Tags.test.tsx index 49afd392f..728e45f67 100644 --- a/tests/Tags.test.tsx +++ b/tests/Tags.test.tsx @@ -58,7 +58,7 @@ describe('Select.Tags', () => { jest.runAllTimers(); expect(findSelection(wrapper).text()).toBe('foo'); - expect(onChange).toHaveBeenCalledWith(['foo'], [undefined]); + expect(onChange).toHaveBeenCalledWith(['foo'], [{}]); }); it('tokenize input', () => { @@ -76,9 +76,9 @@ describe('Select.Tags', () => { wrapper.find('input').simulate('change', { target: { value: '2,3,4' } }); - expect(handleChange).toBeCalledWith(['2', '3', '4'], expect.anything()); + expect(handleChange).toHaveBeenCalledWith(['2', '3', '4'], expect.anything()); expect(handleSelect).toHaveBeenCalledTimes(3); - expect(handleSelect).toHaveBeenLastCalledWith('4', undefined); + expect(handleSelect).toHaveBeenLastCalledWith('4', expect.anything()); expect(findSelection(wrapper).text()).toEqual('2'); expect(findSelection(wrapper, 1).text()).toEqual('3'); expect(findSelection(wrapper, 2).text()).toEqual('4'); @@ -187,7 +187,7 @@ describe('Select.Tags', () => { it('should work fine when filterOption function exists', () => { const children = []; - for (let i = 10; i < 36; i++) { + for (let i = 10; i < 36; i += 1) { children.push( , ], }; + public select: any; public componentDidMount() { @@ -65,14 +66,11 @@ export default function dynamicChildrenTest(mode: any, props?: Partial, ], }; + public select: any; public componentDidMount() { @@ -147,6 +146,7 @@ export default function dynamicChildrenTest(mode: any, props?: Partial2-label, ], }; + public select: any; public componentDidMount() {