Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make clear method fire onChange handlers #286

Merged
merged 3 commits into from May 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 17 additions & 3 deletions src/__tests__/clear.js
Expand Up @@ -3,7 +3,11 @@ import {render, screen} from '@testing-library/react'
import userEvent from '../../src'

test.each(['input', 'textarea'])('should clear text in <%s>', type => {
const onChange = jest.fn()
const onChange = jest.fn().mockImplementation(event => {
// Verify that `event.target`'s value is correct when the event handler is
// fired.
expect(event.target).toHaveProperty('value', '')
})
render(
React.createElement(type, {
'data-testid': 'input',
Expand All @@ -15,13 +19,18 @@ test.each(['input', 'textarea'])('should clear text in <%s>', type => {
const input = screen.getByTestId('input')
userEvent.clear(input)
expect(input.value).toBe('')
expect(onChange).toHaveBeenCalledTimes(1)
})

test.each(['input', 'textarea'])(
'should not clear when <%s> is disabled',
type => {
const text = 'Hello, world!'
const onChange = jest.fn()
const onChange = jest.fn().mockImplementation(event => {
// Verify that `event.target`'s value is correct when the event handler is
// fired.
expect(event.target).toHaveProperty('value', '')
})
render(
React.createElement(type, {
'data-testid': 'input',
Expand All @@ -34,6 +43,7 @@ test.each(['input', 'textarea'])(
const input = screen.getByTestId('input')
userEvent.clear(input)
expect(input).toHaveProperty('value', text)
expect(onChange).toHaveBeenCalledTimes(0)
},
)

Expand All @@ -60,21 +70,24 @@ test.each(['input', 'textarea'])(
userEvent.clear(input)
expect(onKeyDown).toHaveBeenCalledTimes(1)
expect(onKeyUp).toHaveBeenCalledTimes(1)
expect(onChange).toHaveBeenCalledTimes(0)
expect(input).toHaveProperty('value', text)
},
)
;['email', 'password', 'number', 'text'].forEach(type => {
test.each(['input', 'textarea'])(
`should clear when <%s> is of type="${type}"`,
inputType => {
const onChange = jest.fn()

const value = '12345'
const placeholder = 'Enter password'

const element = React.createElement(inputType, {
value,
placeholder,
type,
onChange: jest.fn(),
onChange,
})

render(element)
Expand All @@ -83,6 +96,7 @@ test.each(['input', 'textarea'])(
expect(input.value).toBe(value)
userEvent.clear(input)
expect(input.value).toBe('')
expect(onChange).toHaveBeenCalledTimes(1)
},
)
})
27 changes: 23 additions & 4 deletions src/index.js
Expand Up @@ -93,19 +93,38 @@ const Keys = {
}

function backspace(element) {
const eventOptions = {
const keyboardEventOptions = {
key: Keys.Backspace.key,
keyCode: Keys.Backspace.keyCode,
which: Keys.Backspace.keyCode,
}
fireEvent.keyDown(element, eventOptions)
fireEvent.keyUp(element, eventOptions)
fireEvent.keyDown(element, keyboardEventOptions)
fireEvent.keyUp(element, keyboardEventOptions)

if (!element.readOnly) {
fireEvent.input(element, {
inputType: 'deleteContentBackward',
})
element.value = '' // when we add special keys to API, will need to respect selected range

// We need to call `fireEvent.change` _before_ we change `element.value`
// because `fireEvent.change` will use the element's native value setter
// (meaning it will avoid prototype overrides implemented by React). If we
// call `input.value = ""` first, React will swallow the change event (this
// is checked in the tests). `fireEvent.change` will only call the native
// value setter method if the event options include `{ target: { value }}`
// (https://github.com/testing-library/dom-testing-library/blob/8846eaf20972f8e41ed11f278948ac38a692c3f1/src/events.js#L29-L32).
//
// Also, we still must call `element.value = ""` after calling
// `fireEvent.change` because `fireEvent.change` will _only_ call the native
// `value` setter and not the prototype override defined by React, causing
// React's internal represetation of this state to get out of sync with the
// value set on `input.value`; calling `element.value` after will also call
// React's setter, keeping everything in sync.
//
// Comment either of these out or re-order them and see what parts of the
// tests fail for more context.
fireEvent.change(element, {target: {value: ''}})
element.value = ''
}
}

Expand Down