1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  */
    
  7. 
    
  8. import * as SchedulerMock from 'scheduler/unstable_mock';
    
  9. import {diff} from 'jest-diff';
    
  10. import {equals} from '@jest/expect-utils';
    
  11. import enqueueTask from './enqueueTask';
    
  12. 
    
  13. export {act} from './internalAct';
    
  14. 
    
  15. function assertYieldsWereCleared(caller) {
    
  16.   const actualYields = SchedulerMock.unstable_clearLog();
    
  17.   if (actualYields.length !== 0) {
    
  18.     const error = Error(
    
  19.       'The event log is not empty. Call assertLog(...) first.',
    
  20.     );
    
  21.     Error.captureStackTrace(error, caller);
    
  22.     throw error;
    
  23.   }
    
  24. }
    
  25. 
    
  26. export async function waitForMicrotasks() {
    
  27.   return new Promise(resolve => {
    
  28.     enqueueTask(() => resolve());
    
  29.   });
    
  30. }
    
  31. 
    
  32. export async function waitFor(expectedLog, options) {
    
  33.   assertYieldsWereCleared(waitFor);
    
  34. 
    
  35.   // Create the error object before doing any async work, to get a better
    
  36.   // stack trace.
    
  37.   const error = new Error();
    
  38.   Error.captureStackTrace(error, waitFor);
    
  39. 
    
  40.   const stopAfter = expectedLog.length;
    
  41.   const actualLog = [];
    
  42.   do {
    
  43.     // Wait until end of current task/microtask.
    
  44.     await waitForMicrotasks();
    
  45.     if (SchedulerMock.unstable_hasPendingWork()) {
    
  46.       SchedulerMock.unstable_flushNumberOfYields(stopAfter - actualLog.length);
    
  47.       actualLog.push(...SchedulerMock.unstable_clearLog());
    
  48.       if (stopAfter > actualLog.length) {
    
  49.         // Continue flushing until we've logged the expected number of items.
    
  50.       } else {
    
  51.         // Once we've reached the expected sequence, wait one more microtask to
    
  52.         // flush any remaining synchronous work.
    
  53.         await waitForMicrotasks();
    
  54.         actualLog.push(...SchedulerMock.unstable_clearLog());
    
  55.         break;
    
  56.       }
    
  57.     } else {
    
  58.       // There's no pending work, even after a microtask.
    
  59.       break;
    
  60.     }
    
  61.   } while (true);
    
  62. 
    
  63.   if (options && options.additionalLogsAfterAttemptingToYield) {
    
  64.     expectedLog = expectedLog.concat(
    
  65.       options.additionalLogsAfterAttemptingToYield,
    
  66.     );
    
  67.   }
    
  68. 
    
  69.   if (equals(actualLog, expectedLog)) {
    
  70.     return;
    
  71.   }
    
  72. 
    
  73.   error.message = `
    
  74. Expected sequence of events did not occur.
    
  75. 
    
  76. ${diff(expectedLog, actualLog)}
    
  77. `;
    
  78.   throw error;
    
  79. }
    
  80. 
    
  81. export async function waitForAll(expectedLog) {
    
  82.   assertYieldsWereCleared(waitForAll);
    
  83. 
    
  84.   // Create the error object before doing any async work, to get a better
    
  85.   // stack trace.
    
  86.   const error = new Error();
    
  87.   Error.captureStackTrace(error, waitForAll);
    
  88. 
    
  89.   do {
    
  90.     // Wait until end of current task/microtask.
    
  91.     await waitForMicrotasks();
    
  92.     if (!SchedulerMock.unstable_hasPendingWork()) {
    
  93.       // There's no pending work, even after a microtask. Stop flushing.
    
  94.       break;
    
  95.     }
    
  96.     SchedulerMock.unstable_flushAllWithoutAsserting();
    
  97.   } while (true);
    
  98. 
    
  99.   const actualLog = SchedulerMock.unstable_clearLog();
    
  100.   if (equals(actualLog, expectedLog)) {
    
  101.     return;
    
  102.   }
    
  103. 
    
  104.   error.message = `
    
  105. Expected sequence of events did not occur.
    
  106. 
    
  107. ${diff(expectedLog, actualLog)}
    
  108. `;
    
  109.   throw error;
    
  110. }
    
  111. 
    
  112. export async function waitForThrow(expectedError: mixed): mixed {
    
  113.   assertYieldsWereCleared(waitForThrow);
    
  114. 
    
  115.   // Create the error object before doing any async work, to get a better
    
  116.   // stack trace.
    
  117.   const error = new Error();
    
  118.   Error.captureStackTrace(error, waitForThrow);
    
  119. 
    
  120.   do {
    
  121.     // Wait until end of current task/microtask.
    
  122.     await waitForMicrotasks();
    
  123.     if (!SchedulerMock.unstable_hasPendingWork()) {
    
  124.       // There's no pending work, even after a microtask. Stop flushing.
    
  125.       error.message = 'Expected something to throw, but nothing did.';
    
  126.       throw error;
    
  127.     }
    
  128.     try {
    
  129.       SchedulerMock.unstable_flushAllWithoutAsserting();
    
  130.     } catch (x) {
    
  131.       if (expectedError === undefined) {
    
  132.         // If no expected error was provided, then assume the caller is OK with
    
  133.         // any error being thrown. We're returning the error so they can do
    
  134.         // their own checks, if they wish.
    
  135.         return x;
    
  136.       }
    
  137.       if (equals(x, expectedError)) {
    
  138.         return x;
    
  139.       }
    
  140.       if (
    
  141.         typeof expectedError === 'string' &&
    
  142.         typeof x === 'object' &&
    
  143.         x !== null &&
    
  144.         typeof x.message === 'string'
    
  145.       ) {
    
  146.         if (x.message.includes(expectedError)) {
    
  147.           return x;
    
  148.         } else {
    
  149.           error.message = `
    
  150. Expected error was not thrown.
    
  151. 
    
  152. ${diff(expectedError, x.message)}
    
  153. `;
    
  154.           throw error;
    
  155.         }
    
  156.       }
    
  157.       error.message = `
    
  158. Expected error was not thrown.
    
  159. 
    
  160. ${diff(expectedError, x)}
    
  161. `;
    
  162.       throw error;
    
  163.     }
    
  164.   } while (true);
    
  165. }
    
  166. 
    
  167. // This is prefixed with `unstable_` because you should almost always try to
    
  168. // avoid using it in tests. It's really only for testing a particular
    
  169. // implementation detail (update starvation prevention).
    
  170. export async function unstable_waitForExpired(expectedLog): mixed {
    
  171.   assertYieldsWereCleared(unstable_waitForExpired);
    
  172. 
    
  173.   // Create the error object before doing any async work, to get a better
    
  174.   // stack trace.
    
  175.   const error = new Error();
    
  176.   Error.captureStackTrace(error, unstable_waitForExpired);
    
  177. 
    
  178.   // Wait until end of current task/microtask.
    
  179.   await waitForMicrotasks();
    
  180.   SchedulerMock.unstable_flushExpired();
    
  181. 
    
  182.   const actualLog = SchedulerMock.unstable_clearLog();
    
  183.   if (equals(actualLog, expectedLog)) {
    
  184.     return;
    
  185.   }
    
  186. 
    
  187.   error.message = `
    
  188. Expected sequence of events did not occur.
    
  189. 
    
  190. ${diff(expectedLog, actualLog)}
    
  191. `;
    
  192.   throw error;
    
  193. }
    
  194. 
    
  195. // TODO: This name is a bit misleading currently because it will stop as soon as
    
  196. // React yields for any reason, not just for a paint. I've left it this way for
    
  197. // now because that's how untable_flushUntilNextPaint already worked, but maybe
    
  198. // we should split these use cases into separate APIs.
    
  199. export async function waitForPaint(expectedLog) {
    
  200.   assertYieldsWereCleared(waitForPaint);
    
  201. 
    
  202.   // Create the error object before doing any async work, to get a better
    
  203.   // stack trace.
    
  204.   const error = new Error();
    
  205.   Error.captureStackTrace(error, waitForPaint);
    
  206. 
    
  207.   // Wait until end of current task/microtask.
    
  208.   await waitForMicrotasks();
    
  209.   if (SchedulerMock.unstable_hasPendingWork()) {
    
  210.     // Flush until React yields.
    
  211.     SchedulerMock.unstable_flushUntilNextPaint();
    
  212.     // Wait one more microtask to flush any remaining synchronous work.
    
  213.     await waitForMicrotasks();
    
  214.   }
    
  215. 
    
  216.   const actualLog = SchedulerMock.unstable_clearLog();
    
  217.   if (equals(actualLog, expectedLog)) {
    
  218.     return;
    
  219.   }
    
  220. 
    
  221.   error.message = `
    
  222. Expected sequence of events did not occur.
    
  223. 
    
  224. ${diff(expectedLog, actualLog)}
    
  225. `;
    
  226.   throw error;
    
  227. }
    
  228. 
    
  229. export async function waitForDiscrete(expectedLog) {
    
  230.   assertYieldsWereCleared(waitForDiscrete);
    
  231. 
    
  232.   // Create the error object before doing any async work, to get a better
    
  233.   // stack trace.
    
  234.   const error = new Error();
    
  235.   Error.captureStackTrace(error, waitForDiscrete);
    
  236. 
    
  237.   // Wait until end of current task/microtask.
    
  238.   await waitForMicrotasks();
    
  239. 
    
  240.   const actualLog = SchedulerMock.unstable_clearLog();
    
  241.   if (equals(actualLog, expectedLog)) {
    
  242.     return;
    
  243.   }
    
  244. 
    
  245.   error.message = `
    
  246. Expected sequence of events did not occur.
    
  247. 
    
  248. ${diff(expectedLog, actualLog)}
    
  249. `;
    
  250.   throw error;
    
  251. }
    
  252. 
    
  253. export function assertLog(expectedLog) {
    
  254.   const actualLog = SchedulerMock.unstable_clearLog();
    
  255.   if (equals(actualLog, expectedLog)) {
    
  256.     return;
    
  257.   }
    
  258. 
    
  259.   const error = new Error(`
    
  260. Expected sequence of events did not occur.
    
  261. 
    
  262. ${diff(expectedLog, actualLog)}
    
  263. `);
    
  264.   Error.captureStackTrace(error, assertLog);
    
  265.   throw error;
    
  266. }