Skip to content

Commit 1b420b8

Browse files
author
Kent C. Dodds
committed
feat(fireEvent): helper to assign target properties to node
Specifically this is so the change event can set the value of the node in a way that works for React (and all other frameworks and non-frameworks) nicely. fixes: testing-library/react-testing-library#152
1 parent c07a1ab commit 1b420b8

File tree

3 files changed

+55
-1
lines changed

3 files changed

+55
-1
lines changed

Diff for: README.md

+11
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,17 @@ fireEvent.click(getByText('Submit'), rightClick)
509509
// default `button` property for click events is set to `0` which is a left click.
510510
```
511511

512+
**target**: When an event is dispatched on an element, the event has the
513+
subjected element on a property called `target`. As a convenience, if you
514+
provide a `target` property in the `eventProperties` (second argument), then
515+
those properties will be assigned to the node which is receiving the event.
516+
517+
This is particularly useful for a change event:
518+
519+
```javascript
520+
fireEvent.change(getByLabelText(/username/i), {target: {value: 'a'}})
521+
```
522+
512523
#### `getNodeText`
513524

514525
```typescript

Diff for: src/__tests__/events.js

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {fireEvent} from '../'
1+
import {fireEvent} from '..'
22

33
const eventTypes = [
44
{
@@ -149,3 +149,22 @@ describe(`Aliased Events`, () => {
149149
expect(spy).toHaveBeenCalledTimes(1)
150150
})
151151
})
152+
153+
test('assigns target properties', () => {
154+
const node = document.createElement('input')
155+
const spy = jest.fn()
156+
const value = 'a'
157+
node.addEventListener('change', spy)
158+
fireEvent.change(node, {target: {value}})
159+
expect(spy).toHaveBeenCalledTimes(1)
160+
expect(node.value).toBe(value)
161+
})
162+
163+
test('assigning a value to a target that cannot have a value throws an error', () => {
164+
const node = document.createElement('div')
165+
expect(() =>
166+
fireEvent.change(node, {target: {value: 'a'}}),
167+
).toThrowErrorMatchingInlineSnapshot(
168+
`"The given element does not have a value setter"`,
169+
)
170+
})

Diff for: src/events.js

+24
Original file line numberDiff line numberDiff line change
@@ -322,13 +322,37 @@ Object.entries(eventMap).forEach(([key, {EventType = Event, defaultInit}]) => {
322322

323323
fireEvent[key] = (node, init) => {
324324
const eventInit = Object.assign({}, defaultInit, init)
325+
const {target: {value, ...targetProperties} = {}} = eventInit
326+
Object.assign(node, targetProperties)
327+
if (value !== undefined) {
328+
setNativeValue(node, value)
329+
}
325330
const event = new EventType(eventName, eventInit)
326331
return fireEvent(node, event)
327332
}
328333
})
329334

335+
// function written after some investigation here:
336+
// https://github.com/facebook/react/issues/10135#issuecomment-401496776
337+
function setNativeValue(element, value) {
338+
const {set: valueSetter} =
339+
Object.getOwnPropertyDescriptor(element, 'value') || {}
340+
const prototype = Object.getPrototypeOf(element)
341+
const {set: prototypeValueSetter} =
342+
Object.getOwnPropertyDescriptor(prototype, 'value') || {}
343+
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
344+
prototypeValueSetter.call(element, value)
345+
} /* istanbul ignore next (I don't want to bother) */ else if (valueSetter) {
346+
valueSetter.call(element, value)
347+
} else {
348+
throw new Error('The given element does not have a value setter')
349+
}
350+
}
351+
330352
Object.entries(eventAliasMap).forEach(([aliasKey, key]) => {
331353
fireEvent[aliasKey] = (...args) => fireEvent[key](...args)
332354
})
333355

334356
export {fireEvent}
357+
358+
/* eslint complexity:["error", 9] */

0 commit comments

Comments
 (0)