diff --git a/src/__tests__/wait-for-dom-change.js b/src/__tests__/wait-for-dom-change.js index 39487672..730a4c20 100644 --- a/src/__tests__/wait-for-dom-change.js +++ b/src/__tests__/wait-for-dom-change.js @@ -1,6 +1,17 @@ -import {waitForDomChange} from '../wait-for-dom-change' import {renderIntoDocument} from './helpers/test-utils' +function importModule() { + 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() @@ -48,3 +59,52 @@ Array [ ] `) }) + +describe('timers', () => { + const expectElementToChange = async () => { + const importedWaitForDomChange = importModule() + const {container} = renderIntoDocument('') + + setTimeout(() => container.firstChild.setAttribute('id', 'foo'), 100) + + const promise = importedWaitForDomChange({container, timeout: 200}) + + 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", + }, + ] + `) + } + + 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", () => { + jest.useFakeTimers() + 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 e541d86f..e52dfd21 100644 --- a/src/__tests__/wait-for-element-to-be-removed.js +++ b/src/__tests__/wait-for-element-to-be-removed.js @@ -1,6 +1,17 @@ -import {waitForElementToBeRemoved} from '../' import {renderIntoDocument} from './helpers/test-utils' +function importModule() { + 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(` @@ -57,3 +68,52 @@ 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."`, ) }) + +describe('timers', () => { + 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)) + }, 100) + + const promise = importedWaitForElementToBeRemoved( + () => queryAllByTestId('div'), + { + timeout: 200, + }, + ) + + if (setTimeout._isMockFunction) { + jest.advanceTimersByTime(110) + } + + await promise + } + + 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", () => { + jest.useFakeTimers() + importModule() + + expect(window.setTimeout._isMockFunction).toEqual(true) +}) diff --git a/src/__tests__/wait-for-element.js b/src/__tests__/wait-for-element.js index 2fba0cb0..3dc29687 100644 --- a/src/__tests__/wait-for-element.js +++ b/src/__tests__/wait-for-element.js @@ -1,6 +1,17 @@ -import {waitForElement} from '../wait-for-element' import {render, renderIntoDocument} from './helpers/test-utils' +function importModule() { + 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')) @@ -48,3 +59,34 @@ 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) }) + +describe('timers', () => { + const expectElementToExist = async () => { + const importedWaitForElement = importModule() + + const {rerender, getByTestId} = renderIntoDocument('') + + setTimeout(() => rerender(''), 100) + + const promise = importedWaitForElement(() => getByTestId('div'), { + timeout: 200, + }) + + if (setTimeout._isMockFunction) { + jest.advanceTimersByTime(110) + } + + const element = await promise + + await expect(element).toBeInTheDocument() + } + + it('works with real timers', async () => { + jest.useRealTimers() + await expectElementToExist() + }) + it('works with fake timers', async () => { + jest.useFakeTimers() + await expectElementToExist() + }) +}) diff --git a/src/helpers.js b/src/helpers.js index ab43e72b..1640892a 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -2,16 +2,42 @@ 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' + + 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) { return globalObj.setTimeout(fn, 0) } -const clearTimeoutFn = globalObj.clearTimeout -// istanbul ignore next -const setImmediateFn = globalObj.setImmediate || setImmediatePolyfill -const setTimeoutFn = globalObj.setTimeout +function getTimeFunctions() { + // istanbul ignore next + return { + clearTimeoutFn: globalObj.clearTimeout, + setImmediateFn: globalObj.setImmediate || setImmediatePolyfill, + setTimeoutFn: globalObj.setTimeout, + } +} + +const {clearTimeoutFn, setImmediateFn, setTimeoutFn} = runWithRealTimers( + getTimeFunctions, +) function newMutationObserver(onMutation) { const MutationObserverConstructor = @@ -37,4 +63,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())