From 19c28682dd6beff303e80410a4c3593a808f57b1 Mon Sep 17 00:00:00 2001 From: "Kent C. Dodds" Date: Mon, 6 Aug 2018 15:37:43 -0600 Subject: [PATCH] feat(fireEvent): helper to assign target properties to node (#85) 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: https://github.com/kentcdodds/react-testing-library/issues/152 --- README.md | 11 +++++++++++ src/__tests__/events.js | 21 ++++++++++++++++++++- src/events.js | 24 ++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b8a08df..e62d73ac 100644 --- a/README.md +++ b/README.md @@ -509,6 +509,17 @@ fireEvent.click(getByText('Submit'), rightClick) // default `button` property for click events is set to `0` which is a left click. ``` +**target**: When an event is dispatched on an element, the event has the +subjected element on a property called `target`. As a convenience, if you +provide a `target` property in the `eventProperties` (second argument), then +those properties will be assigned to the node which is receiving the event. + +This is particularly useful for a change event: + +```javascript +fireEvent.change(getByLabelText(/username/i), {target: {value: 'a'}}) +``` + #### `getNodeText` ```typescript diff --git a/src/__tests__/events.js b/src/__tests__/events.js index b57e2f21..2d39770b 100644 --- a/src/__tests__/events.js +++ b/src/__tests__/events.js @@ -1,4 +1,4 @@ -import {fireEvent} from '../' +import {fireEvent} from '..' const eventTypes = [ { @@ -149,3 +149,22 @@ describe(`Aliased Events`, () => { expect(spy).toHaveBeenCalledTimes(1) }) }) + +test('assigns target properties', () => { + const node = document.createElement('input') + const spy = jest.fn() + const value = 'a' + node.addEventListener('change', spy) + fireEvent.change(node, {target: {value}}) + expect(spy).toHaveBeenCalledTimes(1) + expect(node.value).toBe(value) +}) + +test('assigning a value to a target that cannot have a value throws an error', () => { + const node = document.createElement('div') + expect(() => + fireEvent.change(node, {target: {value: 'a'}}), + ).toThrowErrorMatchingInlineSnapshot( + `"The given element does not have a value setter"`, + ) +}) diff --git a/src/events.js b/src/events.js index d1c2ef2d..04a82040 100644 --- a/src/events.js +++ b/src/events.js @@ -322,13 +322,37 @@ Object.entries(eventMap).forEach(([key, {EventType = Event, defaultInit}]) => { fireEvent[key] = (node, init) => { const eventInit = {...defaultInit, ...init} + const {target: {value, ...targetProperties} = {}} = eventInit + Object.assign(node, targetProperties) + if (value !== undefined) { + setNativeValue(node, value) + } const event = new EventType(eventName, eventInit) return fireEvent(node, event) } }) +// function written after some investigation here: +// https://github.com/facebook/react/issues/10135#issuecomment-401496776 +function setNativeValue(element, value) { + const {set: valueSetter} = + Object.getOwnPropertyDescriptor(element, 'value') || {} + const prototype = Object.getPrototypeOf(element) + const {set: prototypeValueSetter} = + Object.getOwnPropertyDescriptor(prototype, 'value') || {} + if (prototypeValueSetter && valueSetter !== prototypeValueSetter) { + prototypeValueSetter.call(element, value) + } /* istanbul ignore next (I don't want to bother) */ else if (valueSetter) { + valueSetter.call(element, value) + } else { + throw new Error('The given element does not have a value setter') + } +} + Object.entries(eventAliasMap).forEach(([aliasKey, key]) => { fireEvent[aliasKey] = (...args) => fireEvent[key](...args) }) export {fireEvent} + +/* eslint complexity:["error", 9] */