From 8d9c2fa2903554d1f5120bfd9e4ae0979a2e233e Mon Sep 17 00:00:00 2001 From: "Kent C. Dodds" Date: Sat, 4 Aug 2018 07:45:42 -0600 Subject: [PATCH 1/6] WIP: add setNativeValue for change event --- src/__tests__/events.js | 14 ++++++++------ src/index.js | 30 +++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/__tests__/events.js b/src/__tests__/events.js index b8dd487b..985183c0 100644 --- a/src/__tests__/events.js +++ b/src/__tests__/events.js @@ -156,10 +156,12 @@ eventTypes.forEach(({type, events, elementType, init}) => { }) test('onChange works', () => { - const handleChange = jest.fn() - const { - container: {firstChild: input}, - } = render() - fireEvent.change(input, {target: {value: 'a'}}) - expect(handleChange).toHaveBeenCalledTimes(1) + const spy = jest.fn() + + const {container} = render() + const input = container.firstChild + fireEvent.change(input, {target: {value: 'c'}}) + expect(spy).toHaveBeenCalledTimes(1) }) + +/* eslint complexity:0 */ diff --git a/src/index.js b/src/index.js index 952d7d86..67d5dd1f 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ import ReactDOM from 'react-dom' import {Simulate} from 'react-dom/test-utils' -import {getQueriesForElement, prettyDOM} from 'dom-testing-library' +import {getQueriesForElement, prettyDOM, fireEvent} from 'dom-testing-library' const mountedContainers = new Set() @@ -59,6 +59,32 @@ function cleanupAtContainer(container) { mountedContainers.delete(container) } +const originalChange = fireEvent.change +fireEvent.change = function reactChange(node, init) { + if (init && init.target && init.target.hasOwnProperty('value')) { + setNativeValue(node, init.target.value) + } + return originalChange(node, init) +} + +// 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) + } else if (valueSetter) { + valueSetter.call(element, value) + } else { + throw new Error('The given element does not have a value setter') + } +} + // fallback to synthetic events for React events that the DOM doesn't support const syntheticEvents = ['select', 'mouseEnter', 'mouseLeave'] syntheticEvents.forEach(eventName => { @@ -70,3 +96,5 @@ syntheticEvents.forEach(eventName => { // just re-export everything from dom-testing-library export * from 'dom-testing-library' export {render, cleanup} + +/* eslint complexity:0, func-name-matching:0 */ From bf5f7ce8fbe0a7c3c775a35cf17b3bdec5aefbd9 Mon Sep 17 00:00:00 2001 From: Vitaly Sivkov Date: Tue, 23 Oct 2018 13:27:55 +0500 Subject: [PATCH 2/6] Quick solution for kentcdodds/react-testing-library#206 --- src/index.js | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/index.js b/src/index.js index 67d5dd1f..f4b4f1cc 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,4 @@ import ReactDOM from 'react-dom' -import {Simulate} from 'react-dom/test-utils' import {getQueriesForElement, prettyDOM, fireEvent} from 'dom-testing-library' const mountedContainers = new Set() @@ -67,6 +66,25 @@ fireEvent.change = function reactChange(node, init) { return originalChange(node, init) } +// probably there is a better way to get access to a component instance +const [ + getInstanceFromNode, +] = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events +const createReactEventSimulator = eventName => (node, init) => { + const propName = `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}` + const simulate = getInstanceFromNode(node).memoizedProps[propName] + + if (init && init.target && init.target.hasOwnProperty('value')) { + setNativeValue(node, init.target.value) + } + + return simulate({ + target: node, + type: eventName, + ...init, + }) +} + // function written after some investigation here: // https://github.com/facebook/react/issues/10135#issuecomment-401496776 function setNativeValue(element, value) { @@ -88,9 +106,7 @@ function setNativeValue(element, value) { // fallback to synthetic events for React events that the DOM doesn't support const syntheticEvents = ['select', 'mouseEnter', 'mouseLeave'] syntheticEvents.forEach(eventName => { - document.addEventListener(eventName.toLowerCase(), e => { - Simulate[eventName](e.target, e) - }) + fireEvent[eventName] = createReactEventSimulator(eventName) }) // just re-export everything from dom-testing-library From 6841747344b8a68e5e4e956feaef7673e0ba0592 Mon Sep 17 00:00:00 2001 From: Vitaly Sivkov Date: Wed, 24 Oct 2018 09:34:45 +0500 Subject: [PATCH 3/6] Add @sivkoff to the contributors table --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index db2f5738..2d2e9190 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -558,6 +558,15 @@ "contributions": [ "doc" ] + }, + { + "login": "sivkoff", + "name": "Vitaly Sivkov", + "avatar_url": "https://avatars1.githubusercontent.com/u/2699953?v=4", + "profile": "http://sivkoff.com", + "contributions": [ + "code" + ] } ] } diff --git a/README.md b/README.md index 9992e8a0..1f498fb3 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ [![version][version-badge]][package] [![downloads][downloads-badge]][npmtrends] [![MIT License][license-badge]][license] -[![All Contributors](https://img.shields.io/badge/all_contributors-57-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-58-orange.svg?style=flat-square)](#contributors) [![PRs Welcome][prs-badge]][prs] [![Code of Conduct][coc-badge]][coc] [![Join the community on Spectrum][spectrum-badge]][spectrum] @@ -1308,6 +1308,7 @@ Thanks goes to these people ([emoji key][emojis]): | [
dadamssg](https://github.com/dadamssg)
[📖](https://github.com/kentcdodds/react-testing-library/commits?author=dadamssg "Documentation") | [
Yazan Aabed](https://www.yaabed.com/)
[📝](#blog-YazanAabeed "Blogposts") | [
Tim](https://github.com/timbonicus)
[🐛](https://github.com/kentcdodds/react-testing-library/issues?q=author%3Atimbonicus "Bug reports") [💻](https://github.com/kentcdodds/react-testing-library/commits?author=timbonicus "Code") [📖](https://github.com/kentcdodds/react-testing-library/commits?author=timbonicus "Documentation") [⚠️](https://github.com/kentcdodds/react-testing-library/commits?author=timbonicus "Tests") | [
Divyanshu Maithani](http://divyanshu.xyz)
[✅](#tutorial-divyanshu013 "Tutorials") [📹](#video-divyanshu013 "Videos") | [
Deepak Grover](https://www.linkedin.com/in/metagrover)
[✅](#tutorial-metagrover "Tutorials") [📹](#video-metagrover "Videos") | [
Eyal Cohen](https://github.com/eyalcohen4)
[📖](https://github.com/kentcdodds/react-testing-library/commits?author=eyalcohen4 "Documentation") | [
Peter Makowski](https://github.com/petermakowski)
[📖](https://github.com/kentcdodds/react-testing-library/commits?author=petermakowski "Documentation") | | [
Michiel Nuyts](https://github.com/Michielnuyts)
[📖](https://github.com/kentcdodds/react-testing-library/commits?author=Michielnuyts "Documentation") | [
Joe Ng'ethe](https://github.com/joeynimu)
[💻](https://github.com/kentcdodds/react-testing-library/commits?author=joeynimu "Code") [📖](https://github.com/kentcdodds/react-testing-library/commits?author=joeynimu "Documentation") | [
Kate](https://github.com/Enikol)
[📖](https://github.com/kentcdodds/react-testing-library/commits?author=Enikol "Documentation") | [
Sean](http://www.seanrparker.com)
[📖](https://github.com/kentcdodds/react-testing-library/commits?author=SeanRParker "Documentation") | [
James Long](http://jlongster.com)
[🤔](#ideas-jlongster "Ideas, Planning, & Feedback") [📦](#platform-jlongster "Packaging/porting to new platform") | [
Herb Hagely](https://github.com/hhagely)
[💡](#example-hhagely "Examples") | [
Alex Wendte](http://www.wendtedesigns.com/)
[💡](#example-themostcolm "Examples") | | [
Monica Powell](http://www.aboutmonica.com)
[📖](https://github.com/kentcdodds/react-testing-library/commits?author=M0nica "Documentation") | +| [
Vitaly Sivkov](http://sivkoff.com)
[💻](https://github.com/kentcdodds/react-testing-library/commits?author=sivkoff "Code") | From 20af35cb520be5f7d5d3462fc8f12fd98f6e26d6 Mon Sep 17 00:00:00 2001 From: Vitaly Sivkov Date: Thu, 25 Oct 2018 15:55:44 +0500 Subject: [PATCH 4/6] Use `dom-testing-library` for synthetic event dispatchers --- src/index.js | 68 +++++++++++++++------------------------------------- 1 file changed, 19 insertions(+), 49 deletions(-) diff --git a/src/index.js b/src/index.js index f4b4f1cc..ba270cb4 100644 --- a/src/index.js +++ b/src/index.js @@ -58,57 +58,27 @@ function cleanupAtContainer(container) { mountedContainers.delete(container) } -const originalChange = fireEvent.change -fireEvent.change = function reactChange(node, init) { - if (init && init.target && init.target.hasOwnProperty('value')) { - setNativeValue(node, init.target.value) - } - return originalChange(node, init) -} - -// probably there is a better way to get access to a component instance -const [ - getInstanceFromNode, -] = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events -const createReactEventSimulator = eventName => (node, init) => { - const propName = `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}` - const simulate = getInstanceFromNode(node).memoizedProps[propName] - - if (init && init.target && init.target.hasOwnProperty('value')) { - setNativeValue(node, init.target.value) - } - - return simulate({ - target: node, - type: eventName, - ...init, - }) +// React event system tracks native mouseOver/mouseOut events for +// running onMouseEnter/onMouseLeave handlers +// @link https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react-dom/src/events/EnterLeaveEventPlugin.js#L24-L31 +fireEvent.mouseEnter = fireEvent.mouseOver +fireEvent.mouseLeave = fireEvent.mouseOut + +fireEvent.select = (node, init) => { + // React tracks this event only on focused inputs + node.focus() + + // React creates this event when one of the following native events happens + // - contextMenu + // - mouseUp + // - dragEnd + // - keyUp + // - keyDown + // so we can use any here + // @link https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react-dom/src/events/SelectEventPlugin.js#L203-L224 + fireEvent.keyUp(node, init) } -// 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) - } else if (valueSetter) { - valueSetter.call(element, value) - } else { - throw new Error('The given element does not have a value setter') - } -} - -// fallback to synthetic events for React events that the DOM doesn't support -const syntheticEvents = ['select', 'mouseEnter', 'mouseLeave'] -syntheticEvents.forEach(eventName => { - fireEvent[eventName] = createReactEventSimulator(eventName) -}) - // just re-export everything from dom-testing-library export * from 'dom-testing-library' export {render, cleanup} From e4feddc74834e2a2a7a026ccf422d76540b603b3 Mon Sep 17 00:00:00 2001 From: Vitaly Sivkov Date: Thu, 25 Oct 2018 16:02:12 +0500 Subject: [PATCH 5/6] Rollback unnecessary test changes --- src/__tests__/events.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/__tests__/events.js b/src/__tests__/events.js index 985183c0..b8dd487b 100644 --- a/src/__tests__/events.js +++ b/src/__tests__/events.js @@ -156,12 +156,10 @@ eventTypes.forEach(({type, events, elementType, init}) => { }) test('onChange works', () => { - const spy = jest.fn() - - const {container} = render() - const input = container.firstChild - fireEvent.change(input, {target: {value: 'c'}}) - expect(spy).toHaveBeenCalledTimes(1) + const handleChange = jest.fn() + const { + container: {firstChild: input}, + } = render() + fireEvent.change(input, {target: {value: 'a'}}) + expect(handleChange).toHaveBeenCalledTimes(1) }) - -/* eslint complexity:0 */ From 50e424278b3d982cd6e30f95b63fa642e172f81d Mon Sep 17 00:00:00 2001 From: "Kent C. Dodds" Date: Thu, 25 Oct 2018 06:48:43 -0600 Subject: [PATCH 6/6] Update index.js --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index ba270cb4..a9db403f 100644 --- a/src/index.js +++ b/src/index.js @@ -83,4 +83,4 @@ fireEvent.select = (node, init) => { export * from 'dom-testing-library' export {render, cleanup} -/* eslint complexity:0, func-name-matching:0 */ +/* eslint func-name-matching:0 */