From 6348601f26edace9d8d70b0f1c3cbf2646c0e5bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brais=20Pi=C3=B1eiro?= Date: Wed, 21 Aug 2019 10:49:00 +0100 Subject: [PATCH 01/13] Upgrade wait-for-expect package The new version of this package includes some important fixes. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 001b3e3c..2fa7a6a4 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@types/testing-library__dom": "^6.0.0", "aria-query": "3.0.0", "pretty-format": "^24.8.0", - "wait-for-expect": "^1.3.0" + "wait-for-expect": "^2.0.0" }, "devDependencies": { "@testing-library/jest-dom": "^4.0.0", From 234bda5b0c816e85d26bd71bf37a00e2471db81e Mon Sep 17 00:00:00 2001 From: brapifra Date: Thu, 22 Aug 2019 10:48:53 +0100 Subject: [PATCH 02/13] Get all helpers functions lazily --- src/helpers.js | 25 +++++++++++++++++-------- src/wait-for-dom-change.js | 12 ++++++------ src/wait-for-element-to-be-removed.js | 12 ++++++------ src/wait-for-element.js | 12 ++++++------ 4 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/helpers.js b/src/helpers.js index ab43e72b..276555b1 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,17 +1,26 @@ import MutationObserver from '@sheerun/mutationobserver-shim' -const globalObj = typeof window === 'undefined' ? global : window +function getGlobalObj() { + return typeof window === 'undefined' ? global : window +} // we only run our tests in node, and setImmediate is supported in node. // istanbul ignore next function setImmediatePolyfill(fn) { - return globalObj.setTimeout(fn, 0) + return getGlobalObj().setTimeout(fn, 0) } -const clearTimeoutFn = globalObj.clearTimeout +function getClearTimeoutFn() { + return getGlobalObj().clearTimeout +} // istanbul ignore next -const setImmediateFn = globalObj.setImmediate || setImmediatePolyfill -const setTimeoutFn = globalObj.setTimeout +function getSetImmediateFn() { + return getGlobalObj().setImmediate || setImmediatePolyfill +} + +function getSetTimeoutFn() { + return getGlobalObj().setTimeout +} function newMutationObserver(onMutation) { const MutationObserverConstructor = @@ -34,7 +43,7 @@ function getDocument() { export { getDocument, newMutationObserver, - clearTimeoutFn as clearTimeout, - setImmediateFn as setImmediate, - setTimeoutFn as setTimeout, + getClearTimeoutFn as getClearTimeout, + getSetImmediateFn as getSetImmediate, + getSetTimeoutFn as getSetTimeout, } diff --git a/src/wait-for-dom-change.js b/src/wait-for-dom-change.js index 71472383..fca49280 100644 --- a/src/wait-for-dom-change.js +++ b/src/wait-for-dom-change.js @@ -1,9 +1,9 @@ import { newMutationObserver, getDocument, - setImmediate, - setTimeout, - clearTimeout, + getSetImmediate, + getSetTimeout, + getClearTimeout, } from './helpers' import {getConfig} from './config' @@ -18,13 +18,13 @@ function waitForDomChange({ }, } = {}) { return new Promise((resolve, reject) => { - const timer = setTimeout(onTimeout, timeout) + const timer = getSetTimeout()(onTimeout, timeout) const observer = newMutationObserver(onMutation) observer.observe(container, mutationObserverOptions) function onDone(error, result) { - clearTimeout(timer) - setImmediate(() => observer.disconnect()) + getClearTimeout()(timer) + getSetImmediate()(() => observer.disconnect()) if (error) { reject(error) } else { diff --git a/src/wait-for-element-to-be-removed.js b/src/wait-for-element-to-be-removed.js index 86e4b1fc..e2f89851 100644 --- a/src/wait-for-element-to-be-removed.js +++ b/src/wait-for-element-to-be-removed.js @@ -1,9 +1,9 @@ import { getDocument, newMutationObserver, - setImmediate, - setTimeout, - clearTimeout, + getSetImmediate, + getSetTimeout, + getClearTimeout, } from './helpers' import {getConfig} from './config' @@ -28,7 +28,7 @@ function waitForElementToBeRemoved( ), ) } - const timer = setTimeout(onTimeout, timeout) + const timer = getSetTimeout()(onTimeout, timeout) const observer = newMutationObserver(onMutation) // Check if the element is not present synchronously, @@ -50,8 +50,8 @@ function waitForElementToBeRemoved( } function onDone(error, result) { - clearTimeout(timer) - setImmediate(() => observer.disconnect()) + getClearTimeout()(timer) + getSetImmediate()(() => observer.disconnect()) if (error) { reject(error) } else { diff --git a/src/wait-for-element.js b/src/wait-for-element.js index 352f36ae..b0bf850b 100644 --- a/src/wait-for-element.js +++ b/src/wait-for-element.js @@ -1,9 +1,9 @@ import { newMutationObserver, getDocument, - setImmediate, - setTimeout, - clearTimeout, + getSetImmediate, + getSetTimeout, + getClearTimeout, } from './helpers' import {getConfig} from './config' @@ -28,13 +28,13 @@ function waitForElement( return } let lastError - const timer = setTimeout(onTimeout, timeout) + const timer = getSetTimeout()(onTimeout, timeout) const observer = newMutationObserver(onMutation) observer.observe(container, mutationObserverOptions) function onDone(error, result) { - clearTimeout(timer) - setImmediate(() => observer.disconnect()) + getClearTimeout()(timer) + getSetImmediate()(() => observer.disconnect()) if (error) { reject(error) } else { From f83b35cb0f0d54d842f6407fdd640447f3cf212f Mon Sep 17 00:00:00 2001 From: brapifra Date: Thu, 22 Aug 2019 11:15:58 +0100 Subject: [PATCH 03/13] Add unit tests for lazy load of helper functions --- src/__tests__/wait-for-dom-change.js | 15 ++++++++++ .../wait-for-element-to-be-removed.js | 28 +++++++++++++++++++ src/__tests__/wait-for-element.js | 15 ++++++++++ 3 files changed, 58 insertions(+) diff --git a/src/__tests__/wait-for-dom-change.js b/src/__tests__/wait-for-dom-change.js index 39487672..202c99ca 100644 --- a/src/__tests__/wait-for-dom-change.js +++ b/src/__tests__/wait-for-dom-change.js @@ -48,3 +48,18 @@ Array [ ] `) }) + +test('uses real timers even if they were set to fake before importing the module', async () => { + jest.resetModules() + jest.useFakeTimers() + const importedWaitForDomChange = require('../').waitForDomChange + jest.useRealTimers() + + const {container} = renderIntoDocument('
') + + setTimeout(() => container.firstChild.setAttribute('id', 'foo'), 1000) + + await expect(importedWaitForDomChange({timeout: 200})).rejects.toThrow( + /timed out/i, + ) +}) diff --git a/src/__tests__/wait-for-element-to-be-removed.js b/src/__tests__/wait-for-element-to-be-removed.js index e541d86f..63ca0e62 100644 --- a/src/__tests__/wait-for-element-to-be-removed.js +++ b/src/__tests__/wait-for-element-to-be-removed.js @@ -57,3 +57,31 @@ test('requires an unempty array of elements to exist first', () => { `"The callback function which was passed did not return an element or non-empty array of elements. waitForElementToBeRemoved requires that the element(s) exist before waiting for removal."`, ) }) + +test('uses real timers even if they were set to fake before importing the module', async () => { + jest.resetModules() + jest.useFakeTimers() + const importedWaitForElementToBeRemoved = require('../') + .waitForElementToBeRemoved + jest.useRealTimers() + + const {queryAllByTestId} = renderIntoDocument(` +
+
+ `) + const divs = queryAllByTestId('div') + // first mutation + setTimeout(() => { + divs.forEach(d => d.setAttribute('id', 'mutated')) + }) + // removal + setTimeout(() => { + divs.forEach(div => div.parentElement.removeChild(div)) + }, 1000) + + await expect( + importedWaitForElementToBeRemoved(() => queryAllByTestId('div'), { + timeout: 200, + }), + ).rejects.toThrow(/timed out/i) +}) diff --git a/src/__tests__/wait-for-element.js b/src/__tests__/wait-for-element.js index 2fba0cb0..4d4efd92 100644 --- a/src/__tests__/wait-for-element.js +++ b/src/__tests__/wait-for-element.js @@ -48,3 +48,18 @@ test('waits until callback does not return null', async () => { test('throws error if no callback is provided', async () => { await expect(waitForElement()).rejects.toThrow(/callback/i) }) + +test('uses real timers even if they were set to fake before importing the module', async () => { + jest.resetModules() + jest.useFakeTimers() + const importedWaitForElement = require('../').waitForElement + jest.useRealTimers() + + const {rerender, getByTestId} = renderIntoDocument('
') + + setTimeout(() => rerender('
'), 1000) + + await expect( + importedWaitForElement(() => getByTestId('div'), {timeout: 200}), + ).rejects.toThrow(/Unable to find/i) +}) From 7e6699e2de786391b7c4a500c904c59a06f84a6e Mon Sep 17 00:00:00 2001 From: brapifra Date: Thu, 29 Aug 2019 10:50:02 +0100 Subject: [PATCH 04/13] Revert: Downgrade wait-for-expect package to 1.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2fa7a6a4..001b3e3c 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@types/testing-library__dom": "^6.0.0", "aria-query": "3.0.0", "pretty-format": "^24.8.0", - "wait-for-expect": "^2.0.0" + "wait-for-expect": "^1.3.0" }, "devDependencies": { "@testing-library/jest-dom": "^4.0.0", From 7d390b4da4887b980569f784e3d1d283ca7a6bfe Mon Sep 17 00:00:00 2001 From: brapifra Date: Thu, 29 Aug 2019 10:59:20 +0100 Subject: [PATCH 05/13] Disable jest fake timers temporary when getting time functions --- src/helpers.js | 41 ++++++++++++++++----------- src/wait-for-dom-change.js | 12 ++++---- src/wait-for-element-to-be-removed.js | 12 ++++---- src/wait-for-element.js | 12 ++++---- 4 files changed, 43 insertions(+), 34 deletions(-) diff --git a/src/helpers.js b/src/helpers.js index 276555b1..4d2b3e36 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,27 +1,36 @@ import MutationObserver from '@sheerun/mutationobserver-shim' -function getGlobalObj() { - return typeof window === 'undefined' ? global : window -} +const globalObj = typeof window === 'undefined' ? global : window // we only run our tests in node, and setImmediate is supported in node. // istanbul ignore next function setImmediatePolyfill(fn) { - return getGlobalObj().setTimeout(fn, 0) + return globalObj.setTimeout(fn, 0) } -function getClearTimeoutFn() { - return getGlobalObj().clearTimeout -} -// istanbul ignore next -function getSetImmediateFn() { - return getGlobalObj().setImmediate || setImmediatePolyfill -} +function getTimeFunctions() { + const usingJestFakeTimers = globalObj.setTimeout._isMockFunction && !!jest + + if (usingJestFakeTimers) { + jest.useRealTimers() + } -function getSetTimeoutFn() { - return getGlobalObj().setTimeout + const timeFunctions = { + clearTimeoutFn: globalObj.clearTimeout, + // istanbul ignore next + setImmediateFn: globalObj.setImmediate || setImmediatePolyfill, + setTimeoutFn: globalObj.setTimeout, + } + + if (usingJestFakeTimers) { + jest.useFakeTimers() + } + + return timeFunctions } +const {clearTimeoutFn, setImmediateFn, setTimeoutFn} = getTimeFunctions() + function newMutationObserver(onMutation) { const MutationObserverConstructor = typeof window !== 'undefined' && @@ -43,7 +52,7 @@ function getDocument() { export { getDocument, newMutationObserver, - getClearTimeoutFn as getClearTimeout, - getSetImmediateFn as getSetImmediate, - getSetTimeoutFn as getSetTimeout, + clearTimeoutFn as clearTimeout, + setImmediateFn as setImmediate, + setTimeoutFn as setTimeout, } diff --git a/src/wait-for-dom-change.js b/src/wait-for-dom-change.js index fca49280..71472383 100644 --- a/src/wait-for-dom-change.js +++ b/src/wait-for-dom-change.js @@ -1,9 +1,9 @@ import { newMutationObserver, getDocument, - getSetImmediate, - getSetTimeout, - getClearTimeout, + setImmediate, + setTimeout, + clearTimeout, } from './helpers' import {getConfig} from './config' @@ -18,13 +18,13 @@ function waitForDomChange({ }, } = {}) { return new Promise((resolve, reject) => { - const timer = getSetTimeout()(onTimeout, timeout) + const timer = setTimeout(onTimeout, timeout) const observer = newMutationObserver(onMutation) observer.observe(container, mutationObserverOptions) function onDone(error, result) { - getClearTimeout()(timer) - getSetImmediate()(() => observer.disconnect()) + clearTimeout(timer) + setImmediate(() => observer.disconnect()) if (error) { reject(error) } else { diff --git a/src/wait-for-element-to-be-removed.js b/src/wait-for-element-to-be-removed.js index e2f89851..86e4b1fc 100644 --- a/src/wait-for-element-to-be-removed.js +++ b/src/wait-for-element-to-be-removed.js @@ -1,9 +1,9 @@ import { getDocument, newMutationObserver, - getSetImmediate, - getSetTimeout, - getClearTimeout, + setImmediate, + setTimeout, + clearTimeout, } from './helpers' import {getConfig} from './config' @@ -28,7 +28,7 @@ function waitForElementToBeRemoved( ), ) } - const timer = getSetTimeout()(onTimeout, timeout) + const timer = setTimeout(onTimeout, timeout) const observer = newMutationObserver(onMutation) // Check if the element is not present synchronously, @@ -50,8 +50,8 @@ function waitForElementToBeRemoved( } function onDone(error, result) { - getClearTimeout()(timer) - getSetImmediate()(() => observer.disconnect()) + clearTimeout(timer) + setImmediate(() => observer.disconnect()) if (error) { reject(error) } else { diff --git a/src/wait-for-element.js b/src/wait-for-element.js index b0bf850b..352f36ae 100644 --- a/src/wait-for-element.js +++ b/src/wait-for-element.js @@ -1,9 +1,9 @@ import { newMutationObserver, getDocument, - getSetImmediate, - getSetTimeout, - getClearTimeout, + setImmediate, + setTimeout, + clearTimeout, } from './helpers' import {getConfig} from './config' @@ -28,13 +28,13 @@ function waitForElement( return } let lastError - const timer = getSetTimeout()(onTimeout, timeout) + const timer = setTimeout(onTimeout, timeout) const observer = newMutationObserver(onMutation) observer.observe(container, mutationObserverOptions) function onDone(error, result) { - getClearTimeout()(timer) - getSetImmediate()(() => observer.disconnect()) + clearTimeout(timer) + setImmediate(() => observer.disconnect()) if (error) { reject(error) } else { From 701f4909bef7120b4bc0828b0ab677c4e723b55d Mon Sep 17 00:00:00 2001 From: brapifra Date: Thu, 29 Aug 2019 11:09:10 +0100 Subject: [PATCH 06/13] Add unit tests to make sure that jest's timers aren't modified by the library --- src/__tests__/wait-for-dom-change.js | 10 +++++++++- src/__tests__/wait-for-element-to-be-removed.js | 10 +++++++++- src/__tests__/wait-for-element.js | 10 +++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/__tests__/wait-for-dom-change.js b/src/__tests__/wait-for-dom-change.js index 202c99ca..83f3eff7 100644 --- a/src/__tests__/wait-for-dom-change.js +++ b/src/__tests__/wait-for-dom-change.js @@ -53,7 +53,6 @@ test('uses real timers even if they were set to fake before importing the module jest.resetModules() jest.useFakeTimers() const importedWaitForDomChange = require('../').waitForDomChange - jest.useRealTimers() const {container} = renderIntoDocument('
') @@ -63,3 +62,12 @@ test('uses real timers even if they were set to fake before importing the module /timed out/i, ) }) + +test("doesn't change jest's timers value when importing the module", () => { + jest.resetModules() + jest.useFakeTimers() + // eslint-disable-next-line + require('../').waitForDomChange + + expect(window.setTimeout._isMockFunction).toEqual(true) +}) diff --git a/src/__tests__/wait-for-element-to-be-removed.js b/src/__tests__/wait-for-element-to-be-removed.js index 63ca0e62..829f741d 100644 --- a/src/__tests__/wait-for-element-to-be-removed.js +++ b/src/__tests__/wait-for-element-to-be-removed.js @@ -63,7 +63,6 @@ test('uses real timers even if they were set to fake before importing the module jest.useFakeTimers() const importedWaitForElementToBeRemoved = require('../') .waitForElementToBeRemoved - jest.useRealTimers() const {queryAllByTestId} = renderIntoDocument(`
@@ -85,3 +84,12 @@ test('uses real timers even if they were set to fake before importing the module }), ).rejects.toThrow(/timed out/i) }) + +test("doesn't change jest's timers value when importing the module", () => { + jest.resetModules() + jest.useFakeTimers() + // eslint-disable-next-line + require('../').waitForElementToBeRemoved + + expect(window.setTimeout._isMockFunction).toEqual(true) +}) diff --git a/src/__tests__/wait-for-element.js b/src/__tests__/wait-for-element.js index 4d4efd92..08df6255 100644 --- a/src/__tests__/wait-for-element.js +++ b/src/__tests__/wait-for-element.js @@ -53,7 +53,6 @@ test('uses real timers even if they were set to fake before importing the module jest.resetModules() jest.useFakeTimers() const importedWaitForElement = require('../').waitForElement - jest.useRealTimers() const {rerender, getByTestId} = renderIntoDocument('
') @@ -63,3 +62,12 @@ test('uses real timers even if they were set to fake before importing the module importedWaitForElement(() => getByTestId('div'), {timeout: 200}), ).rejects.toThrow(/Unable to find/i) }) + +test("doesn't change jest's timers value when importing the module", () => { + jest.resetModules() + jest.useFakeTimers() + // eslint-disable-next-line + require('../').waitForElement + + expect(window.setTimeout._isMockFunction).toEqual(true) +}) From 3aedde0b92a1421e42d302ed09c5a09aa1540bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brais=20Pi=C3=B1eiro?= Date: Fri, 30 Aug 2019 00:50:21 +0100 Subject: [PATCH 07/13] Create runWithRealTimers helper function --- src/helpers.js | 35 ++++++++++++++++----------- src/wait-for-dom-change.js | 5 +++- src/wait-for-element-to-be-removed.js | 5 +++- src/wait-for-element.js | 5 +++- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/helpers.js b/src/helpers.js index 4d2b3e36..999c4802 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -2,6 +2,22 @@ import MutationObserver from '@sheerun/mutationobserver-shim' const globalObj = typeof window === 'undefined' ? global : window +function runWithRealTimers(callback) { + const usingJestFakeTimers = globalObj.setTimeout._isMockFunction && !!jest + + if (usingJestFakeTimers) { + jest.useRealTimers() + } + + const callbackReturnValue = callback() + + if (usingJestFakeTimers) { + jest.useFakeTimers() + } + + return callbackReturnValue +} + // we only run our tests in node, and setImmediate is supported in node. // istanbul ignore next function setImmediatePolyfill(fn) { @@ -9,27 +25,17 @@ function setImmediatePolyfill(fn) { } function getTimeFunctions() { - const usingJestFakeTimers = globalObj.setTimeout._isMockFunction && !!jest - - if (usingJestFakeTimers) { - jest.useRealTimers() - } - - const timeFunctions = { + return { clearTimeoutFn: globalObj.clearTimeout, // istanbul ignore next setImmediateFn: globalObj.setImmediate || setImmediatePolyfill, setTimeoutFn: globalObj.setTimeout, } - - if (usingJestFakeTimers) { - jest.useFakeTimers() - } - - return timeFunctions } -const {clearTimeoutFn, setImmediateFn, setTimeoutFn} = getTimeFunctions() +const {clearTimeoutFn, setImmediateFn, setTimeoutFn} = runWithRealTimers( + getTimeFunctions, +) function newMutationObserver(onMutation) { const MutationObserverConstructor = @@ -55,4 +61,5 @@ export { clearTimeoutFn as clearTimeout, setImmediateFn as setImmediate, setTimeoutFn as setTimeout, + runWithRealTimers, } diff --git a/src/wait-for-dom-change.js b/src/wait-for-dom-change.js index 71472383..09b742bf 100644 --- a/src/wait-for-dom-change.js +++ b/src/wait-for-dom-change.js @@ -4,6 +4,7 @@ import { setImmediate, setTimeout, clearTimeout, + runWithRealTimers, } from './helpers' import {getConfig} from './config' @@ -20,7 +21,9 @@ function waitForDomChange({ return new Promise((resolve, reject) => { const timer = setTimeout(onTimeout, timeout) const observer = newMutationObserver(onMutation) - observer.observe(container, mutationObserverOptions) + runWithRealTimers(() => + observer.observe(container, mutationObserverOptions), + ) function onDone(error, result) { clearTimeout(timer) diff --git a/src/wait-for-element-to-be-removed.js b/src/wait-for-element-to-be-removed.js index 86e4b1fc..8bcbccb2 100644 --- a/src/wait-for-element-to-be-removed.js +++ b/src/wait-for-element-to-be-removed.js @@ -4,6 +4,7 @@ import { setImmediate, setTimeout, clearTimeout, + runWithRealTimers, } from './helpers' import {getConfig} from './config' @@ -43,7 +44,9 @@ function waitForElementToBeRemoved( ) } else { // Only observe for mutations only if there is element while checking synchronously - observer.observe(container, mutationObserverOptions) + runWithRealTimers(() => + observer.observe(container, mutationObserverOptions), + ) } } catch (error) { onDone(error) diff --git a/src/wait-for-element.js b/src/wait-for-element.js index 352f36ae..30fe0e9a 100644 --- a/src/wait-for-element.js +++ b/src/wait-for-element.js @@ -4,6 +4,7 @@ import { setImmediate, setTimeout, clearTimeout, + runWithRealTimers, } from './helpers' import {getConfig} from './config' @@ -31,7 +32,9 @@ function waitForElement( const timer = setTimeout(onTimeout, timeout) const observer = newMutationObserver(onMutation) - observer.observe(container, mutationObserverOptions) + runWithRealTimers(() => + observer.observe(container, mutationObserverOptions), + ) function onDone(error, result) { clearTimeout(timer) setImmediate(() => observer.disconnect()) From e87fdb792a963cd2c5c69fcfb53a5baa3604f621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brais=20Pi=C3=B1eiro?= Date: Fri, 30 Aug 2019 00:50:52 +0100 Subject: [PATCH 08/13] Modify tests to check that wait functions always use real timers --- src/__tests__/wait-for-dom-change.js | 51 ++++++++++---- .../wait-for-element-to-be-removed.js | 66 +++++++++++-------- src/__tests__/wait-for-element.js | 40 +++++++---- 3 files changed, 107 insertions(+), 50 deletions(-) diff --git a/src/__tests__/wait-for-dom-change.js b/src/__tests__/wait-for-dom-change.js index 83f3eff7..12e75ca7 100644 --- a/src/__tests__/wait-for-dom-change.js +++ b/src/__tests__/wait-for-dom-change.js @@ -1,6 +1,11 @@ import {waitForDomChange} from '../wait-for-dom-change' import {renderIntoDocument} from './helpers/test-utils' +function importModule() { + jest.resetModules() + return require('../').waitForDomChange +} + test('waits for the dom to change in the document', async () => { const {container} = renderIntoDocument('
') const promise = waitForDomChange() @@ -49,25 +54,47 @@ Array [ `) }) -test('uses real timers even if they were set to fake before importing the module', async () => { - jest.resetModules() - jest.useFakeTimers() - const importedWaitForDomChange = require('../').waitForDomChange +test('always uses real timers', async () => { + const expectElementToChange = async () => { + const importedWaitForDomChange = importModule() + const {container} = renderIntoDocument('
') - const {container} = renderIntoDocument('
') + setTimeout(() => container.firstChild.setAttribute('id', 'foo'), 100) - setTimeout(() => container.firstChild.setAttribute('id', 'foo'), 1000) + const promise = importedWaitForDomChange({container, timeout: 200}) - await expect(importedWaitForDomChange({timeout: 200})).rejects.toThrow( - /timed out/i, - ) + if (setTimeout._isMockFunction) { + jest.advanceTimersByTime(110) + } + + await expect(promise).resolves.toMatchInlineSnapshot(` + Array [ + Object { + "addedNodes": Array [], + "attributeName": "id", + "attributeNamespace": null, + "nextSibling": null, + "oldValue": null, + "previousSibling": null, + "removedNodes": Array [], + "target":
, + "type": "attributes", + }, + ] + `) + } + + jest.useFakeTimers() + await expectElementToChange() + jest.useRealTimers() + await expectElementToChange() }) test("doesn't change jest's timers value when importing the module", () => { - jest.resetModules() jest.useFakeTimers() - // eslint-disable-next-line - require('../').waitForDomChange + importModule() expect(window.setTimeout._isMockFunction).toEqual(true) }) diff --git a/src/__tests__/wait-for-element-to-be-removed.js b/src/__tests__/wait-for-element-to-be-removed.js index 829f741d..91dc08ed 100644 --- a/src/__tests__/wait-for-element-to-be-removed.js +++ b/src/__tests__/wait-for-element-to-be-removed.js @@ -1,6 +1,11 @@ import {waitForElementToBeRemoved} from '../' import {renderIntoDocument} from './helpers/test-utils' +function importModule() { + jest.resetModules() + return require('../').waitForElementToBeRemoved +} + test('resolves on mutation only when the element is removed', async () => { const {queryAllByTestId} = renderIntoDocument(`
@@ -58,38 +63,47 @@ test('requires an unempty array of elements to exist first', () => { ) }) -test('uses real timers even if they were set to fake before importing the module', async () => { - jest.resetModules() - jest.useFakeTimers() - const importedWaitForElementToBeRemoved = require('../') - .waitForElementToBeRemoved +test('always uses real timers', async () => { + const expectElementToBeRemoved = async () => { + const importedWaitForElementToBeRemoved = importModule() - const {queryAllByTestId} = renderIntoDocument(` -
-
- `) - const divs = queryAllByTestId('div') - // first mutation - setTimeout(() => { - divs.forEach(d => d.setAttribute('id', 'mutated')) - }) - // removal - setTimeout(() => { - divs.forEach(div => div.parentElement.removeChild(div)) - }, 1000) + const {queryAllByTestId} = renderIntoDocument(` +
+
+`) + const divs = queryAllByTestId('div') + // first mutation + setTimeout(() => { + divs.forEach(d => d.setAttribute('id', 'mutated')) + }) + // removal + setTimeout(() => { + divs.forEach(div => div.parentElement.removeChild(div)) + }, 100) + + const promise = importedWaitForElementToBeRemoved( + () => queryAllByTestId('div'), + { + timeout: 200, + }, + ) + + if (setTimeout._isMockFunction) { + jest.advanceTimersByTime(110) + } - await expect( - importedWaitForElementToBeRemoved(() => queryAllByTestId('div'), { - timeout: 200, - }), - ).rejects.toThrow(/timed out/i) + await promise + } + + jest.useFakeTimers() + await expectElementToBeRemoved() + jest.useRealTimers() + await expectElementToBeRemoved() }) test("doesn't change jest's timers value when importing the module", () => { - jest.resetModules() jest.useFakeTimers() - // eslint-disable-next-line - require('../').waitForElementToBeRemoved + importModule() expect(window.setTimeout._isMockFunction).toEqual(true) }) diff --git a/src/__tests__/wait-for-element.js b/src/__tests__/wait-for-element.js index 08df6255..c8dfb863 100644 --- a/src/__tests__/wait-for-element.js +++ b/src/__tests__/wait-for-element.js @@ -1,6 +1,11 @@ import {waitForElement} from '../wait-for-element' import {render, renderIntoDocument} from './helpers/test-utils' +function importModule() { + jest.resetModules() + return require('../').waitForElement +} + test('waits for element to appear in the document', async () => { const {rerender, getByTestId} = renderIntoDocument('
') const promise = waitForElement(() => getByTestId('div')) @@ -49,25 +54,36 @@ test('throws error if no callback is provided', async () => { await expect(waitForElement()).rejects.toThrow(/callback/i) }) -test('uses real timers even if they were set to fake before importing the module', async () => { - jest.resetModules() - jest.useFakeTimers() - const importedWaitForElement = require('../').waitForElement +test('always uses real timers', async () => { + const expectElementToExist = async () => { + const importedWaitForElement = importModule() - const {rerender, getByTestId} = renderIntoDocument('
') + const {rerender, getByTestId} = renderIntoDocument('
') + + setTimeout(() => rerender('
'), 100) - setTimeout(() => rerender('
'), 1000) + const promise = importedWaitForElement(() => getByTestId('div'), { + timeout: 200, + }) - await expect( - importedWaitForElement(() => getByTestId('div'), {timeout: 200}), - ).rejects.toThrow(/Unable to find/i) + if (setTimeout._isMockFunction) { + jest.advanceTimersByTime(110) + } + + const element = await promise + + await expect(element).toBeInTheDocument() + } + + jest.useFakeTimers() + await expectElementToExist() + jest.useRealTimers() + await expectElementToExist() }) test("doesn't change jest's timers value when importing the module", () => { - jest.resetModules() jest.useFakeTimers() - // eslint-disable-next-line - require('../').waitForElement + importModule() expect(window.setTimeout._isMockFunction).toEqual(true) }) From 264c143f9cd8ff4c17177ecee0f7204bf5007279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brais=20Pi=C3=B1eiro?= Date: Fri, 30 Aug 2019 09:46:56 +0100 Subject: [PATCH 09/13] Move istanbul ignore comment to the top of return block --- src/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers.js b/src/helpers.js index 999c4802..1a4a9354 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -25,9 +25,9 @@ function setImmediatePolyfill(fn) { } function getTimeFunctions() { + // istanbul ignore next return { clearTimeoutFn: globalObj.clearTimeout, - // istanbul ignore next setImmediateFn: globalObj.setImmediate || setImmediatePolyfill, setTimeoutFn: globalObj.setTimeout, } From e73f1815090972507a83f038b73e5044a74e7c38 Mon Sep 17 00:00:00 2001 From: brapifra Date: Fri, 30 Aug 2019 17:17:34 +0100 Subject: [PATCH 10/13] Make sure tests are isolated --- src/__tests__/wait-for-dom-change.js | 10 ++++++++-- src/__tests__/wait-for-element-to-be-removed.js | 10 ++++++++-- src/__tests__/wait-for-element.js | 10 ++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/__tests__/wait-for-dom-change.js b/src/__tests__/wait-for-dom-change.js index 12e75ca7..eab196b8 100644 --- a/src/__tests__/wait-for-dom-change.js +++ b/src/__tests__/wait-for-dom-change.js @@ -1,11 +1,17 @@ -import {waitForDomChange} from '../wait-for-dom-change' import {renderIntoDocument} from './helpers/test-utils' function importModule() { - jest.resetModules() return require('../').waitForDomChange } +let waitForDomChange + +beforeEach(() => { + jest.useRealTimers() + jest.resetModules() + waitForDomChange = importModule() +}) + test('waits for the dom to change in the document', async () => { const {container} = renderIntoDocument('
') const promise = waitForDomChange() diff --git a/src/__tests__/wait-for-element-to-be-removed.js b/src/__tests__/wait-for-element-to-be-removed.js index 91dc08ed..fd978a4b 100644 --- a/src/__tests__/wait-for-element-to-be-removed.js +++ b/src/__tests__/wait-for-element-to-be-removed.js @@ -1,11 +1,17 @@ -import {waitForElementToBeRemoved} from '../' import {renderIntoDocument} from './helpers/test-utils' function importModule() { - jest.resetModules() return require('../').waitForElementToBeRemoved } +let waitForElementToBeRemoved + +beforeEach(() => { + jest.useRealTimers() + jest.resetModules() + waitForElementToBeRemoved = importModule() +}) + test('resolves on mutation only when the element is removed', async () => { const {queryAllByTestId} = renderIntoDocument(`
diff --git a/src/__tests__/wait-for-element.js b/src/__tests__/wait-for-element.js index c8dfb863..470268b3 100644 --- a/src/__tests__/wait-for-element.js +++ b/src/__tests__/wait-for-element.js @@ -1,11 +1,17 @@ -import {waitForElement} from '../wait-for-element' import {render, renderIntoDocument} from './helpers/test-utils' function importModule() { - jest.resetModules() return require('../').waitForElement } +let waitForElement + +beforeEach(() => { + jest.useRealTimers() + jest.resetModules() + waitForElement = importModule() +}) + test('waits for element to appear in the document', async () => { const {rerender, getByTestId} = renderIntoDocument('
') const promise = waitForElement(() => getByTestId('div')) From 27c27efd4444d9b7f750d67b31c6f877c291a8df Mon Sep 17 00:00:00 2001 From: brapifra Date: Fri, 30 Aug 2019 17:25:44 +0100 Subject: [PATCH 11/13] Refactor timers tests --- src/__tests__/wait-for-dom-change.js | 14 ++++++++----- .../wait-for-element-to-be-removed.js | 14 ++++++++----- src/__tests__/wait-for-element.js | 21 ++++++++----------- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/__tests__/wait-for-dom-change.js b/src/__tests__/wait-for-dom-change.js index eab196b8..730a4c20 100644 --- a/src/__tests__/wait-for-dom-change.js +++ b/src/__tests__/wait-for-dom-change.js @@ -60,7 +60,7 @@ Array [ `) }) -test('always uses real timers', async () => { +describe('timers', () => { const expectElementToChange = async () => { const importedWaitForDomChange = importModule() const {container} = renderIntoDocument('
') @@ -92,10 +92,14 @@ test('always uses real timers', async () => { `) } - jest.useFakeTimers() - await expectElementToChange() - jest.useRealTimers() - await expectElementToChange() + it('works with real timers', async () => { + jest.useRealTimers() + await expectElementToChange() + }) + it('works with fake timers', async () => { + jest.useFakeTimers() + await expectElementToChange() + }) }) test("doesn't change jest's timers value when importing the module", () => { diff --git a/src/__tests__/wait-for-element-to-be-removed.js b/src/__tests__/wait-for-element-to-be-removed.js index fd978a4b..e52dfd21 100644 --- a/src/__tests__/wait-for-element-to-be-removed.js +++ b/src/__tests__/wait-for-element-to-be-removed.js @@ -69,7 +69,7 @@ test('requires an unempty array of elements to exist first', () => { ) }) -test('always uses real timers', async () => { +describe('timers', () => { const expectElementToBeRemoved = async () => { const importedWaitForElementToBeRemoved = importModule() @@ -101,10 +101,14 @@ test('always uses real timers', async () => { await promise } - jest.useFakeTimers() - await expectElementToBeRemoved() - jest.useRealTimers() - await expectElementToBeRemoved() + it('works with real timers', async () => { + jest.useRealTimers() + await expectElementToBeRemoved() + }) + it('works with fake timers', async () => { + jest.useFakeTimers() + await expectElementToBeRemoved() + }) }) test("doesn't change jest's timers value when importing the module", () => { diff --git a/src/__tests__/wait-for-element.js b/src/__tests__/wait-for-element.js index 470268b3..3dc29687 100644 --- a/src/__tests__/wait-for-element.js +++ b/src/__tests__/wait-for-element.js @@ -60,7 +60,7 @@ test('throws error if no callback is provided', async () => { await expect(waitForElement()).rejects.toThrow(/callback/i) }) -test('always uses real timers', async () => { +describe('timers', () => { const expectElementToExist = async () => { const importedWaitForElement = importModule() @@ -81,15 +81,12 @@ test('always uses real timers', async () => { await expect(element).toBeInTheDocument() } - jest.useFakeTimers() - await expectElementToExist() - jest.useRealTimers() - await expectElementToExist() -}) - -test("doesn't change jest's timers value when importing the module", () => { - jest.useFakeTimers() - importModule() - - expect(window.setTimeout._isMockFunction).toEqual(true) + it('works with real timers', async () => { + jest.useRealTimers() + await expectElementToExist() + }) + it('works with fake timers', async () => { + jest.useFakeTimers() + await expectElementToExist() + }) }) From 068230da55a562ea17e8b412715a5d2f18730e02 Mon Sep 17 00:00:00 2001 From: brapifra Date: Fri, 30 Aug 2019 17:27:59 +0100 Subject: [PATCH 12/13] Check that jest is not undefined --- src/helpers.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/helpers.js b/src/helpers.js index 1a4a9354..84f8ae0c 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -3,7 +3,8 @@ import MutationObserver from '@sheerun/mutationobserver-shim' const globalObj = typeof window === 'undefined' ? global : window function runWithRealTimers(callback) { - const usingJestFakeTimers = globalObj.setTimeout._isMockFunction && !!jest + const usingJestFakeTimers = + globalObj.setTimeout._isMockFunction && typeof jest !== 'undefined' if (usingJestFakeTimers) { jest.useRealTimers() From ee3ef66be0ccb42e7ad31554d104b10586facc50 Mon Sep 17 00:00:00 2001 From: brapifra Date: Fri, 30 Aug 2019 17:31:14 +0100 Subject: [PATCH 13/13] runWithRealTimers: Add comment about jest support --- src/helpers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/helpers.js b/src/helpers.js index 84f8ae0c..1640892a 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -2,6 +2,7 @@ import MutationObserver from '@sheerun/mutationobserver-shim' const globalObj = typeof window === 'undefined' ? global : window +// Currently this fn only supports jest timers, but it could support other test runners in the future. function runWithRealTimers(callback) { const usingJestFakeTimers = globalObj.setTimeout._isMockFunction && typeof jest !== 'undefined'