diff --git a/src/__tests__/fake-timers.js b/src/__tests__/fake-timers.js index 5f08c1b7..af2a09ac 100644 --- a/src/__tests__/fake-timers.js +++ b/src/__tests__/fake-timers.js @@ -1,14 +1,6 @@ import {waitFor, waitForElementToBeRemoved} from '..' import {render} from './helpers/test-utils' -beforeAll(() => { - jest.useFakeTimers() -}) - -afterAll(() => { - jest.useRealTimers() -}) - async function runWaitFor({time = 300} = {}, options) { const response = 'data' const doAsyncThing = () => @@ -48,6 +40,7 @@ test('fake timer timeout', async () => { }) test('times out after 1000ms by default', async () => { + jest.useFakeTimers() const {container} = render(`
`) const start = performance.now() // there's a bug with this rule here... @@ -66,6 +59,7 @@ test('times out after 1000ms by default', async () => { }) test('recursive timers do not cause issues', async () => { + jest.useFakeTimers() let recurse = true function startTimer() { setTimeout(() => { diff --git a/src/__tests__/wait-for.js b/src/__tests__/wait-for.js index b015090e..7235f6a5 100644 --- a/src/__tests__/wait-for.js +++ b/src/__tests__/wait-for.js @@ -180,10 +180,10 @@ test('when a promise is returned, it does not call the callback again until that test('when a promise is returned, if that is not resolved within the timeout, then waitFor is rejected', async () => { const sleep = t => new Promise(r => setTimeout(r, t)) const {promise} = deferred() - const waitForPromise = waitFor(() => promise, {timeout: 1}).catch(e => e) + const waitForError = waitFor(() => promise, {timeout: 1}).catch(e => e) await sleep(5) - expect((await waitForPromise).message).toMatchInlineSnapshot(` + expect((await waitForError).message).toMatchInlineSnapshot(` "Timed out in waitFor. @@ -192,3 +192,64 @@ test('when a promise is returned, if that is not resolved within the timeout, th " `) }) + +test('if you switch from fake timers to real timers during the wait period you get an error', async () => { + jest.useFakeTimers() + const waitForError = waitFor(() => { + throw new Error('this error message does not matter...') + }).catch(e => e) + + // this is the problem... + jest.useRealTimers() + + const error = await waitForError + + expect(error.message).toMatchInlineSnapshot( + `"Changed from using fake timers to real timers while using waitFor. This is not allowed and will result in very strange behavior. Please ensure you're awaiting all async things your test is doing before changing to real timers. For more info, please go to https://github.com/testing-library/dom-testing-library/issues/830"`, + ) + // stack trace has this file in it + expect(error.stack).toMatch(__dirname) +}) + +test('if you switch from real timers to fake timers during the wait period you get an error', async () => { + const waitForError = waitFor(() => { + throw new Error('this error message does not matter...') + }).catch(e => e) + + // this is the problem... + jest.useFakeTimers() + const error = await waitForError + + expect(error.message).toMatchInlineSnapshot( + `"Changed from using real timers to fake timers while using waitFor. This is not allowed and will result in very strange behavior. Please ensure you're awaiting all async things your test is doing before changing to fake timers. For more info, please go to https://github.com/testing-library/dom-testing-library/issues/830"`, + ) + // stack trace has this file in it + expect(error.stack).toMatch(__dirname) +}) + +test('the fake timers => real timers error shows the original stack trace when configured to do so', async () => { + jest.useFakeTimers() + const waitForError = waitFor( + () => { + throw new Error('this error message does not matter...') + }, + {showOriginalStackTrace: true}, + ).catch(e => e) + + jest.useRealTimers() + + expect((await waitForError).stack).not.toMatch(__dirname) +}) + +test('the real timers => fake timers error shows the original stack trace when configured to do so', async () => { + const waitForError = waitFor( + () => { + throw new Error('this error message does not matter...') + }, + {showOriginalStackTrace: true}, + ).catch(e => e) + + jest.useFakeTimers() + + expect((await waitForError).stack).not.toMatch(__dirname) +}) diff --git a/src/wait-for.js b/src/wait-for.js index 465542f9..a1a49f5b 100644 --- a/src/wait-for.js +++ b/src/wait-for.js @@ -60,6 +60,14 @@ function waitFor( // waiting or when we've timed out. // eslint-disable-next-line no-unmodified-loop-condition while (!finished) { + if (!jestFakeTimersAreEnabled()) { + const error = new Error( + `Changed from using fake timers to real timers while using waitFor. This is not allowed and will result in very strange behavior. Please ensure you're awaiting all async things your test is doing before changing to real timers. For more info, please go to https://github.com/testing-library/dom-testing-library/issues/830`, + ) + if (!showOriginalStackTrace) copyStackTrace(error, stackTraceError) + reject(error) + return + } // we *could* (maybe should?) use `advanceTimersToNextTimer` but it's // possible that could make this loop go on forever if someone is using // third party code that's setting up recursive timers so rapidly that @@ -81,9 +89,9 @@ function waitFor( await new Promise(r => setImmediate(r)) } } else { - intervalId = setInterval(checkCallback, interval) + intervalId = setInterval(checkRealTimersCallback, interval) const {MutationObserver} = getWindowFromNode(container) - observer = new MutationObserver(checkCallback) + observer = new MutationObserver(checkRealTimersCallback) observer.observe(container, mutationObserverOptions) checkCallback() } @@ -104,6 +112,18 @@ function waitFor( } } + function checkRealTimersCallback() { + if (jestFakeTimersAreEnabled()) { + const error = new Error( + `Changed from using real timers to fake timers while using waitFor. This is not allowed and will result in very strange behavior. Please ensure you're awaiting all async things your test is doing before changing to fake timers. For more info, please go to https://github.com/testing-library/dom-testing-library/issues/830`, + ) + if (!showOriginalStackTrace) copyStackTrace(error, stackTraceError) + return reject(error) + } else { + return checkCallback() + } + } + function checkCallback() { if (promiseStatus === 'pending') return try { @@ -177,3 +197,8 @@ function wait(...args) { } export {waitForWrapper as waitFor, wait} + +/* +eslint + max-lines-per-function: ["error", {"max": 200}], +*/ diff --git a/tests/setup-env.js b/tests/setup-env.js index 64906811..0a6d0af0 100644 --- a/tests/setup-env.js +++ b/tests/setup-env.js @@ -36,6 +36,12 @@ beforeAll(() => { }) }) +afterEach(() => { + if (jest.isMockFunction(global.setTimeout)) { + jest.useRealTimers() + } +}) + afterAll(() => { jest.restoreAllMocks() })