diff --git a/src/__tests__/wait-for-element.js b/src/__tests__/wait-for-element.js index 6c655855..1feaa619 100644 --- a/src/__tests__/wait-for-element.js +++ b/src/__tests__/wait-for-element.js @@ -312,3 +312,48 @@ test('it returns immediately if the callback returns the value before any mutati return promise }) + +test('does not get into infinite setTimeout loop after MutationObserver notification', async () => { + const {container} = render(`
`) + + let didMakeMutation = false + const callback = jest.fn(() => didMakeMutation).mockName('callback') + const successHandler = jest.fn().mockName('successHandler') + const errorHandler = jest.fn().mockName('errorHandler') + jest.useFakeTimers() + + const promise = waitForElement(callback, { + container, + timeout: 70, + mutationObserverOptions: {attributes: true}, + }).then(successHandler, errorHandler) + + // Expect 2 timeouts to be scheduled: + // - waitForElement timeout + // - MutationObserver timeout + expect(setTimeout).toHaveBeenCalledTimes(2) + + // One synchronous `callback` call is expected. + expect(callback).toHaveBeenCalledTimes(1) + expect(successHandler).toHaveBeenCalledTimes(0) + expect(errorHandler).toHaveBeenCalledTimes(0) + + // Make a mutation so MutationObserver calls out callback + container.setAttribute('data-test-attribute', 'something changed') + didMakeMutation = true + + // Advance timer to expire MutationObserver timeout + jest.advanceTimersByTime(50) + jest.runAllImmediates() + await promise + expect(setTimeout).toHaveBeenCalledTimes(3) + + // Expect callback and successHandler to be called + expect(callback).toHaveBeenCalledTimes(2) + expect(successHandler).toHaveBeenCalledTimes(1) + expect(errorHandler).toHaveBeenCalledTimes(0) + + // Expect no more setTimeout calls + jest.advanceTimersByTime(100) + expect(setTimeout).toHaveBeenCalledTimes(3) +}) diff --git a/src/wait-for-element.js b/src/wait-for-element.js index 0ae3ba41..1bed5511 100644 --- a/src/wait-for-element.js +++ b/src/wait-for-element.js @@ -18,7 +18,7 @@ function waitForElement( let lastError, observer, timer // eslint-disable-line prefer-const function onDone(error, result) { clearTimeout(timer) - observer.disconnect() + setImmediate(() => observer.disconnect()) if (error) { reject(error) } else {