Skip to content

Commit

Permalink
feat(hooks): dispatch changes only when needed
Browse files Browse the repository at this point in the history
  • Loading branch information
silviuaavram committed Jan 20, 2021
1 parent 4b6860e commit fcdee47
Show file tree
Hide file tree
Showing 11 changed files with 665 additions and 577 deletions.
30 changes: 15 additions & 15 deletions .size-snapshot.json
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
{
"downshift.cjs.js": {
"bundled": 148289,
"minified": 68373,
"gzipped": 14410
"bundled": 148721,
"minified": 68507,
"gzipped": 14453
},
"downshift.umd.min.js": {
"bundled": 156069,
"minified": 51613,
"gzipped": 14058
"bundled": 156563,
"minified": 51795,
"gzipped": 14102
},
"downshift.umd.js": {
"bundled": 200681,
"minified": 69912,
"gzipped": 18283
"bundled": 201175,
"minified": 70094,
"gzipped": 18317
},
"downshift.esm.js": {
"bundled": 143545,
"minified": 64317,
"gzipped": 14173,
"bundled": 145342,
"minified": 65553,
"gzipped": 14304,
"treeshaked": {
"rollup": {
"code": 2275,
"import_statements": 318
"code": 2274,
"import_statements": 317
},
"webpack": {
"code": 4626
"code": 4625
}
}
}
Expand Down
28 changes: 27 additions & 1 deletion src/__tests__/downshift.get-input-props.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react'
import {render, fireEvent, screen} from '@testing-library/react'
import {render, fireEvent, screen, createEvent} from '@testing-library/react'
import Downshift from '../'

jest.useFakeTimers()
Expand Down Expand Up @@ -367,6 +367,32 @@ test('home and end keys should not call highlighting method when menu is closed'
)
})

test('home and end keys should not prevent event default when menu is closed', () => {
const {input} = renderDownshift()
const homeKeyDownEvent = createEvent.keyDown(input, {key: 'Home'})
const endKeyDownEvent = createEvent.keyDown(input, {key: 'End'})
// home
fireEvent(input, homeKeyDownEvent)
expect(homeKeyDownEvent.defaultPrevented).toBe(false)

// end
fireEvent(input, endKeyDownEvent)
expect(endKeyDownEvent.defaultPrevented).toBe(false)
})

test('home and end keys should prevent event default when menu is open', () => {
const {input} = renderDownshift({props: {defaultIsOpen: true}})
const homeKeyDownEvent = createEvent.keyDown(input, {key: 'Home'})
const endKeyDownEvent = createEvent.keyDown(input, {key: 'End'})
// home
fireEvent(input, homeKeyDownEvent)
expect(homeKeyDownEvent.defaultPrevented).toBe(true)

// end
fireEvent(input, endKeyDownEvent)
expect(endKeyDownEvent.defaultPrevented).toBe(true)
})

