diff --git a/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js b/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js index 65f8c246d25..56952f47fca 100644 --- a/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js +++ b/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js @@ -475,7 +475,6 @@ class AutocompleteInstance extends React.PureComponent { clearTimeout(this._ariaLiveUpdateTimeout) clearTimeout(this._focusTimeout) clearTimeout(this._blurTimeout) - clearTimeout(this._toggleVisibleTimeout) } setVisible = (args = null, onStateComplete = null) => { @@ -564,6 +563,8 @@ class AutocompleteInstance extends React.PureComponent { const data = this.runFilter(value, options) const count = this.countData(data) + const { keep_value, keep_value_and_selection } = this.props + if (value && value.length > 0) { // show the "no_options" message if (count === 0) { @@ -583,15 +584,12 @@ class AutocompleteInstance extends React.PureComponent { } } } else { - if ( - !isTrue(this.props.keep_value) && - !isTrue(this.props.keep_value_and_selection) - ) { + if (!isTrue(keep_value) && !isTrue(keep_value_and_selection)) { // this will not remove selected_item this.totalReset() } - if (isTrue(this.props.keep_value)) { + if (isTrue(keep_value)) { this.resetSelectedItem() } @@ -893,15 +891,38 @@ class AutocompleteInstance extends React.PureComponent { } } - onReserveActivityHandler = (event) => { - // Prevent to happen the on_blur event during drawer-list activity - this.__preventFiringBlurEvent = + reserveActivityHandler = (event = null) => { + this.__preventFiringBlurEvent = Boolean( event.key === 'enter' || - event.key === 'space' || - (event.target && getPreviousSibling('dnb-drawer-list', event.target)) + (event?.currentTarget + ? getPreviousSibling('dnb-drawer-list', event.currentTarget) || + getPreviousSibling( + 'dnb-input__submit-button__button', + event.currentTarget + ) + : false) + ) + + if (this.__preventFiringBlurEvent) { + setTimeout( + () => { + this.__preventFiringBlurEvent = false + }, + isTrue(this.props.no_animation) ? 1 : DrawerList.blurDelay + ) + } } onBlurHandler = (event) => { + if ( + this.__preventFiringBlurEvent || + this.context.drawerList.hasFocusOnElement || + this.state.hasBlur + ) { + this.__preventFiringBlurEvent = null + return false + } + const { open_on_focus, keep_value, @@ -910,60 +931,54 @@ class AutocompleteInstance extends React.PureComponent { no_animation, } = this.props - if ( - !this.state.hasBlur && - !this.__preventFiringBlurEvent && - !this.context.drawerList.hasFocusOnElement - ) { - dispatchCustomElementEvent(this, 'on_blur', { - event, - ...this.getEventObjects('on_blur'), - }) + dispatchCustomElementEvent(this, 'on_blur', { + event, + ...this.getEventObjects('on_blur'), + }) + + this.setState({ + hasBlur: true, + hasFocus: false, + }) + if (!isTrue(keep_value) && !isTrue(keep_value_and_selection)) { this.setState({ - hasBlur: true, - hasFocus: false, + typedInputValue: null, + _listenForPropChanges: false, }) + } - if (!isTrue(keep_value_and_selection)) { - this.setState({ - typedInputValue: null, - _listenForPropChanges: false, - }) - } + if (!isTrue(prevent_selection)) { + const existingValue = this.state.inputValue - if (!isTrue(prevent_selection)) { - const existingValue = this.state.inputValue + if (!isTrue(keep_value) && !isTrue(keep_value_and_selection)) { this.clearInputValue() + } - const resetAfterClose = () => { - if ( - !isTrue(keep_value) || - !existingValue || - this.hasSelectedItem() - ) { - this.resetActiveItem() - } - this.resetFilter() - } - - if (isTrue(no_animation)) { - resetAfterClose() - } else { - clearTimeout(this._blurTimeout) - this._blurTimeout = setTimeout( - resetAfterClose, - DrawerList.blurDelay - ) // only to let the animation pass, before we make the effect. Else this would be a visible change + const resetAfterClose = () => { + if ( + !isTrue(keep_value) || + !existingValue || + this.hasSelectedItem() + ) { + this.resetActiveItem() } + this.resetFilter() } - if (isTrue(open_on_focus)) { - this.setHidden() + if (isTrue(no_animation)) { + resetAfterClose() + } else { + clearTimeout(this._blurTimeout) + this._blurTimeout = setTimeout( + resetAfterClose, + DrawerList.blurDelay + ) // only to let the animation pass, before we make the effect. Else this would be a visible change } - } else if (this.__preventFiringBlurEvent) { - this.__preventFiringBlurEvent = null - return false + } + + if (isTrue(open_on_focus)) { + this.setHidden() } } @@ -1029,8 +1044,7 @@ class AutocompleteInstance extends React.PureComponent { (!this.hasValidData() || !this.hasSelectedItem()) && !this.hasActiveItem() ) { - clearTimeout(this._toggleVisibleTimeout) - this._toggleVisibleTimeout = setTimeout(this.toggleVisible, 1) // to make sure we first handle the DrawerList key enter, before we update the state with a toggle/visible. Else the submit is not set properly + this.toggleVisible() } else { this.setVisible() } @@ -1541,12 +1555,9 @@ class AutocompleteInstance extends React.PureComponent { } catch (e) { // do nothing } - clearTimeout(this._focusTimeout) - this._focusTimeout = setTimeout(() => { - this.setState({ - hasFocus: false, - }) - }, 1) // we have to wait in order to make sure the focus situation is cleared up + this.setState({ + hasFocus: false, + }) } ) } @@ -1895,6 +1906,7 @@ class AutocompleteInstance extends React.PureComponent { status: !opened && status ? status_state : null, onKeyDown: this.onTriggerKeyDownHandler, onSubmit: this.toggleVisible, + onMouseDown: this.reserveActivityHandler, 'aria-haspopup': 'listbox', 'aria-expanded': isExpanded, 'aria-label': !hidden ? submit_button_title : undefined, @@ -2057,8 +2069,8 @@ class AutocompleteInstance extends React.PureComponent { on_select={this.onSelectHandler} on_hide={this.onHideHandler} on_pre_change={this.onPreChangeHandler} - on_key_down={this.onReserveActivityHandler} - onMouseDown={this.onReserveActivityHandler} + on_key_down={this.reserveActivityHandler} + onMouseDown={this.reserveActivityHandler} independent_width={independent_width} /> diff --git a/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx b/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx index 08b43b27eda..4caaa05851f 100644 --- a/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx +++ b/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx @@ -238,7 +238,7 @@ describe('Autocomplete component', () => { assertInputValue() // open - keydown(40) // down + keyDownOnInput(40) // down expect( document @@ -494,32 +494,28 @@ describe('Autocomplete component', () => { ).toBe('') // simulate changes - keydown(40) // down + keyDownOnInput(40) // down expect( document.querySelector('.dnb-sr-only:not([hidden])').textContent ).toBe('AA c') // simulate changes - keydown(40) // down + keyDownOnInput(40) // down expect( document.querySelector('.dnb-sr-only:not([hidden])').textContent ).toBe('BB cc zethx') // simulate changes - keydown(40) // down + keyDownOnInput(40) // down expect( document.querySelector('.dnb-sr-only:not([hidden])').textContent ).toBe('CCcc') act(() => { - document.dispatchEvent( - new KeyboardEvent('keydown', { - keyCode: 13, // enter - }) - ) + dispatchKeyDown(13) // enter }) expect( @@ -528,7 +524,7 @@ describe('Autocomplete component', () => { // simulate changes toggle() - keydown(38) // up + keyDownOnInput(38) // up expect( document.querySelector('.dnb-sr-only:not([hidden])').textContent @@ -540,7 +536,7 @@ describe('Autocomplete component', () => { }) // simulate changes - keydown(38) // up + keyDownOnInput(38) // up expect( document.querySelector('.dnb-sr-only:not([hidden])').textContent @@ -769,14 +765,14 @@ describe('Autocomplete component', () => { ).not.toBeInTheDocument() // then simulate changes - keydown(40) // down + keyDownOnInput(40) // down expect(optionElements()[0].classList).toContain( 'dnb-drawer-list__option--focus' ) // then simulate changes - keydown(40) // down + keyDownOnInput(40) // down expect(optionElements()[1].classList).toContain( 'dnb-drawer-list__option--focus' @@ -816,7 +812,7 @@ describe('Autocomplete component', () => { // remove selection and reset the order and open again // aria-selected should now be on place 1 - keydown(27) // esc + keyDownOnInput(27) // esc toggle() elem = optionElements()[1] @@ -849,7 +845,7 @@ describe('Autocomplete component', () => { it('has correct "aria-expanded"', () => { render() - keydown(13) // enter + keyDownOnInput(13) // enter const elem = document.querySelector('.dnb-autocomplete') expect( @@ -957,7 +953,7 @@ describe('Autocomplete component', () => { ulElement: null, }) - keydown(27) // esc + keyDownOnInput(27) // esc expect(on_hide).toHaveBeenCalledTimes(1) expect(on_hide.mock.calls[0][0].attributes).toMatchObject(params) expect(on_hide.mock.calls[0][0].event).toEqual( @@ -980,7 +976,7 @@ describe('Autocomplete component', () => { document.querySelector('.dnb-autocomplete').classList ).toContain('dnb-autocomplete--opened') - keydown(27) // esc + keyDownOnInput(27) // esc expect( document.querySelector('.dnb-autocomplete').classList @@ -1090,67 +1086,6 @@ describe('Autocomplete component', () => { ).toBe('') }) - it('returns correct value in on_blur event', () => { - const on_focus = jest.fn() - const on_blur = jest.fn() - const onBlur = jest.fn() - const on_change = jest.fn() - - render( - - ) - - fireEvent.focus(document.querySelector('input')) - expect(on_focus).toHaveBeenCalledTimes(1) - - fireEvent.change(document.querySelector('input'), { - target: { value: 'cc' }, - }) - - // Try to call on_blur by mousedown - fireEvent.mouseDown(document.querySelector('.dnb-drawer-list')) - fireEvent.blur(document.querySelector('input')) - expect(on_blur).toHaveBeenCalledTimes(0) - expect(onBlur).toHaveBeenCalledTimes(0) - - // Try to call on_blur by keystroke - document.dispatchEvent( - new KeyboardEvent('keydown', { - keyCode: 13, // enter - }) - ) - fireEvent.blur(document.querySelector('input')) - expect(on_blur).toHaveBeenCalledTimes(0) - expect(onBlur).toHaveBeenCalledTimes(0) - - // Make a selection - fireEvent.click( - document.querySelectorAll('li.dnb-drawer-list__option')[1] - ) - - expect(on_change).toHaveBeenCalledTimes(1) - expect(on_change.mock.calls[0][0].data).toBe('BB cc zethx') - - // All the clicks should not have invoked the on_blur event - expect(on_blur).toHaveBeenCalledTimes(0) - expect(onBlur).toHaveBeenCalledTimes(0) - - // But a second one will - fireEvent.blur(document.querySelector('input')) - - expect(on_blur).toHaveBeenCalledTimes(1) - expect(onBlur).toHaveBeenCalledTimes(1) - expect(on_blur.mock.calls[0][0].value).toBe('BB cc zethx') - expect(onBlur.mock.calls[0][0].value).toBe('BB cc zethx') - }) - it('will invalidate selected_item when selected_key changes', () => { const mockData = [ { selected_key: 'a', content: 'AA c' }, @@ -1432,13 +1367,9 @@ describe('Autocomplete component', () => { const openAndSelectNext = () => { // then simulate changes - keydown(40) // down + keyDownOnInput(40) // down act(() => { - document.dispatchEvent( - new KeyboardEvent('keydown', { - keyCode: 13, // enter - }) - ) + dispatchKeyDown(13) // enter }) } @@ -1557,7 +1488,7 @@ describe('Autocomplete component', () => { }) // Make first item active - keydown(40) // down + keyDownOnInput(40) // down expect(focusElement()).toBeInTheDocument() @@ -1571,7 +1502,7 @@ describe('Autocomplete component', () => { expect(focusElement()).not.toBeInTheDocument() - keydown(40) // down + keyDownOnInput(40) // down expect(focusElement()).toBeInTheDocument() @@ -1585,12 +1516,8 @@ describe('Autocomplete component', () => { target: { value: 'cc' }, }) - keydown(40) // activate - document.dispatchEvent( - new KeyboardEvent('keydown', { - keyCode: 13, // enter - }) - ) + keyDownOnInput(40) // activate + dispatchKeyDown(13) // enter closeAndReopen() @@ -1658,7 +1585,7 @@ describe('Autocomplete component', () => { }) // Make first item active - keydown(40) // down + keyDownOnInput(40) // down expect(focusElement()).toBeInTheDocument() @@ -1672,7 +1599,7 @@ describe('Autocomplete component', () => { expect(focusElement()).not.toBeInTheDocument() - keydown(40) // down + keyDownOnInput(40) // down expect(focusElement()).toBeInTheDocument() @@ -1686,12 +1613,8 @@ describe('Autocomplete component', () => { target: { value: 'cc' }, }) - keydown(40) // activate - document.dispatchEvent( - new KeyboardEvent('keydown', { - keyCode: 13, // enter - }) - ) + keyDownOnInput(40) // activate + dispatchKeyDown(13) // enter closeAndReopen() @@ -1740,7 +1663,9 @@ describe('Autocomplete component', () => { /> ) - const inputElement = document.querySelector('.dnb-input__input') + const inputElement = document.querySelector( + '.dnb-input__input' + ) as HTMLInputElement const optionElements = () => document.querySelectorAll('li.dnb-drawer-list__option') const focusElement = () => @@ -1759,13 +1684,15 @@ describe('Autocomplete component', () => { }) // Make first item active - keydown(40) // down + keyDownOnInput(40) // down expect(focusElement()).toBeInTheDocument() + expect(inputElement.value).toBe('cc') closeAndReopen() expect(focusElement()).not.toBeInTheDocument() + expect(inputElement.value).toBe('cc') fireEvent.change(inputElement, { target: { value: '' }, @@ -1773,7 +1700,7 @@ describe('Autocomplete component', () => { expect(focusElement()).not.toBeInTheDocument() - keydown(40) // down + keyDownOnInput(40) // down expect(focusElement()).toBeInTheDocument() @@ -1781,25 +1708,22 @@ describe('Autocomplete component', () => { // This here is what we expect expect(focusElement()).not.toBeInTheDocument() + expect(inputElement.value).toBe('') // This also opens the drawer-list fireEvent.change(inputElement, { target: { value: 'cc' }, }) - keydown(40) // activate - document.dispatchEvent( - new KeyboardEvent('keydown', { - keyCode: 13, // enter - }) - ) + keyDownOnInput(40) // activate + dispatchKeyDown(13) // enter closeAndReopen() // Now we have a selected item expect(selectedElement()).toBeInTheDocument() expect(focusElement()).toBeInTheDocument() - expect((inputElement as HTMLInputElement).value).toBe('CC cc') + expect(inputElement.value).toBe('CC cc') fireEvent.change(inputElement, { target: { value: '' }, @@ -1810,6 +1734,7 @@ describe('Autocomplete component', () => { // This here is what we expect expect(focusElement()).toBeInTheDocument() expect(selectedElement()).toBeInTheDocument() + expect(inputElement.value).toBe('') expect(on_show).toBeCalledTimes(2) expect(on_hide).toBeCalledTimes(2) @@ -1912,7 +1837,7 @@ describe('Autocomplete component', () => { ).toContain('dnb-autocomplete--opened') // close - keydown(27) // esc + keyDownOnInput(27) // esc expect( document.querySelector('.dnb-autocomplete').classList @@ -1976,7 +1901,7 @@ describe('Autocomplete component', () => { act(() => { // close - document.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 27 })) + dispatchKeyDown(27) }) expect(on_hide).toHaveBeenCalledTimes(1) @@ -1995,7 +1920,7 @@ describe('Autocomplete component', () => { // close again, but with false returned act(() => { - document.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 27 })) + dispatchKeyDown(27) }) expect(on_hide).toHaveBeenCalledTimes(2) @@ -2040,7 +1965,7 @@ describe('Autocomplete component', () => { ) - keydown(40) // down + keyDownOnInput(40) // down fireEvent.change(document.querySelector('.dnb-input__input'), { target: { value: 'aa' }, @@ -2080,7 +2005,7 @@ describe('Autocomplete component', () => { /> ) - keydown(40) // down + keyDownOnInput(40) // down fireEvent.change(document.querySelector('.dnb-input__input'), { target: { value: 'aa' }, @@ -2206,7 +2131,7 @@ describe('Autocomplete component', () => { toggle() // then simulate changes - keydown(40) // down + keyDownOnInput(40) // down expect( (document.querySelector('.dnb-input__input') as HTMLInputElement) @@ -2350,10 +2275,10 @@ describe('Autocomplete component', () => { fireEvent.focus(document.querySelector('input')) // focus the first item - keydown(40) // down + keyDownOnInput(40) // down // focus the second item - keydown(40) // down + keyDownOnInput(40) // down await userEvent.tab() @@ -2376,7 +2301,7 @@ describe('Autocomplete component', () => { document.querySelector('input').focus() // open - keydown(40) // down + keyDownOnInput(40) // down expect(document.activeElement.tagName).toBe('INPUT') }) @@ -2616,6 +2541,350 @@ describe('Autocomplete component', () => { expect(inputElement.value).toEqual('CH (+41)') }) + + describe('input blur', () => { + const mainElement = () => document.querySelector('.dnb-autocomplete') + const inputElement = () => document.querySelector('.dnb-input__input') + const inputComponent = () => document.querySelector('.dnb-input') + const listElement = () => + document.querySelector('.dnb-autocomplete__list') + const optionElement = () => + document.querySelector('li.dnb-drawer-list__option') + const focusElement = () => + document.querySelector('li.dnb-drawer-list__option--focus') + const selectedElement = () => + document.querySelector('li.dnb-drawer-list__option--selected') + + it('shold emit with empty value', async () => { + const on_blur = jest.fn() + const onBlur = jest.fn() + + render( + + ) + + await userEvent.type(inputElement(), '{Enter}') + + expect(mainElement().classList).toContain('dnb-autocomplete--opened') + expect(optionElement()).toBeInTheDocument() + expect(focusElement()).not.toBeInTheDocument() + expect(inputComponent()).toHaveAttribute('data-input-state', 'focus') + expect(onBlur).toHaveBeenCalledTimes(0) + expect(on_blur).toHaveBeenCalledTimes(0) + + fireEvent.blur(inputElement()) + keyDownOnInput(13) // enter + + expect(mainElement().classList).not.toContain( + 'dnb-autocomplete--opened' + ) + expect(inputElement()).toHaveValue('') + expect(inputComponent()).toHaveAttribute( + 'data-input-state', + 'initial' + ) + expect(onBlur).toHaveBeenCalledTimes(1) + expect(on_blur).toHaveBeenCalledTimes(1) + expect(on_blur).toHaveBeenLastCalledWith( + expect.objectContaining({ value: '' }) + ) + }) + + it('shold not emit on submit button press', () => { + const on_blur = jest.fn() + const onBlur = jest.fn() + + render( + + ) + + const submitElement = () => + document.querySelector( + 'button.dnb-input__submit-button__button:not(.dnb-input__clear-button)' + ) + + fireEvent.focus(inputElement()) + keyDownOnInput(13) // enter + + expect(mainElement().classList).toContain('dnb-autocomplete--opened') + + fireEvent.click(submitElement()) + + expect(mainElement().classList).not.toContain( + 'dnb-autocomplete--opened' + ) + expect(inputComponent()).toHaveAttribute('data-input-state', 'focus') + + fireEvent.click(submitElement()) + + expect(mainElement().classList).toContain('dnb-autocomplete--opened') + expect(inputComponent()).toHaveAttribute('data-input-state', 'focus') + expect(onBlur).toHaveBeenCalledTimes(0) + expect(on_blur).toHaveBeenCalledTimes(0) + + fireEvent.blur(inputElement()) + + expect(inputComponent()).toHaveAttribute( + 'data-input-state', + 'initial' + ) + expect(onBlur).toHaveBeenCalledTimes(1) + expect(on_blur).toHaveBeenCalledTimes(1) + expect(on_blur).toHaveBeenLastCalledWith( + expect.objectContaining({ value: '' }) + ) + }) + + it('should include custom input value and not emit on input enter key', () => { + const on_blur = jest.fn() + const onBlur = jest.fn() + + render( + + ) + + fireEvent.focus(inputElement()) + keyDownOnInput(13) // enter + + expect(mainElement().classList).toContain('dnb-autocomplete--opened') + + fireEvent.change(inputElement(), { + target: { value: 'invalid' }, + }) + + expect(optionElement()).toBeInTheDocument() + expect(focusElement()).not.toBeInTheDocument() + expect(selectedElement()).not.toBeInTheDocument() + + keyDownOnInput(13) // enter + + expect(mainElement().classList).not.toContain( + 'dnb-autocomplete--opened' + ) + expect(optionElement()).not.toBeInTheDocument() + expect(focusElement()).not.toBeInTheDocument() + expect(selectedElement()).not.toBeInTheDocument() + expect(inputElement()).toHaveValue('invalid') + expect(inputComponent()).toHaveAttribute('data-input-state', 'focus') + + expect(onBlur).toHaveBeenCalledTimes(0) + expect(on_blur).toHaveBeenCalledTimes(0) + + fireEvent.blur(inputElement()) + + expect(onBlur).toHaveBeenCalledTimes(1) + expect(on_blur).toHaveBeenCalledTimes(1) + expect(on_blur).toHaveBeenLastCalledWith( + expect.objectContaining({ + value: 'invalid', + dataList: [ + expect.objectContaining({ content: 'Ingen alternativer' }), + ], + }) + ) + expect(inputComponent()).toHaveAttribute( + 'data-input-state', + 'initial' + ) + }) + + it('should not emit on item selection with enter key', async () => { + const on_blur = jest.fn() + const onBlur = jest.fn() + + render( + + ) + + fireEvent.focus(inputElement()) + keyDownOnInput(13) // enter + keyDownOnInput(40) // down + + expect(mainElement().classList).toContain('dnb-autocomplete--opened') + expect(optionElement()).toBeInTheDocument() + expect(focusElement()).toBeInTheDocument() + expect(selectedElement()).not.toBeInTheDocument() + + fireEvent.keyDown(listElement(), { + keyCode: 13, // enter + }) + + expect(mainElement().classList).not.toContain( + 'dnb-autocomplete--opened' + ) + expect(inputElement()).toHaveValue('AA c') + + keyDownOnInput(13) // enter + + expect(mainElement().classList).toContain('dnb-autocomplete--opened') + expect(optionElement()).toBeInTheDocument() + expect(focusElement()).toBeInTheDocument() + expect(selectedElement()).toBeInTheDocument() + + keyDownOnInput(13) // enter + + expect(mainElement().classList).not.toContain( + 'dnb-autocomplete--opened' + ) + expect(inputComponent()).toHaveAttribute('data-input-state', 'focus') + + await wait(1) + + expect(onBlur).toHaveBeenCalledTimes(0) + expect(on_blur).toHaveBeenCalledTimes(0) + + fireEvent.blur(inputElement()) + + expect(onBlur).toHaveBeenCalledTimes(1) + expect(on_blur).toHaveBeenCalledTimes(1) + expect(on_blur).toHaveBeenLastCalledWith( + expect.objectContaining({ + value: 'AA c', + dataList: [ + expect.objectContaining({ content: 'AA c' }), + expect.anything(), + expect.anything(), + ], + }) + ) + expect(inputComponent()).toHaveAttribute( + 'data-input-state', + 'initial' + ) + }) + + it('should not emit on item selection with mouse click', async () => { + const on_blur = jest.fn() + const onBlur = jest.fn() + + render( + + ) + + fireEvent.focus(inputElement()) + keyDownOnInput(13) // enter + keyDownOnInput(40) // down + + expect(mainElement().classList).toContain('dnb-autocomplete--opened') + expect(optionElement()).toBeInTheDocument() + expect(focusElement()).toBeInTheDocument() + expect(selectedElement()).not.toBeInTheDocument() + + fireEvent.click(focusElement()) + + expect(onBlur).toHaveBeenCalledTimes(0) + expect(on_blur).toHaveBeenCalledTimes(0) + + expect(mainElement().classList).not.toContain( + 'dnb-autocomplete--opened' + ) + expect(inputComponent()).toHaveAttribute('data-input-state', 'focus') + expect(inputElement()).toHaveValue('AA c') + + keyDownOnInput(13) // enter + + expect(mainElement().classList).toContain('dnb-autocomplete--opened') + expect(optionElement()).toBeInTheDocument() + expect(focusElement()).toBeInTheDocument() + expect(selectedElement()).toBeInTheDocument() + + keyDownOnInput(13) // enter + + await wait(1) + + expect(mainElement().classList).not.toContain( + 'dnb-autocomplete--opened' + ) + expect(inputComponent()).toHaveAttribute('data-input-state', 'focus') + expect(onBlur).toHaveBeenCalledTimes(0) + expect(on_blur).toHaveBeenCalledTimes(0) + + fireEvent.blur(inputElement()) + + expect(onBlur).toHaveBeenCalledTimes(1) + expect(on_blur).toHaveBeenCalledTimes(1) + expect(on_blur).toHaveBeenLastCalledWith( + expect.objectContaining({ + value: 'AA c', + dataList: [ + expect.objectContaining({ content: 'AA c' }), + expect.anything(), + expect.anything(), + ], + }) + ) + expect(inputComponent()).toHaveAttribute( + 'data-input-state', + 'initial' + ) + }) + + it('should dismiss focus only on blur', () => { + const on_focus = jest.fn() + const on_blur = jest.fn() + const onBlur = jest.fn() + const on_change = jest.fn() + + render( + + ) + + expect(document.querySelector('.dnb-input')).toHaveAttribute( + 'data-input-state', + 'virgin' + ) + + fireEvent.focus(document.querySelector('input')) + + fireEvent.keyDown(document.querySelector('input'), { + key: 'Enter', + keyCode: 13, + }) + + expect(document.querySelector('.dnb-input')).toHaveAttribute( + 'data-input-state', + 'focus' + ) + + fireEvent.keyDown(document.querySelector('input'), { + key: 'Enter', + keyCode: 13, + }) + }) + }) }) describe('Autocomplete markup', () => { @@ -2660,12 +2929,20 @@ describe('Autocomplete scss', () => { }) }) -const keydown = (keyCode) => { +const keyDownOnInput = (keyCode) => { fireEvent.keyDown(document.querySelector('.dnb-input__input'), { keyCode, }) } +const dispatchKeyDown = (keyCode) => { + document.dispatchEvent( + new KeyboardEvent('keydown', { + keyCode, + }) + ) +} + const toggle = () => { fireEvent.click( document.querySelector(