diff --git a/src/__tests__/events-compat.js b/src/__tests__/events-compat.js index 9687a4e..2044c39 100644 --- a/src/__tests__/events-compat.js +++ b/src/__tests__/events-compat.js @@ -23,3 +23,23 @@ test('calling `fireEvent` with `preact/compat` and onChange works too', () => { expect(handler).toHaveBeenCalledTimes(1) expect(handler).toHaveBeenCalledWith(expect.objectContaining(otherProperties)) }) + +test('should not alias `change` event to `input` for file, checkbox, or radio inputs', () => { + for (const type of ['file', 'checkbox', 'radio']) { + const inputHandler = jest.fn() + const changeHandler = jest.fn() + + const { + container: { firstChild: input } + } = render() + + fireEvent.change(input, { + target: input.type === 'file' + ? { files: [new File(['Hello World!'], 'foo.txt')] } + : { checked: true } + }) + + expect(inputHandler).toHaveBeenCalledTimes(0) + expect(changeHandler).toHaveBeenCalledTimes(1) + } +}) diff --git a/src/fire-event.js b/src/fire-event.js index 217462e..1b7ff64 100644 --- a/src/fire-event.js +++ b/src/fire-event.js @@ -10,9 +10,19 @@ options.vnode = (vnode) => { if (oldHook) oldHook(vnode) } -// Renames event to match React (preact/compat) version -const renameEventCompat = (key) => { - return key === 'change' ? 'input' : key +// Matches the behavior of `preact/compat`: +// https://github.com/preactjs/preact/blob/2459326755dea9ad6184b42bda1128c5004b8544/compat/src/render.js#L173-L175 +// https://github.com/preactjs/preact/blob/2459326755dea9ad6184b42bda1128c5004b8544/compat/src/render.js#L36-L37 +const maybeAliasKey = (key, elem) => { + if ( + key === 'change' && + (elem.tagName === 'INPUT' || elem.tagName === 'TEXTAREA') && + !/fil|che|rad/.test(elem.type) + ) { + return 'input' + } + + return key; } // Similar to RTL we make are own fireEvent helper that just calls DTL's fireEvent with that @@ -26,16 +36,15 @@ Object.keys(domFireEvent).forEach((key) => { // we hit the Preact listeners. const eventName = `on${key.toLowerCase()}` const isInElem = eventName in elem - // Preact changes all change events to input events when running 'preact/compat', - // making the event name out of sync. - // The problematic code is in: preact/compat/src/render.js > handleDomVNode() - const keyFiltered = !isCompat ? key : renameEventCompat(key) + + // Preact aliases some change events when using `preact/compat` to mirror React's behavior + const maybeAliasedKey = !isCompat ? key : maybeAliasKey(key, elem) return isInElem - ? domFireEvent[keyFiltered](elem, init) + ? domFireEvent[maybeAliasedKey](elem, init) : domFireEvent( elem, - createEvent(keyFiltered[0].toUpperCase() + keyFiltered.slice(1), elem, init) + createEvent(maybeAliasedKey[0].toUpperCase() + maybeAliasedKey.slice(1), elem, init) ) } })