Skip to content

Commit

Permalink
fix: onchange event with preact/compat enabled (#72)
Browse files Browse the repository at this point in the history
* fix: fireEvent doesnt mirror onChange behavior when using preact/compat

this fix aligns this library with testing-library for React where fireEvent.change() works as expected

* refactor: move onChange test to separate file

onChange test requires import from preact/compat which affects other tests in the same test file so I moved it to separate file

* refactor: simplify imports

replace unnecessary forwardRef import with general preact/compat; improve commentary

Co-authored-by: Ryan Christian <33403762+rschristian@users.noreply.github.com>

* fix: apply aliasing only when preact/compat is used

prevent applying aliasing when compat library is not used; add change event to basic event tests to ensure both cases work

---------

Co-authored-by: Ryan Christian <33403762+rschristian@users.noreply.github.com>
  • Loading branch information
DanielMaczak and rschristian authored May 27, 2024
1 parent 9fdc19b commit 3e5394e
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 5 deletions.
25 changes: 25 additions & 0 deletions src/__tests__/events-compat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { h } from 'preact' // required by render
import { fireEvent, render } from '..'
import 'preact/compat'

test('calling `fireEvent` with `preact/compat` and onChange works too', () => {
const handler = jest.fn()

// Preact only matches React's aliasing of `onChange` when `preact/compat` is used
// This test ensures this is supported properly with `fireEvent.change()`
const {
container: { firstChild: input }
} = render(<input type="text" onChange={handler} />)

const targetProperties = { value: 'a' }
const otherProperties = { isComposing: true }
const init = {
target: targetProperties,
...otherProperties
}

expect(fireEvent.change(input, init)).toBe(true)

expect(handler).toHaveBeenCalledTimes(1)
expect(handler).toHaveBeenCalledWith(expect.objectContaining(otherProperties))
})
12 changes: 9 additions & 3 deletions src/__tests__/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const eventTypes = [
},
{
type: 'Focus',
events: ['input', 'invalid'],
events: ['input', 'invalid', 'change'],
elementType: 'input'
},
{
Expand Down Expand Up @@ -200,8 +200,14 @@ test('calling `fireEvent` directly works too', () => {
})

test('`fireEvent` returns false when prevented', () => {
const { container: { firstChild: button } } = render(
(<button onClick={(e) => { e.preventDefault() }} />)
const {
container: { firstChild: button }
} = render(
<button
onClick={(e) => {
e.preventDefault()
}}
/>
)

expect(fireEvent.click(button)).toBe(false)
Expand Down
27 changes: 25 additions & 2 deletions src/fire-event.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
import { fireEvent as domFireEvent, createEvent } from '@testing-library/dom'
import { options } from 'preact'

let isCompat = false

// Detects if preact/compat is used
const oldHook = options.vnode
options.vnode = (vnode) => {
if (vnode.$$typeof) isCompat = true
if (oldHook) oldHook(vnode)
}

// Renames event to match React (preact/compat) version
const renameEventCompat = (key) => {
return key === 'change' ? 'input' : key
}

// Similar to RTL we make are own fireEvent helper that just calls DTL's fireEvent with that
// we can that any specific behaviors to the helpers we need
Expand All @@ -11,8 +26,16 @@ 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)

return isInElem
? domFireEvent[key](elem, init)
: domFireEvent(elem, createEvent(key[0].toUpperCase() + key.slice(1), elem, init))
? domFireEvent[keyFiltered](elem, init)
: domFireEvent(
elem,
createEvent(keyFiltered[0].toUpperCase() + keyFiltered.slice(1), elem, init)
)
}
})

0 comments on commit 3e5394e

Please sign in to comment.