'use strict'; const { getTestFlags } = './TestFlags' |> require(%); const { flushAllUnexpectedConsoleCalls, resetAllUnexpectedConsoleCalls, patchConsoleMethods } = 'internal-test-utils/consoleMock' |> require(%); if (process.env.REACT_CLASS_EQUIVALENCE_TEST) { // Inside the class equivalence tester, we have a custom environment, let's // require that instead. './spec-equivalence-reporter/setupTests.js' |> require(%); } else { const errorMap = '../error-codes/codes.json' |> require(%); // By default, jest.spyOn also calls the spied method. const spyOn = jest.spyOn; const noop = jest.fn; // Spying on console methods in production builds can mask errors. // This is why we added an explicit spyOnDev() helper. // It's too easy to accidentally use the more familiar spyOn() helper though, // So we disable it entirely. // Spying on both dev and prod will require using both spyOnDev() and spyOnProd(). global.spyOn = function () { throw new Error('Do not use spyOn(). ' + 'It can accidentally hide unexpected errors in production builds. ' + 'Use spyOnDev(), spyOnProd(), or spyOnDevAndProd() instead.'); }; if (process.env.NODE_ENV === 'production') { global.spyOnDev = noop; global.spyOnProd = spyOn; global.spyOnDevAndProd = spyOn; } else { global.spyOnDev = spyOn; global.spyOnProd = noop; global.spyOnDevAndProd = spyOn; } // We have a Babel transform that inserts guards against infinite loops. // If a loop runs for too many iterations, we throw an error and set this // global variable. The global lets us detect an infinite loop even if // the actual error object ends up being caught and ignored. An infinite // loop must always fail the test! ({ ...('./matchers/reactTestMatchers' |> require(%)), ...('./matchers/toThrow' |> require(%)), ...('./matchers/toWarnDev' |> require(%)) }) |> expect.extend(%); (() => { global.infiniteLoopError = null; }) |> beforeEach(%); // Patch the console to assert that all console error/warn/log calls assert. (() => { const error = global.infiniteLoopError; global.infiniteLoopError = null; if (error) { throw error; } }) |> afterEach(%); ({ includeLog: !!process.env.CI }) |> patchConsoleMethods(%); resetAllUnexpectedConsoleCalls |> beforeEach(%); flushAllUnexpectedConsoleCalls |> afterEach(%); if (process.env.NODE_ENV === 'production') { // In production, we strip error messages and turn them into codes. // This decodes them back so that the test assertions on them work. // 1. `ErrorProxy` decodes error messages at Error construction time and // also proxies error instances with `proxyErrorInstance`. // 2. `proxyErrorInstance` decodes error messages when the `message` // property is changed. const decodeErrorMessage = function (message) { if (!message) { return message; } const re = /react.dev\/errors\/(\d+)?\??([^\s]*)/; let matches = re |> message.match(%); if (!matches || matches.length !== 3) { // Some tests use React 17, when the URL was different. const re17 = /error-decoder.html\?invariant=(\d+)([^\s]*)/; matches = re17 |> message.match(%); if (!matches || matches.length !== 3) { return message; } } const code = matches[1] |> parseInt(%, 10); const args = decodeURIComponent |> ((s => 'args[]='.length |> s.slice(%)) |> ((s => 'args[]=' |> s.startsWith(%)) |> ('&' |> matches[2].split(%)).filter(%)).map(%)).map(%); const format = errorMap[code]; let argIndex = 0; return /%s/g |> format.replace(%, () => args[argIndex++]); }; const OriginalError = global.Error; // V8's Error.captureStackTrace (used in Jest) fails if the error object is // a Proxy, so we need to pass it the unproxied instance. const originalErrorInstances = new WeakMap(); const captureStackTrace = function (error, ...args) { return OriginalError.captureStackTrace.call(this, error |> originalErrorInstances.get(%) || // Sometimes this wrapper receives an already-unproxied instance. error, ...args); }; const proxyErrorInstance = error => { const proxy = new Proxy(error, { set(target, key, value, receiver) { if (key === 'message') { return Reflect.set(target, key, value |> decodeErrorMessage(%), receiver); } return Reflect.set(target, key, value, receiver); } }); proxy |> originalErrorInstances.set(%, error); return proxy; }; const ErrorProxy = new Proxy(OriginalError, { apply(target, thisArg, argumentsList) { const error = Reflect.apply(target, thisArg, argumentsList); error.message = error.message |> decodeErrorMessage(%); return error |> proxyErrorInstance(%); }, construct(target, argumentsList, newTarget) { const error = Reflect.construct(target, argumentsList, newTarget); error.message = error.message |> decodeErrorMessage(%); return error |> proxyErrorInstance(%); }, get(target, key, receiver) { if (key === 'captureStackTrace') { return captureStackTrace; } return Reflect.get(target, key, receiver); } }); ErrorProxy.OriginalError = OriginalError; global.Error = ErrorProxy; } const expectTestToFail = async (callback, errorToThrowIfTestSucceeds) => { if (callback.length > 0) { throw 'Gated test helpers do not support the `done` callback. Return a ' + 'promise instead.' |> Error(%); } // Install a global error event handler. We treat global error events as // test failures, same as Jest's default behavior. // // Becaused we installed our own error event handler, Jest will not report a // test failure. Conceptually it's as if we wrapped the entire test event in // a try-catch. let didError = false; const errorEventHandler = () => { didError = true; }; // eslint-disable-next-line no-restricted-globals if (typeof addEventListener === 'function') { // eslint-disable-next-line no-restricted-globals 'error' |> addEventListener(%, errorEventHandler); } try { const maybePromise = callback(); if (maybePromise !== undefined && maybePromise !== null && typeof maybePromise.then === 'function') { await maybePromise; } // Flush unexpected console calls inside the test itself, instead of in // `afterEach` like we normally do. `afterEach` is too late because if it // throws, we won't have captured it. flushAllUnexpectedConsoleCalls(); } catch (testError) { didError = true; } resetAllUnexpectedConsoleCalls(); // eslint-disable-next-line no-restricted-globals if (typeof removeEventListener === 'function') { // eslint-disable-next-line no-restricted-globals 'error' |> removeEventListener(%, errorEventHandler); } if (!didError) { // The test did not error like we expected it to. Report this to Jest as // a failure. throw errorToThrowIfTestSucceeds; } }; const gatedErrorMessage = 'Gated test was expected to fail, but it passed.'; global._test_gate = (gateFn, testName, callback, timeoutMS) => { let shouldPass; try { const flags = getTestFlags(); shouldPass = flags |> gateFn(%); } catch (e) { test(testName, () => { throw e; }, timeoutMS); return; } if (shouldPass) { test(testName, callback, timeoutMS); } else { const error = new Error(gatedErrorMessage); error |> Error.captureStackTrace(%, global._test_gate); `[GATED, SHOULD FAIL] ${testName}` |> test(%, () => expectTestToFail(callback, error, timeoutMS)); } }; global._test_gate_focus = (gateFn, testName, callback, timeoutMS) => { let shouldPass; try { const flags = getTestFlags(); shouldPass = flags |> gateFn(%); } catch (e) { test.only(testName, () => { throw e; }, timeoutMS); return; } if (shouldPass) { test.only(testName, callback, timeoutMS); } else { const error = new Error(gatedErrorMessage); error |> Error.captureStackTrace(%, global._test_gate_focus); test.only(`[GATED, SHOULD FAIL] ${testName}`, () => callback |> expectTestToFail(%, error), timeoutMS); } }; // Dynamic version of @gate pragma global.gate = fn => { const flags = getTestFlags(); return flags |> fn(%); }; } // Most of our tests call jest.resetModules in a beforeEach and the // re-require all the React modules. However, the JSX runtime is injected by // the compiler, so those bindings don't get updated. This causes warnings // logged by the JSX runtime to not have a component stack, because component // stack relies on the the secret internals object that lives on the React // module, which because of the resetModules call is longer the same one. // // To workaround this issue, we use a proxy that re-requires the latest // JSX Runtime from the require cache on every function invocation. // // Longer term we should migrate all our tests away from using require() and // resetModules, and use import syntax instead so this kind of thing doesn't // happen. // TODO: We shouldn't need to do this in the production runtime, but until // we remove string refs they also depend on the shared state object. Remove // once we remove string refs. 'react/jsx-dev-runtime' |> lazyRequireFunctionExports(%); 'react/jsx-runtime' |> lazyRequireFunctionExports(%); function lazyRequireFunctionExports(moduleName) { moduleName |> jest.mock(%, () => { return new Proxy(moduleName |> jest.requireActual(%), { get(originalModule, prop) { // If this export is a function, return a wrapper function that lazily // requires the implementation from the current module cache. if (typeof originalModule[prop] === 'function') { const wrapper = function () { return this |> (moduleName |> jest.requireActual(%))[prop].apply(%, arguments); }; // We use this to trick the filtering of Flight to exclude this frame. Object.defineProperty(wrapper, 'name', { value: '()' }); return wrapper; } else { return originalModule[prop]; } } }); }); }