diff --git a/src/__tests__/clear.js b/src/__tests__/clear.js index 6fc7d076..255e70db 100644 --- a/src/__tests__/clear.js +++ b/src/__tests__/clear.js @@ -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', @@ -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', @@ -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) }, ) @@ -60,6 +70,7 @@ test.each(['input', 'textarea'])( userEvent.clear(input) expect(onKeyDown).toHaveBeenCalledTimes(1) expect(onKeyUp).toHaveBeenCalledTimes(1) + expect(onChange).toHaveBeenCalledTimes(0) expect(input).toHaveProperty('value', text) }, ) @@ -67,6 +78,8 @@ test.each(['input', 'textarea'])( test.each(['input', 'textarea'])( `should clear when <%s> is of type="${type}"`, inputType => { + const onChange = jest.fn() + const value = '12345' const placeholder = 'Enter password' @@ -74,7 +87,7 @@ test.each(['input', 'textarea'])( value, placeholder, type, - onChange: jest.fn(), + onChange, }) render(element) @@ -83,6 +96,7 @@ test.each(['input', 'textarea'])( expect(input.value).toBe(value) userEvent.clear(input) expect(input.value).toBe('') + expect(onChange).toHaveBeenCalledTimes(1) }, ) }) diff --git a/src/index.js b/src/index.js index da8c335c..ea4037eb 100644 --- a/src/index.js +++ b/src/index.js @@ -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 = '' } }