UNPKG

6.73 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.waitFor = waitForWrapper;
7var _helpers = require("./helpers");
8var _config = require("./config");
9// This is so the stack trace the developer sees is one that's
10// closer to their code (because async stack traces are hard to follow).
11function copyStackTrace(target, source) {
12 target.stack = source.stack.replace(source.message, target.message);
13}
14function waitFor(callback, {
15 container = (0, _helpers.getDocument)(),
16 timeout = (0, _config.getConfig)().asyncUtilTimeout,
17 showOriginalStackTrace = (0, _config.getConfig)().showOriginalStackTrace,
18 stackTraceError,
19 interval = 50,
20 onTimeout = error => {
21 Object.defineProperty(error, 'message', {
22 value: (0, _config.getConfig)().getElementError(error.message, container).message
23 });
24 return error;
25 },
26 mutationObserverOptions = {
27 subtree: true,
28 childList: true,
29 attributes: true,
30 characterData: true
31 }
32}) {
33 if (typeof callback !== 'function') {
34 throw new TypeError('Received `callback` arg must be a function');
35 }
36 return new Promise(async (resolve, reject) => {
37 let lastError, intervalId, observer;
38 let finished = false;
39 let promiseStatus = 'idle';
40 const overallTimeoutTimer = setTimeout(handleTimeout, timeout);
41 const usingJestFakeTimers = (0, _helpers.jestFakeTimersAreEnabled)();
42 if (usingJestFakeTimers) {
43 const {
44 unstable_advanceTimersWrapper: advanceTimersWrapper
45 } = (0, _config.getConfig)();
46 checkCallback();
47 // this is a dangerous rule to disable because it could lead to an
48 // infinite loop. However, eslint isn't smart enough to know that we're
49 // setting finished inside `onDone` which will be called when we're done
50 // waiting or when we've timed out.
51 // eslint-disable-next-line no-unmodified-loop-condition
52 while (!finished) {
53 if (!(0, _helpers.jestFakeTimersAreEnabled)()) {
54 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`);
55 if (!showOriginalStackTrace) copyStackTrace(error, stackTraceError);
56 reject(error);
57 return;
58 }
59
60 // In this rare case, we *need* to wait for in-flight promises
61 // to resolve before continuing. We don't need to take advantage
62 // of parallelization so we're fine.
63 // https://stackoverflow.com/a/59243586/971592
64 // eslint-disable-next-line no-await-in-loop
65 await advanceTimersWrapper(async () => {
66 // we *could* (maybe should?) use `advanceTimersToNextTimer` but it's
67 // possible that could make this loop go on forever if someone is using
68 // third party code that's setting up recursive timers so rapidly that
69 // the user's timer's don't get a chance to resolve. So we'll advance
70 // by an interval instead. (We have a test for this case).
71 jest.advanceTimersByTime(interval);
72 });
73
74 // Could have timed-out
75 if (finished) {
76 break;
77 }
78 // It's really important that checkCallback is run *before* we flush
79 // in-flight promises. To be honest, I'm not sure why, and I can't quite
80 // think of a way to reproduce the problem in a test, but I spent
81 // an entire day banging my head against a wall on this.
82 checkCallback();
83 }
84 } else {
85 try {
86 (0, _helpers.checkContainerType)(container);
87 } catch (e) {
88 reject(e);
89 return;
90 }
91 intervalId = setInterval(checkRealTimersCallback, interval);
92 const {
93 MutationObserver
94 } = (0, _helpers.getWindowFromNode)(container);
95 observer = new MutationObserver(checkRealTimersCallback);
96 observer.observe(container, mutationObserverOptions);
97 checkCallback();
98 }
99 function onDone(error, result) {
100 finished = true;
101 clearTimeout(overallTimeoutTimer);
102 if (!usingJestFakeTimers) {
103 clearInterval(intervalId);
104 observer.disconnect();
105 }
106 if (error) {
107 reject(error);
108 } else {
109 resolve(result);
110 }
111 }
112 function checkRealTimersCallback() {
113 if ((0, _helpers.jestFakeTimersAreEnabled)()) {
114 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`);
115 if (!showOriginalStackTrace) copyStackTrace(error, stackTraceError);
116 return reject(error);
117 } else {
118 return checkCallback();
119 }
120 }
121 function checkCallback() {
122 if (promiseStatus === 'pending') return;
123 try {
124 const result = (0, _config.runWithExpensiveErrorDiagnosticsDisabled)(callback);
125 if (typeof result?.then === 'function') {
126 promiseStatus = 'pending';
127 result.then(resolvedValue => {
128 promiseStatus = 'resolved';
129 onDone(null, resolvedValue);
130 }, rejectedValue => {
131 promiseStatus = 'rejected';
132 lastError = rejectedValue;
133 });
134 } else {
135 onDone(null, result);
136 }
137 // If `callback` throws, wait for the next mutation, interval, or timeout.
138 } catch (error) {
139 // Save the most recent callback error to reject the promise with it in the event of a timeout
140 lastError = error;
141 }
142 }
143 function handleTimeout() {
144 let error;
145 if (lastError) {
146 error = lastError;
147 if (!showOriginalStackTrace && error.name === 'TestingLibraryElementError') {
148 copyStackTrace(error, stackTraceError);
149 }
150 } else {
151 error = new Error('Timed out in waitFor.');
152 if (!showOriginalStackTrace) {
153 copyStackTrace(error, stackTraceError);
154 }
155 }
156 onDone(onTimeout(error), null);
157 }
158 });
159}
160function waitForWrapper(callback, options) {
161 // create the error here so its stack trace is as close to the
162 // calling code as possible
163 const stackTraceError = new Error('STACK_TRACE_MESSAGE');
164 return (0, _config.getConfig)().asyncWrapper(() => waitFor(callback, {
165 stackTraceError,
166 ...options
167 }));
168}
169
170/*
171eslint
172 max-lines-per-function: ["error", {"max": 200}],
173*/
\No newline at end of file