JSTQL-JS-Transform/output_testing/112setupTests.js

264 lines
No EOL
10 KiB
JavaScript

'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: '(<anonymous>)'
});
return wrapper;
} else {
return originalModule[prop];
}
}
});
});
}