From 4516c2490877042329865cbccfd9865d28ce1b27 Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Fri, 12 Oct 2018 23:04:29 +0200 Subject: [PATCH 1/5] Allow for running in node environment --- .size-snapshot.json | 6 +- jest.config.js | 2 +- package.json | 8 +- .../__snapshots__/wait-for-element.js.snap | 1 + src/__tests__/element-queries.js | 9 +- src/__tests__/events.js | 3 +- src/__tests__/example.js | 1 + src/__tests__/get-queries-for-element.js | 1 + src/__tests__/helpers/document.js | 9 + src/__tests__/helpers/test-utils.js | 1 + src/__tests__/pretty-dom.js | 1 + src/__tests__/wait-for-dom-change.js | 16 +- src/__tests__/wait-for-element.js | 19 +- src/events.js | 197 ++++++++---------- src/get-node-text.js | 2 + src/query-helpers.js | 1 + src/wait-for-dom-change.js | 25 ++- src/wait-for-element.js | 18 +- 18 files changed, 192 insertions(+), 128 deletions(-) create mode 100644 src/__tests__/helpers/document.js diff --git a/.size-snapshot.json b/.size-snapshot.json index a5e568a4..8b346d5f 100644 --- a/.size-snapshot.json +++ b/.size-snapshot.json @@ -1,7 +1,7 @@ { "dist/dom-testing-library.umd.js": { - "bundled": 122519, - "minified": 52052, - "gzipped": 15632 + "bundled": 144335, + "minified": 52790, + "gzipped": 15639 } } diff --git a/jest.config.js b/jest.config.js index 4160d66d..feca704e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,5 @@ const jestConfig = require('kcd-scripts/jest') module.exports = Object.assign(jestConfig, { - testEnvironment: 'jest-environment-jsdom', + testEnvironment: 'jest-environment-node', }) diff --git a/package.json b/package.json index 27221312..b47bc1ee 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,9 @@ "build": "kcd-scripts build && kcd-scripts build --bundle umd --no-clean", "lint": "kcd-scripts lint", "test": "kcd-scripts test", + "test:all": "npm test && npm test -- --env jsdom", "test:update": "npm test -- --updateSnapshot --coverage", - "validate": "kcd-scripts validate", + "validate": "kcd-scripts validate build,lint,test:all", "setup": "npm install && npm run validate -s", "precommit": "kcd-scripts precommit", "dtslint": "dtslint typings" @@ -38,14 +39,15 @@ "typings" ], "dependencies": { - "mutationobserver-shim": "^0.3.2", + "@sheerun/mutationobserver-shim": "^0.3.2", "pretty-format": "^23.6.0", "wait-for-expect": "^1.0.0" }, "devDependencies": { "dtslint": "^0.3.0", - "jest-dom": "^1.7.0", + "jest-dom": "^2.0.4", "jest-in-case": "^1.0.2", + "jsdom": "^12.2.0", "kcd-scripts": "^0.41.0", "microbundle": "^0.4.4" }, diff --git a/src/__tests__/__snapshots__/wait-for-element.js.snap b/src/__tests__/__snapshots__/wait-for-element.js.snap index 2ffcf686..07f5e46d 100644 --- a/src/__tests__/__snapshots__/wait-for-element.js.snap +++ b/src/__tests__/__snapshots__/wait-for-element.js.snap @@ -79,3 +79,4 @@ exports[`it waits for the callback to return a value and only reacts to DOM muta /> `; + diff --git a/src/__tests__/element-queries.js b/src/__tests__/element-queries.js index 47d71609..d49d62c8 100644 --- a/src/__tests__/element-queries.js +++ b/src/__tests__/element-queries.js @@ -1,8 +1,9 @@ import 'jest-dom/extend-expect' import {render} from './helpers/test-utils' +import document from './helpers/document' beforeEach(() => { - window.Cypress = null + document.defaultView.Cypress = null }) test('query can return null', () => { @@ -169,9 +170,7 @@ test('get element by its alt text', () => { finding nemo poster , `) - expect(getByAltText(/fin.*nem.*poster$/i).src).toBe( - 'http://localhost/finding-nemo.png', - ) + expect(getByAltText(/fin.*nem.*poster$/i).src).toContain('/finding-nemo.png') }) test('query/get element by its title', () => { @@ -510,7 +509,7 @@ test('test the debug helper prints the dom state here', () => { }) test('get throws a useful error message without DOM in Cypress', () => { - window.Cypress = {} + document.defaultView.Cypress = {} const { getByLabelText, getBySelectText, diff --git a/src/__tests__/events.js b/src/__tests__/events.js index cb3e7483..10b15f69 100644 --- a/src/__tests__/events.js +++ b/src/__tests__/events.js @@ -1,4 +1,5 @@ import {fireEvent} from '..' +import document from './helpers/document' const eventTypes = [ { @@ -171,7 +172,7 @@ test('assigning a value to a target that cannot have a value throws an error', ( test('assigning the files property on an input', () => { const node = document.createElement('input') - const file = new File(['(⌐□_□)'], 'chucknorris.png', { + const file = new document.defaultView.File(['(⌐□_□)'], 'chucknorris.png', { type: 'image/png', }) fireEvent.change(node, {target: {files: [file]}}) diff --git a/src/__tests__/example.js b/src/__tests__/example.js index da0bb8ba..f6a96dd6 100644 --- a/src/__tests__/example.js +++ b/src/__tests__/example.js @@ -2,6 +2,7 @@ import {getByLabelText, getByText, getByTestId, queryByTestId, wait} from '../' // adds special assertions like toHaveTextContent import 'jest-dom/extend-expect' +import document from './helpers/document' function getExampleDOM() { // This is just a raw example of setting up some DOM diff --git a/src/__tests__/get-queries-for-element.js b/src/__tests__/get-queries-for-element.js index 1a6afba8..44d2e43d 100644 --- a/src/__tests__/get-queries-for-element.js +++ b/src/__tests__/get-queries-for-element.js @@ -1,5 +1,6 @@ import {getQueriesForElement} from '../get-queries-for-element' import {queries} from '..' +import document from './helpers/document' test('uses default queries', () => { const container = document.createElement('div') diff --git a/src/__tests__/helpers/document.js b/src/__tests__/helpers/document.js new file mode 100644 index 00000000..1df46c07 --- /dev/null +++ b/src/__tests__/helpers/document.js @@ -0,0 +1,9 @@ +let testWindow = typeof window === 'undefined' ? undefined : window + +if (typeof window === 'undefined') { + const {JSDOM} = require('jsdom') + const dom = new JSDOM() + testWindow = dom.window +} + +module.exports = testWindow.document diff --git a/src/__tests__/helpers/test-utils.js b/src/__tests__/helpers/test-utils.js index a21a316b..46d270d9 100644 --- a/src/__tests__/helpers/test-utils.js +++ b/src/__tests__/helpers/test-utils.js @@ -1,4 +1,5 @@ import {getQueriesForElement} from '../../get-queries-for-element' +import document from './document' function render(html) { const container = document.createElement('div') diff --git a/src/__tests__/pretty-dom.js b/src/__tests__/pretty-dom.js index 312cf78b..b2fe3d02 100644 --- a/src/__tests__/pretty-dom.js +++ b/src/__tests__/pretty-dom.js @@ -1,5 +1,6 @@ import {prettyDOM} from '../pretty-dom' import {render} from './helpers/test-utils' +import document from './helpers/document' test('it prints out the given DOM element tree', () => { const {container} = render('
Hello World!
') diff --git a/src/__tests__/wait-for-dom-change.js b/src/__tests__/wait-for-dom-change.js index 0034f9bb..4d43d2df 100644 --- a/src/__tests__/wait-for-dom-change.js +++ b/src/__tests__/wait-for-dom-change.js @@ -2,6 +2,7 @@ import {waitForDomChange} from '../' // adds special assertions like toBeTruthy import 'jest-dom/extend-expect' import {render} from './helpers/test-utils' +import document from './helpers/document' const skipSomeTime = delayMs => new Promise(resolve => setTimeout(resolve, delayMs)) @@ -15,7 +16,11 @@ test('it waits for the next DOM mutation', async () => { const successHandler = jest.fn().mockName('successHandler') const errorHandler = jest.fn().mockName('errorHandler') - waitForDomChange().then(successHandler, errorHandler) + if (typeof window === 'undefined') { + waitForDomChange({container: document}).then(successHandler, errorHandler) + } else { + waitForDomChange().then(successHandler, errorHandler) + } // Promise callbacks are always asynchronous. expect(successHandler).toHaveBeenCalledTimes(0) @@ -83,7 +88,14 @@ test('it throws if timeout is exceeded', async () => { const successHandler = jest.fn().mockName('successHandler') const errorHandler = jest.fn().mockName('errorHandler') - waitForDomChange({timeout: 70}).then(successHandler, errorHandler) + if (typeof window === 'undefined') { + waitForDomChange({container: document, timeout: 70}).then( + successHandler, + errorHandler, + ) + } else { + waitForDomChange({timeout: 70}).then(successHandler, errorHandler) + } await skipSomeTimeForMutationObserver(100) diff --git a/src/__tests__/wait-for-element.js b/src/__tests__/wait-for-element.js index d8883d06..aa9a8f9f 100644 --- a/src/__tests__/wait-for-element.js +++ b/src/__tests__/wait-for-element.js @@ -2,6 +2,7 @@ import {waitForElement, wait} from '../' // adds special assertions like toBeTruthy import 'jest-dom/extend-expect' import {render} from './helpers/test-utils' +import document from './helpers/document' const skipSomeTime = delayMs => new Promise(resolve => setTimeout(resolve, delayMs)) @@ -320,7 +321,14 @@ test('works if a container is not defined', async () => { const successHandler = jest.fn().mockName('successHandler') const errorHandler = jest.fn().mockName('errorHandler') - waitForElement(callback).then(successHandler, errorHandler) + if (typeof window === 'undefined') { + waitForElement(callback, {container: document}).then( + successHandler, + errorHandler, + ) + } else { + waitForElement(callback).then(successHandler, errorHandler) + } await skipSomeTimeForMutationObserver() @@ -342,7 +350,14 @@ test('throws an error if callback is not a function', async () => { const successHandler = jest.fn().mockName('successHandler') const errorHandler = jest.fn().mockName('errorHandler') - waitForElement().then(successHandler, errorHandler) + if (typeof window === 'undefined') { + waitForElement(undefined, {container: document}).then( + successHandler, + errorHandler, + ) + } else { + waitForElement().then(successHandler, errorHandler) + } await skipSomeTimeForMutationObserver() diff --git a/src/events.js b/src/events.js index 51073c5b..c50fb59a 100644 --- a/src/events.js +++ b/src/events.js @@ -1,218 +1,201 @@ -const { - AnimationEvent, - ClipboardEvent, - CompositionEvent, - DragEvent, - Event, - FocusEvent, - InputEvent, - KeyboardEvent, - MouseEvent, - ProgressEvent, - TouchEvent, - TransitionEvent, - UIEvent, - WheelEvent, -} = typeof window === 'undefined' ? /* istanbul ignore next */ global : window - const eventMap = { // Clipboard Events copy: { - EventType: ClipboardEvent, + EventType: 'ClipboardEvent', defaultInit: {bubbles: true, cancelable: true}, }, cut: { - EventType: ClipboardEvent, + EventType: 'ClipboardEvent', defaultInit: {bubbles: true, cancelable: true}, }, paste: { - EventType: ClipboardEvent, + EventType: 'ClipboardEvent', defaultInit: {bubbles: true, cancelable: true}, }, // Composition Events compositionEnd: { - EventType: CompositionEvent, + EventType: 'CompositionEvent', defaultInit: {bubbles: true, cancelable: true}, }, compositionStart: { - EventType: CompositionEvent, + EventType: 'CompositionEvent', defaultInit: {bubbles: true, cancelable: true}, }, compositionUpdate: { - EventType: CompositionEvent, + EventType: 'CompositionEvent', defaultInit: {bubbles: true, cancelable: false}, }, // Keyboard Events keyDown: { - EventType: KeyboardEvent, + EventType: 'KeyboardEvent', defaultInit: {bubbles: true, cancelable: true}, }, keyPress: { - EventType: KeyboardEvent, + EventType: 'KeyboardEvent', defaultInit: {bubbles: true, cancelable: true}, }, keyUp: { - EventType: KeyboardEvent, + EventType: 'KeyboardEvent', defaultInit: {bubbles: true, cancelable: true}, }, // Focus Events focus: { - EventType: FocusEvent, + EventType: 'FocusEvent', defaultInit: {bubbles: false, cancelable: false}, }, blur: { - EventType: FocusEvent, + EventType: 'FocusEvent', defaultInit: {bubbles: false, cancelable: false}, }, // Form Events change: { - EventType: InputEvent, + EventType: 'InputEvent', defaultInit: {bubbles: true, cancelable: true}, }, input: { - EventType: InputEvent, + EventType: 'InputEvent', defaultInit: {bubbles: true, cancelable: true}, }, invalid: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: true}, }, submit: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: true, cancelable: true}, }, // Mouse Events click: { - EventType: MouseEvent, + EventType: 'MouseEvent', defaultInit: {bubbles: true, cancelable: true, button: 0}, }, contextMenu: { - EventType: MouseEvent, + EventType: 'MouseEvent', defaultInit: {bubbles: true, cancelable: true}, }, dblClick: { - EventType: MouseEvent, + EventType: 'MouseEvent', defaultInit: {bubbles: true, cancelable: true}, }, drag: { - EventType: DragEvent, + EventType: 'DragEvent', defaultInit: {bubbles: true, cancelable: true}, }, dragEnd: { - EventType: DragEvent, + EventType: 'DragEvent', defaultInit: {bubbles: true, cancelable: false}, }, dragEnter: { - EventType: DragEvent, + EventType: 'DragEvent', defaultInit: {bubbles: true, cancelable: true}, }, dragExit: { - EventType: DragEvent, + EventType: 'DragEvent', defaultInit: {bubbles: true, cancelable: false}, }, dragLeave: { - EventType: DragEvent, + EventType: 'DragEvent', defaultInit: {bubbles: true, cancelable: false}, }, dragOver: { - EventType: DragEvent, + EventType: 'DragEvent', defaultInit: {bubbles: true, cancelable: true}, }, dragStart: { - EventType: DragEvent, + EventType: 'DragEvent', defaultInit: {bubbles: true, cancelable: true}, }, drop: { - EventType: DragEvent, + EventType: 'DragEvent', defaultInit: {bubbles: true, cancelable: true}, }, mouseDown: { - EventType: MouseEvent, + EventType: 'MouseEvent', defaultInit: {bubbles: true, cancelable: true}, }, mouseEnter: { - EventType: MouseEvent, + EventType: 'MouseEvent', defaultInit: {bubbles: true, cancelable: true}, }, mouseLeave: { - EventType: MouseEvent, + EventType: 'MouseEvent', defaultInit: {bubbles: true, cancelable: true}, }, mouseMove: { - EventType: MouseEvent, + EventType: 'MouseEvent', defaultInit: {bubbles: true, cancelable: true}, }, mouseOut: { - EventType: MouseEvent, + EventType: 'MouseEvent', defaultInit: {bubbles: true, cancelable: true}, }, mouseOver: { - EventType: MouseEvent, + EventType: 'MouseEvent', defaultInit: {bubbles: true, cancelable: true}, }, mouseUp: { - EventType: MouseEvent, + EventType: 'MouseEvent', defaultInit: {bubbles: true, cancelable: true}, }, // Selection Events select: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: true, cancelable: false}, }, // Touch Events touchCancel: { - EventType: TouchEvent, + EventType: 'TouchEvent', defaultInit: {bubbles: true, cancelable: false}, }, touchEnd: { - EventType: TouchEvent, + EventType: 'TouchEvent', defaultInit: {bubbles: true, cancelable: true}, }, touchMove: { - EventType: TouchEvent, + EventType: 'TouchEvent', defaultInit: {bubbles: true, cancelable: true}, }, touchStart: { - EventType: TouchEvent, + EventType: 'TouchEvent', defaultInit: {bubbles: true, cancelable: true}, }, // UI Events scroll: { - EventType: UIEvent, + EventType: 'UIEvent', defaultInit: {bubbles: false, cancelable: false}, }, // Wheel Events wheel: { - EventType: WheelEvent, + EventType: 'WheelEvent', defaultInit: {bubbles: true, cancelable: true}, }, // Media Events abort: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, canPlay: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, canPlayThrough: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, durationChange: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, emptied: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, encrypted: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, ended: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, // error: { @@ -220,90 +203,90 @@ const eventMap = { // defaultInit: {bubbles: false, cancelable: false}, // }, loadedData: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, loadedMetadata: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, loadStart: { - EventType: ProgressEvent, + EventType: 'ProgressEvent', defaultInit: {bubbles: false, cancelable: false}, }, pause: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, play: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, playing: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, progress: { - EventType: ProgressEvent, + EventType: 'ProgressEvent', defaultInit: {bubbles: false, cancelable: false}, }, rateChange: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, seeked: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, seeking: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, stalled: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, suspend: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, timeUpdate: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, volumeChange: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, waiting: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, // Image Events load: { - EventType: UIEvent, + EventType: 'UIEvent', defaultInit: {bubbles: false, cancelable: false}, }, error: { - EventType: Event, + EventType: 'Event', defaultInit: {bubbles: false, cancelable: false}, }, // Animation Events animationStart: { - EventType: AnimationEvent, + EventType: 'AnimationEvent', defaultInit: {bubbles: true, cancelable: false}, }, animationEnd: { - EventType: AnimationEvent, + EventType: 'AnimationEvent', defaultInit: {bubbles: true, cancelable: false}, }, animationIteration: { - EventType: AnimationEvent, + EventType: 'AnimationEvent', defaultInit: {bubbles: true, cancelable: false}, }, // Transition Events transitionEnd: { - EventType: TransitionEvent, + EventType: 'TransitionEvent', defaultInit: {bubbles: true, cancelable: true}, }, } @@ -316,28 +299,32 @@ function fireEvent(element, event) { return element.dispatchEvent(event) } -Object.entries(eventMap).forEach(([key, {EventType = Event, defaultInit}]) => { - const eventName = key.toLowerCase() +Object.entries(eventMap).forEach( + ([key, {EventType = 'Event', defaultInit}]) => { + const eventName = key.toLowerCase() - fireEvent[key] = (node, init) => { - const eventInit = {...defaultInit, ...init} - const {target: {value, files, ...targetProperties} = {}} = eventInit - Object.assign(node, targetProperties) - if (value !== undefined) { - setNativeValue(node, value) - } - if (files !== undefined) { - // input.files is a read-only property so this is not allowed: - // input.files = [file] - // so we have to use this workaround to set the property - Object.defineProperty(node, 'files', { - value: files, - }) + fireEvent[key] = (node, init) => { + const eventInit = {...defaultInit, ...init} + const {target: {value, files, ...targetProperties} = {}} = eventInit + Object.assign(node, targetProperties) + if (value !== undefined) { + setNativeValue(node, value) + } + if (files !== undefined) { + // input.files is a read-only property so this is not allowed: + // input.files = [file] + // so we have to use this workaround to set the property + Object.defineProperty(node, 'files', { + value: files, + }) + } + const window = node.ownerDocument.defaultView + const EventConstructor = window[EventType] || window.Event + const event = new EventConstructor(eventName, eventInit) + return fireEvent(node, event) } - 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 diff --git a/src/get-node-text.js b/src/get-node-text.js index cace4734..1f7c3ecd 100644 --- a/src/get-node-text.js +++ b/src/get-node-text.js @@ -1,4 +1,6 @@ function getNodeText(node) { + const window = node.ownerDocument.defaultView + return Array.from(node.childNodes) .filter( child => diff --git a/src/query-helpers.js b/src/query-helpers.js index 8a8e7751..3f3e7e56 100644 --- a/src/query-helpers.js +++ b/src/query-helpers.js @@ -8,6 +8,7 @@ function debugDOM(htmlElement) { typeof process !== 'undefined' && process.versions !== undefined && process.versions.node !== undefined + const window = htmlElement.ownerDocument.defaultView const inCypress = typeof window !== 'undefined' && window.Cypress /* istanbul ignore else */ if (inCypress) { diff --git a/src/wait-for-dom-change.js b/src/wait-for-dom-change.js index 3a9069f6..703c278d 100644 --- a/src/wait-for-dom-change.js +++ b/src/wait-for-dom-change.js @@ -1,7 +1,7 @@ -import 'mutationobserver-shim' +import MutationObserver from '@sheerun/mutationobserver-shim' function waitForDomChange({ - container = document, + container = getDocument(), timeout = 4500, mutationObserverOptions = { subtree: true, @@ -10,9 +10,21 @@ function waitForDomChange({ characterData: true, }, } = {}) { + if (typeof container === 'undefined') { + if (typeof window === 'undefined') { + throw new Error('Could not find default container') + } else { + container = window.document + } + } return new Promise((resolve, reject) => { const timer = setTimeout(onTimeout, timeout) - const observer = new window.MutationObserver(onMutation) + const MutationObserverConstructor = + typeof window !== 'undefined' && + typeof window.MutationObserver !== 'undefined' + ? window.MutationObserver + : MutationObserver + const observer = new MutationObserverConstructor(onMutation) observer.observe(container, mutationObserverOptions) function onDone(error, result) { @@ -33,4 +45,11 @@ function waitForDomChange({ }) } +function getDocument() { + if (typeof window === 'undefined') { + throw new Error('Could not find default container') + } + return window.document +} + export {waitForDomChange} diff --git a/src/wait-for-element.js b/src/wait-for-element.js index a632020d..bd2158ca 100644 --- a/src/wait-for-element.js +++ b/src/wait-for-element.js @@ -1,9 +1,9 @@ -import 'mutationobserver-shim' +import MutationObserver from '@sheerun/mutationobserver-shim' function waitForElement( callback, { - container = document, + container = getDocument(), timeout = 4500, mutationObserverOptions = { subtree: true, @@ -19,7 +19,12 @@ function waitForElement( } let lastError const timer = setTimeout(onTimeout, timeout) - const observer = new window.MutationObserver(onMutation) + const MutationObserverConstructor = + typeof window !== 'undefined' && + typeof window.MutationObserver !== 'undefined' + ? window.MutationObserver + : MutationObserver + const observer = new MutationObserverConstructor(onMutation) observer.observe(container, mutationObserverOptions) function onDone(error, result) { clearTimeout(timer) @@ -50,4 +55,11 @@ function waitForElement( }) } +function getDocument() { + if (typeof window === 'undefined') { + throw new Error('Could not find default container') + } + return window.document +} + export {waitForElement} From 6c011ae7d1a4d2320c682394e685b36d650c8823 Mon Sep 17 00:00:00 2001 From: "Kent C. Dodds" Date: Fri, 12 Oct 2018 15:24:49 -0600 Subject: [PATCH 2/5] use jest projects --- .size-snapshot.json | 6 +++--- jest.config.js | 22 ++++++++++++++++++---- package.json | 3 +-- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/.size-snapshot.json b/.size-snapshot.json index 8b346d5f..363648a9 100644 --- a/.size-snapshot.json +++ b/.size-snapshot.json @@ -1,7 +1,7 @@ { "dist/dom-testing-library.umd.js": { - "bundled": 144335, - "minified": 52790, - "gzipped": 15639 + "bundled": 146890, + "minified": 53947, + "gzipped": 15849 } } diff --git a/jest.config.js b/jest.config.js index feca704e..006e2155 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,19 @@ -const jestConfig = require('kcd-scripts/jest') +const baseConfig = require('kcd-scripts/jest') -module.exports = Object.assign(jestConfig, { - testEnvironment: 'jest-environment-node', -}) +module.exports = { + collectCoverageFrom: baseConfig.collectCoverageFrom, + projects: [ + { + ...baseConfig, + displayName: 'jsdom', + testEnvironment: 'jest-environment-jsdom', + }, + { + ...baseConfig, + displayName: 'node', + testEnvironment: 'jest-environment-node', + }, + ], + // this is for eslint + modulePaths: baseConfig.modulePaths, +} diff --git a/package.json b/package.json index b47bc1ee..b82e6473 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,8 @@ "build": "kcd-scripts build && kcd-scripts build --bundle umd --no-clean", "lint": "kcd-scripts lint", "test": "kcd-scripts test", - "test:all": "npm test && npm test -- --env jsdom", "test:update": "npm test -- --updateSnapshot --coverage", - "validate": "kcd-scripts validate build,lint,test:all", + "validate": "kcd-scripts validate", "setup": "npm install && npm run validate -s", "precommit": "kcd-scripts precommit", "dtslint": "dtslint typings" From 399c2e95263fcdf703a2f55e56140879f6d09f35 Mon Sep 17 00:00:00 2001 From: "Kent C. Dodds" Date: Fri, 12 Oct 2018 15:28:12 -0600 Subject: [PATCH 3/5] remove unecessary code and add coverage checking --- jest.config.js | 1 + src/wait-for-dom-change.js | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/jest.config.js b/jest.config.js index 006e2155..435c4715 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,6 +2,7 @@ const baseConfig = require('kcd-scripts/jest') module.exports = { collectCoverageFrom: baseConfig.collectCoverageFrom, + coverageThreshold: baseConfig.coverageThreshold, projects: [ { ...baseConfig, diff --git a/src/wait-for-dom-change.js b/src/wait-for-dom-change.js index 703c278d..3db6c260 100644 --- a/src/wait-for-dom-change.js +++ b/src/wait-for-dom-change.js @@ -10,13 +10,6 @@ function waitForDomChange({ characterData: true, }, } = {}) { - if (typeof container === 'undefined') { - if (typeof window === 'undefined') { - throw new Error('Could not find default container') - } else { - container = window.document - } - } return new Promise((resolve, reject) => { const timer = setTimeout(onTimeout, timeout) const MutationObserverConstructor = From c456a7f8713b080e1ba16fa101f4694aa8aee0fd Mon Sep 17 00:00:00 2001 From: "Kent C. Dodds" Date: Fri, 12 Oct 2018 15:31:08 -0600 Subject: [PATCH 4/5] fix coverage --- src/events.js | 46 ++++++++++++++++++-------------------- src/wait-for-dom-change.js | 2 ++ src/wait-for-element.js | 2 ++ 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/events.js b/src/events.js index c50fb59a..7eedfb49 100644 --- a/src/events.js +++ b/src/events.js @@ -299,32 +299,30 @@ function fireEvent(element, event) { return element.dispatchEvent(event) } -Object.entries(eventMap).forEach( - ([key, {EventType = 'Event', defaultInit}]) => { - const eventName = key.toLowerCase() +Object.entries(eventMap).forEach(([key, {EventType, defaultInit}]) => { + const eventName = key.toLowerCase() - fireEvent[key] = (node, init) => { - const eventInit = {...defaultInit, ...init} - const {target: {value, files, ...targetProperties} = {}} = eventInit - Object.assign(node, targetProperties) - if (value !== undefined) { - setNativeValue(node, value) - } - if (files !== undefined) { - // input.files is a read-only property so this is not allowed: - // input.files = [file] - // so we have to use this workaround to set the property - Object.defineProperty(node, 'files', { - value: files, - }) - } - const window = node.ownerDocument.defaultView - const EventConstructor = window[EventType] || window.Event - const event = new EventConstructor(eventName, eventInit) - return fireEvent(node, event) + fireEvent[key] = (node, init) => { + const eventInit = {...defaultInit, ...init} + const {target: {value, files, ...targetProperties} = {}} = eventInit + Object.assign(node, targetProperties) + if (value !== undefined) { + setNativeValue(node, value) } - }, -) + if (files !== undefined) { + // input.files is a read-only property so this is not allowed: + // input.files = [file] + // so we have to use this workaround to set the property + Object.defineProperty(node, 'files', { + value: files, + }) + } + const window = node.ownerDocument.defaultView + const EventConstructor = window[EventType] || window.Event + const event = new EventConstructor(eventName, eventInit) + return fireEvent(node, event) + } +}) // function written after some investigation here: // https://github.com/facebook/react/issues/10135#issuecomment-401496776 diff --git a/src/wait-for-dom-change.js b/src/wait-for-dom-change.js index 3db6c260..ead39b2c 100644 --- a/src/wait-for-dom-change.js +++ b/src/wait-for-dom-change.js @@ -12,6 +12,7 @@ function waitForDomChange({ } = {}) { return new Promise((resolve, reject) => { const timer = setTimeout(onTimeout, timeout) + /* istanbul ignore next */ const MutationObserverConstructor = typeof window !== 'undefined' && typeof window.MutationObserver !== 'undefined' @@ -39,6 +40,7 @@ function waitForDomChange({ } function getDocument() { + /* istanbul ignore if */ if (typeof window === 'undefined') { throw new Error('Could not find default container') } diff --git a/src/wait-for-element.js b/src/wait-for-element.js index bd2158ca..01195445 100644 --- a/src/wait-for-element.js +++ b/src/wait-for-element.js @@ -19,6 +19,7 @@ function waitForElement( } let lastError const timer = setTimeout(onTimeout, timeout) + /* istanbul ignore next */ const MutationObserverConstructor = typeof window !== 'undefined' && typeof window.MutationObserver !== 'undefined' @@ -56,6 +57,7 @@ function waitForElement( } function getDocument() { + /* istanbul ignore if */ if (typeof window === 'undefined') { throw new Error('Could not find default container') } From ad95459b6d5443c537ff3614e6c65ed66ca8e0a5 Mon Sep 17 00:00:00 2001 From: "Kent C. Dodds" Date: Fri, 12 Oct 2018 16:14:31 -0600 Subject: [PATCH 5/5] time is funny --- src/__tests__/wait.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/wait.js b/src/__tests__/wait.js index 0efbc93d..95e1ace0 100644 --- a/src/__tests__/wait.js +++ b/src/__tests__/wait.js @@ -20,6 +20,6 @@ test('can just be used for a next tick thing', async () => { const after = Date.now() // if it's greater than 50 then `wait` used a timeout // but it should have resolved sooner. It really should be 0 - expect(after - before).not.toBeGreaterThan(50) + expect(after - before).not.toBeGreaterThan(60) expect(spy).toHaveBeenCalledTimes(1) })