From b601060c332f073eea52d07bee6a58386bbc1b57 Mon Sep 17 00:00:00 2001 From: Sasha Kondrashov Date: Sun, 3 Mar 2024 01:17:48 -0500 Subject: [PATCH] dropdown --- src/modules/Dropdown/Dropdown.d.ts | 53 ++++---- src/modules/Dropdown/Dropdown.js | 8 +- test/specs/modules/Dropdown/Dropdown-test.js | 122 ++++++++++--------- 3 files changed, 98 insertions(+), 85 deletions(-) diff --git a/src/modules/Dropdown/Dropdown.d.ts b/src/modules/Dropdown/Dropdown.d.ts index b5ae9c65e5..aff48eda66 100644 --- a/src/modules/Dropdown/Dropdown.d.ts +++ b/src/modules/Dropdown/Dropdown.d.ts @@ -126,49 +126,57 @@ export interface StrictDropdownProps { * Called when a user adds a new item. Use this to update the options list. * * @param {SyntheticEvent} event - React's original SyntheticEvent. - * @param {object} data - All props and the new item's value. + * @param {object} props - All props. */ - onAddItem?: (event: React.SyntheticEvent, data: DropdownProps) => void + onAddItem?: ( + event: React.SyntheticEvent, + props: DropdownProps, + value: boolean | number | string | (boolean | number | string)[], + ) => void /** * Called on blur. * * @param {SyntheticEvent} event - React's original SyntheticEvent. - * @param {object} data - All props. + * @param {object} props - All props. */ - onBlur?: (event: React.FocusEvent, data: DropdownProps) => void + onBlur?: (event: React.FocusEvent, props: DropdownProps) => void /** * Called when the user attempts to change the value. * * @param {SyntheticEvent} event - React's original SyntheticEvent. - * @param {object} data - All props and proposed value. + * @param {object} props - All props. */ - onChange?: (event: React.SyntheticEvent, data: DropdownProps) => void + onChange?: ( + event: React.SyntheticEvent, + props: DropdownProps, + value: boolean | number | string | (boolean | number | string)[], + ) => void /** * Called on click. * * @param {SyntheticEvent} event - React's original SyntheticEvent. - * @param {object} data - All props. + * @param {object} props - All props. */ - onClick?: (event: React.MouseEvent, data: DropdownProps) => void + onClick?: (event: React.MouseEvent, props: DropdownProps) => void /** * Called when a close event happens. * * @param {SyntheticEvent} event - React's original SyntheticEvent. - * @param {object} data - All props. + * @param {object} props - All props. */ - onClose?: (event: React.SyntheticEvent, data: DropdownProps) => void + onClose?: (event: React.SyntheticEvent, props: DropdownProps) => void /** * Called on focus. * * @param {SyntheticEvent} event - React's original SyntheticEvent. - * @param {object} data - All props. + * @param {object} props - All props. */ - onFocus?: (event: React.FocusEvent, data: DropdownProps) => void + onFocus?: (event: React.FocusEvent, props: DropdownProps) => void /** * Called when a multi-select label is clicked. @@ -182,27 +190,29 @@ export interface StrictDropdownProps { * Called on mousedown. * * @param {SyntheticEvent} event - React's original SyntheticEvent. - * @param {object} data - All props. + * @param {object} props - All props. */ - onMouseDown?: (event: React.MouseEvent, data: DropdownProps) => void + onMouseDown?: (event: React.MouseEvent, props: DropdownProps) => void /** * Called when an open event happens. * * @param {SyntheticEvent} event - React's original SyntheticEvent. - * @param {object} data - All props. + * @param {object} props - All props. */ - onOpen?: (event: React.SyntheticEvent, data: DropdownProps) => void + onOpen?: (event: React.SyntheticEvent, props: DropdownProps) => void /** * Called on search input change. * * @param {SyntheticEvent} event - React's original SyntheticEvent. - * @param {object} data - All props, includes current value of searchQuery. + * @param {object} props - All props. + * @param {string} searchQuery - Current value of searchQuery. */ onSearchChange?: ( event: React.SyntheticEvent, - data: DropdownOnSearchChangeData, + data: DropdownProps, + searchQuery: string, ) => void /** Controls whether or not the dropdown menu is displayed. */ @@ -292,13 +302,6 @@ export interface StrictDropdownProps { wrapSelection?: boolean } -/* TODO: replace with DropdownProps when #1829 will be fixed: - * https://github.com/Semantic-Org/Semantic-UI-React/issues/1829 - */ -export interface DropdownOnSearchChangeData extends DropdownProps { - searchQuery: string -} - declare const Dropdown: ForwardRefComponent & { Divider: typeof DropdownDivider Header: typeof DropdownHeader diff --git a/src/modules/Dropdown/Dropdown.js b/src/modules/Dropdown/Dropdown.js index d77720b102..e9f50b45fc 100644 --- a/src/modules/Dropdown/Dropdown.js +++ b/src/modules/Dropdown/Dropdown.js @@ -231,7 +231,7 @@ class DropdownInner extends Component { // can't rely on props.value if we are controlled handleChange = (e, value) => { debug('handleChange()', value) - _.invoke(this.props, 'onChange', e, { ...this.props, value }) + _.invoke(this.props, 'onChange', e, this.props, value) } closeOnChange = (e) => { @@ -342,7 +342,7 @@ class DropdownInner extends Component { // Heads up! This event handler should be called after `onChange` // Notify the onAddItem prop if this is a new value if (item['data-additional']) { - _.invoke(this.props, 'onAddItem', e, { ...this.props, value: selectedValue }) + _.invoke(this.props, 'onAddItem', e, this.props, selectedValue) } } @@ -544,7 +544,7 @@ class DropdownInner extends Component { // Heads up! This event handler should be called after `onChange` // Notify the onAddItem prop if this is a new value if (isAdditionItem) { - _.invoke(this.props, 'onAddItem', e, { ...this.props, value }) + _.invoke(this.props, 'onAddItem', e, this.props, value) } } @@ -592,7 +592,7 @@ class DropdownInner extends Component { const { open } = this.state const newQuery = value - _.invoke(this.props, 'onSearchChange', e, { ...this.props, searchQuery: newQuery }) + _.invoke(this.props, 'onSearchChange', e, this.props, newQuery) this.setState({ searchQuery: newQuery, selectedIndex: 0 }) // open search dropdown on search query diff --git a/test/specs/modules/Dropdown/Dropdown-test.js b/test/specs/modules/Dropdown/Dropdown-test.js index 4b3d9e8d06..9dabce0eaf 100644 --- a/test/specs/modules/Dropdown/Dropdown-test.js +++ b/test/specs/modules/Dropdown/Dropdown-test.js @@ -394,7 +394,7 @@ describe('Dropdown', () => { wrapper.find('i.clear').simulate('click', event) onChange.should.have.been.calledOnce() - onChange.should.have.been.calledWithMatch(event, { value: '' }) + onChange.should.have.been.calledWithMatch(event, {}, '') wrapper.should.have.exactly(1).descendants('.selected.item') wrapper.find('.item').at(0).should.have.className('selected') }) @@ -416,7 +416,7 @@ describe('Dropdown', () => { wrapper.find('i.clear').simulate('click', event) onChange.should.have.been.calledOnce() - onChange.should.have.been.calledWithMatch(event, { value: [] }) + onChange.should.have.been.calledWithMatch(event, {}, []) wrapper.should.have.exactly(1).descendants('.selected.item') wrapper.find('.item').at(0).should.have.className('selected') }) @@ -1627,11 +1627,6 @@ describe('Dropdown', () => { }) describe('selecting items', () => { - let spy - beforeEach(() => { - spy = sandbox.spy() - }) - it('does not close the menu on clicking on a label', () => { const value = _.map(options, 'value') const randomIndex = _.random(options.length - 1) @@ -1659,19 +1654,26 @@ describe('Dropdown', () => { }) it('calls onLabelClick', () => { + const onLabelClick = sandbox.spy() const value = _.map(options, 'value') const randomIndex = _.random(options.length - 1) const randomValue = value[randomIndex] wrapperMount( - , + , ) .simulate('click', nativeEvent) .find('Label') .at(randomIndex) .simulate('click', nativeEvent) - spy.should.have.been.calledWithMatch({}, { value: randomValue }) + onLabelClick.should.have.been.calledWithMatch({}, { value: randomValue }) }) it('refocuses search on select', () => { @@ -1693,38 +1695,40 @@ describe('Dropdown', () => { const randomIndex = _.random(options.length - 1) const randomValue = value[randomIndex] const expected = _.without(value, randomValue) - const spy = sandbox.spy() - wrapperMount() + const onChange = sandbox.spy() + wrapperMount( + , + ) wrapper.find('.delete.icon').at(randomIndex).simulate('click') - spy.should.have.been.calledOnce() - spy.should.have.been.calledWithMatch({}, { value: expected }) + onChange.should.have.been.calledOnce() + onChange.should.have.been.calledWithMatch({}, {}, expected) }) }) }) describe('removing items on backspace', () => { - let spy + let onChange beforeEach(() => { - spy = sandbox.spy() + onChange = sandbox.spy() }) it('does nothing without selected items', () => { - wrapperMount() + wrapperMount() // open wrapper.simulate('click') domEvent.keyDown(document, { key: 'Backspace' }) - spy.should.not.have.been.called() + onChange.should.not.have.been.called() }) it('removes the last item when there is no search query', () => { const value = _.map(options, 'value') const expected = _.dropRight(value) wrapperMount( - , + , ) // open @@ -1732,8 +1736,8 @@ describe('Dropdown', () => { domEvent.keyDown(document, { key: 'Backspace' }) - spy.should.have.been.calledOnce() - spy.should.have.been.calledWithMatch({}, { value: expected }) + onChange.should.have.been.calledOnce() + onChange.should.have.been.calledWithMatch({}, {}, expected) }) it('removes the last item when there is no search query when uncontrolled', () => { @@ -1746,7 +1750,7 @@ describe('Dropdown', () => { defaultValue={value} multiple search - onChange={spy} + onChange={onChange} />, ) @@ -1754,8 +1758,8 @@ describe('Dropdown', () => { wrapper.simulate('click') domEvent.keyDown(document, { key: 'Backspace' }) - spy.should.have.been.calledOnce() - spy.should.have.been.calledWithMatch({}, { value: expected }) + onChange.should.have.been.calledOnce() + onChange.should.have.been.calledWithMatch({}, {}, expected) }) it('does not remove the last item when there is a search query', () => { @@ -1763,7 +1767,7 @@ describe('Dropdown', () => { const searchQuery = _.sample(options).text const value = _.map(options, 'value') wrapperMount( - , + , ) // open and simulate search @@ -1772,105 +1776,111 @@ describe('Dropdown', () => { domEvent.keyDown(document, { key: 'Backspace' }) - spy.should.not.have.been.called() + onChange.should.not.have.been.called() }) it('does not remove items for multiple dropdowns without search', () => { const value = _.map(options, 'value') - wrapperMount() + wrapperMount( + , + ) // open wrapper.simulate('click') domEvent.keyDown(document, { key: 'Backspace' }) - spy.should.not.have.been.called() + onChange.should.not.have.been.called() }) }) describe('onChange', () => { - let spy + let onChange beforeEach(() => { - spy = sandbox.spy() + onChange = sandbox.spy() }) it('is called with event and value on item click', () => { const randomIndex = _.random(options.length - 1) const randomValue = options[randomIndex].value - wrapperMount() + wrapperMount() .simulate('click') .find('DropdownItem') .at(randomIndex) .simulate('click') - spy.should.have.been.calledOnce() - spy.should.have.been.calledWithMatch({}, { value: randomValue }) + onChange.should.have.been.calledOnce() + onChange.should.have.been.calledWithMatch({}, {}, randomValue) }) it('is not called when value is not changed on item click', () => { - wrapperMount() + wrapperMount() wrapper.simulate('click').find('DropdownItem').at(0).simulate('click') - spy.should.have.been.calledOnce() + onChange.should.have.been.calledOnce() // TODO: try reenable after Enzyme update // https://github.com/Semantic-Org/Semantic-UI-React/pull/3747#issuecomment-522018329 // dropdownMenuIsClosed() wrapper.simulate('click').find('DropdownItem').at(0).simulate('click') - spy.should.have.been.calledOnce() + onChange.should.have.been.calledOnce() // TODO: try reenable after Enzyme update // dropdownMenuIsClosed() }) it('is called with event and value when pressing enter on a selected item', () => { const firstValue = options[0].value - wrapperMount().simulate('click') + wrapperMount().simulate('click') wrapper.simulate('keydown', { key: 'Enter' }) - spy.should.have.been.calledOnce() - spy.should.have.been.calledWithMatch({}, { value: firstValue }) + onChange.should.have.been.calledOnce() + onChange.should.have.been.calledWithMatch({}, {}, firstValue) }) it('is called with event and value when blurring', () => { const firstValue = options[0].value - wrapperMount() + wrapperMount() .simulate('focus') // open, highlights first item .simulate('blur') // blur should activate selected item - spy.should.have.been.calledOnce() - spy.should.have.been.calledWithMatch({}, { value: firstValue }) + onChange.should.have.been.calledOnce() + onChange.should.have.been.calledWithMatch({}, {}, firstValue) }) it('is not called on blur when closed', () => { - wrapperMount() + wrapperMount() .simulate('focus') .simulate('blur') - spy.should.not.have.been.called() + onChange.should.not.have.been.called() }) it('is not called on blur when selectOnBlur is false', () => { - wrapperMount() + wrapperMount( + , + ) .simulate('focus') .simulate('click') wrapper.simulate('blur') - spy.should.not.have.been.called() + onChange.should.not.have.been.called() }) it('is not called on blur with multiple select', () => { - wrapperMount() + wrapperMount() .simulate('focus') .simulate('click') wrapper.simulate('blur') - spy.should.not.have.been.called() + onChange.should.not.have.been.called() }) it('is not called when updating the value prop', () => { const value = _.sample(options).value const next = _.sample(_.without(options, value)).value - wrapperMount().setProps({ + wrapperMount( + , + ).setProps({ value: next, }) - spy.should.not.have.been.called() + onChange.should.not.have.been.called() }) }) @@ -1946,18 +1956,18 @@ describe('Dropdown', () => { describe('onSearchChange', () => { it('is called with (event, value) on search input change', () => { - const spy = sandbox.spy() - wrapperMount() + const onSearchChange = sandbox.spy() + wrapperMount() .find('input.search') .simulate('change', { target: { value: 'a' }, stopPropagation: _.noop }) - spy.should.have.been.calledOnce() - spy.should.have.been.calledWithMatch( + onSearchChange.should.have.been.calledOnce() + onSearchChange.should.have.been.calledWithMatch( { target: { value: 'a' } }, { search: true, - searchQuery: 'a', }, + 'a', ) }) @@ -2709,7 +2719,7 @@ describe('Dropdown', () => { onChange.should.have.been.calledOnce() onAddItem.should.have.been.calledOnce() - onAddItem.should.have.been.calledWithMatch({}, { value: 'boo' }) + onAddItem.should.have.been.calledWithMatch({}, {}, 'boo') onAddItem.should.have.been.calledImmediatelyAfter(onChange) }) @@ -2734,7 +2744,7 @@ describe('Dropdown', () => { onChange.should.have.been.calledOnce() onAddItem.should.have.been.calledOnce() - onAddItem.should.have.been.calledWithMatch({}, { value: 'boo' }) + onAddItem.should.have.been.calledWithMatch({}, {}, 'boo') onAddItem.should.have.been.calledImmediatelyAfter(onChange) })