test('enter on an input with a closed menu does nothing', () => {
const {enterOnInput, childrenSpy} = renderDownshift()
childrenSpy.mockClear()
Expand Down
14 changes: 12 additions & 2 deletions src/downshift.js
Original file line number Diff line number Diff line change
Expand Up @@ -616,10 +616,15 @@ class Downshift extends Component {
inputKeyDownHandlers = {
...this.keyDownHandlers,
Home(event) {
const {isOpen} = this.getState()

if (!isOpen) {
return
}

event.preventDefault()

const itemCount = this.getItemCount()
const {isOpen} = this.getState()

if (itemCount <= 0 || !isOpen) {
return
Expand All @@ -640,10 +645,15 @@ class Downshift extends Component {
},

End(event) {
const {isOpen} = this.getState()

if (!isOpen) {
return
}

event.preventDefault()

const itemCount = this.getItemCount()
const {isOpen} = this.getState()

if (itemCount <= 0 || !isOpen) {
return
Expand Down
146 changes: 137 additions & 9 deletions src/hooks/useCombobox/__tests__/getInputProps.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable jest/no-disabled-tests */
import * as React from 'react'
import {act, renderHook} from '@testing-library/react-hooks'
import {fireEvent} from '@testing-library/react'
import {fireEvent, createEvent} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import * as stateChangeTypes from '../stateChangeTypes'
import {noop} from '../../../utils'
Expand Down Expand Up @@ -160,13 +160,14 @@ describe('getInputProps', () => {
const userOnKeyDown = jest.fn()
const {result} = renderUseCombobox()

const {ref: inputRef, onKeyDown} = result.current.getInputProps({
onKeyDown: userOnKeyDown,
})
inputRef({focus: noop})
act(() => {
const {ref: inputRef, onKeyDown} = result.current.getInputProps({
onKeyDown: userOnKeyDown,
})

inputRef({focus: noop})
result.current.toggleMenu()
})
act(() => {
onKeyDown({key: 'Escape', preventDefault: noop})
})

Expand Down Expand Up @@ -196,7 +197,7 @@ describe('getInputProps', () => {

test('event handler onBlur is called along with downshift handler', () => {
const userOnBlur = jest.fn()
const {result} = renderUseCombobox()
const {result} = renderUseCombobox({initialIsOpen: true})

act(() => {
const {ref: inputRef, onBlur} = result.current.getInputProps({
Expand Down Expand Up @@ -347,6 +348,15 @@ describe('getInputProps', () => {

describe('on key down', () => {
describe('arrow up', () => {
test('it prevents the default event behavior', () => {
const {input} = renderCombobox()
const keyDownEvent = createEvent.keyDown(input, {key: 'ArrowUp'})

fireEvent(input, keyDownEvent)

expect(keyDownEvent.defaultPrevented).toBe(true)
})

test('it does not open or highlight anything if there are no options', () => {
const {keyDownOnInput, getItems, input} = renderCombobox({items: []})

Expand Down Expand Up @@ -467,6 +477,15 @@ describe('getInputProps', () => {
})

describe('arrow down', () => {
test('it prevents the default event behavior', () => {
const {input} = renderCombobox()
const keyDownEvent = createEvent.keyDown(input, {key: 'ArrowDown'})

fireEvent(input, keyDownEvent)

expect(keyDownEvent.defaultPrevented).toBe(true)
})

test('it does not opne on highlight anything if there are no options', () => {
const {keyDownOnInput, getItems, input} = renderCombobox({items: []})

Expand Down Expand Up @@ -648,6 +667,24 @@ describe('getInputProps', () => {
)
})

test('end it prevents the default event and calls dispatch only when menu is open', () => {
const {input, rerender, renderSpy} = renderCombobox()
const keyDownEvent = createEvent.keyDown(input, {key: 'End'})

renderSpy.mockClear()
fireEvent(input, keyDownEvent)

expect(keyDownEvent.defaultPrevented).toBe(false)
expect(renderSpy).not.toHaveBeenCalled()

rerender({isOpen: true})
renderSpy.mockClear()
fireEvent(input, keyDownEvent)

expect(keyDownEvent.defaultPrevented).toBe(true)
expect(renderSpy).toHaveBeenCalledTimes(1)
})

test('home it highlights the first option number', () => {
const {keyDownOnInput, input} = renderCombobox({
isOpen: true,
Expand All @@ -662,6 +699,24 @@ describe('getInputProps', () => {
)
})

test('home it prevents the default event calls dispatch only when menu is open', () => {
const {input, rerender, renderSpy} = renderCombobox()
const keyDownEvent = createEvent.keyDown(input, {key: 'Home'})

renderSpy.mockClear()
fireEvent(input, keyDownEvent)

expect(keyDownEvent.defaultPrevented).toBe(false)
expect(renderSpy).not.toHaveBeenCalled()

rerender({isOpen: true})
renderSpy.mockClear()
fireEvent(input, keyDownEvent)

expect(keyDownEvent.defaultPrevented).toBe(true)
expect(renderSpy).toHaveBeenCalledTimes(1) // re-render on key
})

test('escape with menu open has the menu closed and focused kept on input', () => {
const {keyDownOnInput, input, getItems} = renderCombobox({
initialIsOpen: true,
Expand Down Expand Up @@ -690,6 +745,27 @@ describe('getInputProps', () => {
expect(input).toHaveFocus()
})

test('escape it prevents the rerender when menu closed, no selectedItem and no inputValue', () => {
const {keyDownOnInput, rerender, renderSpy} = renderCombobox()

renderSpy.mockClear()
keyDownOnInput('Escape')

expect(renderSpy).toHaveBeenCalledTimes(0) // no re-render

rerender({isOpen: true})
renderSpy.mockClear() // reset rerender and initial render
keyDownOnInput('Escape')

expect(renderSpy).toHaveBeenCalledTimes(1) // re-render on key

rerender({isOpen: false, inputValue: 'still'})
renderSpy.mockClear() // reset rerender and initial render
keyDownOnInput('Escape')

expect(renderSpy).toHaveBeenCalledTimes(1) // re-render on key
})

test('enter it closes the menu and selects highlighted item', () => {
const initialHighlightedIndex = 2
const {keyDownOnInput, input, getItems} = renderCombobox({
Expand Down Expand Up @@ -743,7 +819,7 @@ describe('getInputProps', () => {
expect(input).not.toHaveAttribute('aria-activedescendant')
})

test('enter on an input with a closed menu does nothing', () => {
test('enter with a closed menu does nothing', () => {
const {keyDownOnInput, getItems, input} = renderCombobox({
initialHighlightedIndex: 2,
initialIsOpen: false,
Expand All @@ -755,7 +831,7 @@ describe('getInputProps', () => {
expect(input).not.toHaveValue()
})

test('enter on an input with an open menu does nothing without a highlightedIndex', () => {
test('enter with an open menu does nothing without a highlightedIndex', () => {
const {keyDownOnInput, getItems, input} = renderCombobox({
initialIsOpen: true,
})
Expand All @@ -766,6 +842,43 @@ describe('getInputProps', () => {
expect(input).not.toHaveValue()
})

test('enter with closed menu, no item highlighted or composing event, it will not rerender or prevent event default', () => {
const {input, renderSpy, rerender} = renderCombobox()
let keyDownEvent = createEvent.keyDown(input, {key: 'Enter'})

renderSpy.mockClear()
fireEvent(input, keyDownEvent)

expect(renderSpy).not.toHaveBeenCalled()
expect(keyDownEvent.defaultPrevented).toBe(false)

rerender({
isOpen: true,
})
renderSpy.mockClear()
fireEvent(input, keyDownEvent)

expect(renderSpy).not.toHaveBeenCalled()
expect(keyDownEvent.defaultPrevented).toBe(false)

keyDownEvent = createEvent.keyDown(input, {key: 'Enter', keyCode: 229})
rerender({
isOpen: true,
highlightedIndex: 2,
})
renderSpy.mockClear()
fireEvent(input, keyDownEvent)

expect(renderSpy).not.toHaveBeenCalled()
expect(keyDownEvent.defaultPrevented).toBe(false)

keyDownEvent = createEvent.keyDown(input, {key: 'Enter'})
fireEvent(input, keyDownEvent)

expect(renderSpy).toHaveBeenCalledTimes(1)
expect(keyDownEvent.defaultPrevented).toBe(true)
})

test('tab it closes the menu and selects highlighted item', () => {
const initialHighlightedIndex = 2
const {input, getItems} = renderCombobox(
Expand All @@ -786,6 +899,21 @@ describe('getInputProps', () => {
expect(input).toHaveValue(items[initialHighlightedIndex])
})

test('tab it prevents the rerender and does not call dispatch when menu is closed', () => {
const {rerender, renderSpy} = renderCombobox()

renderSpy.mockClear()
userEvent.tab()

expect(renderSpy).toHaveBeenCalledTimes(0)

rerender({isOpen: true})
renderSpy.mockClear()
userEvent.tab()

expect(renderSpy).toHaveBeenCalledTimes(1)
})

test('shift+tab it closes the menu', () => {
const initialHighlightedIndex = 2
const {input, getItems} = renderCombobox(
Expand Down
Loading

0 comments on commit fcdee47

Please sign in to comment.