1. 'use strict';
    
  2. 
    
  3. const chalk = require('chalk');
    
  4. const util = require('util');
    
  5. const shouldIgnoreConsoleError = require('./shouldIgnoreConsoleError');
    
  6. const {getTestFlags} = require('./TestFlags');
    
  7. 
    
  8. if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
    
  9.   // Inside the class equivalence tester, we have a custom environment, let's
    
  10.   // require that instead.
    
  11.   require('./spec-equivalence-reporter/setupTests.js');
    
  12. } else {
    
  13.   const errorMap = require('../error-codes/codes.json');
    
  14. 
    
  15.   // By default, jest.spyOn also calls the spied method.
    
  16.   const spyOn = jest.spyOn;
    
  17.   const noop = jest.fn;
    
  18. 
    
  19.   // Spying on console methods in production builds can mask errors.
    
  20.   // This is why we added an explicit spyOnDev() helper.
    
  21.   // It's too easy to accidentally use the more familiar spyOn() helper though,
    
  22.   // So we disable it entirely.
    
  23.   // Spying on both dev and prod will require using both spyOnDev() and spyOnProd().
    
  24.   global.spyOn = function () {
    
  25.     throw new Error(
    
  26.       'Do not use spyOn(). ' +
    
  27.         'It can accidentally hide unexpected errors in production builds. ' +
    
  28.         'Use spyOnDev(), spyOnProd(), or spyOnDevAndProd() instead.'
    
  29.     );
    
  30.   };
    
  31. 
    
  32.   if (process.env.NODE_ENV === 'production') {
    
  33.     global.spyOnDev = noop;
    
  34.     global.spyOnProd = spyOn;
    
  35.     global.spyOnDevAndProd = spyOn;
    
  36.   } else {
    
  37.     global.spyOnDev = spyOn;
    
  38.     global.spyOnProd = noop;
    
  39.     global.spyOnDevAndProd = spyOn;
    
  40.   }
    
  41. 
    
  42.   expect.extend({
    
  43.     ...require('./matchers/reactTestMatchers'),
    
  44.     ...require('./matchers/toThrow'),
    
  45.     ...require('./matchers/toWarnDev'),
    
  46.   });
    
  47. 
    
  48.   // We have a Babel transform that inserts guards against infinite loops.
    
  49.   // If a loop runs for too many iterations, we throw an error and set this
    
  50.   // global variable. The global lets us detect an infinite loop even if
    
  51.   // the actual error object ends up being caught and ignored. An infinite
    
  52.   // loop must always fail the test!
    
  53.   beforeEach(() => {
    
  54.     global.infiniteLoopError = null;
    
  55.   });
    
  56.   afterEach(() => {
    
  57.     const error = global.infiniteLoopError;
    
  58.     global.infiniteLoopError = null;
    
  59.     if (error) {
    
  60.       throw error;
    
  61.     }
    
  62.   });
    
  63. 
    
  64.   // TODO: Consider consolidating this with `yieldValue`. In both cases, tests
    
  65.   // should not be allowed to exit without asserting on the entire log.
    
  66.   const patchConsoleMethod = (methodName, unexpectedConsoleCallStacks) => {
    
  67.     const newMethod = function (format, ...args) {
    
  68.       // Ignore uncaught errors reported by jsdom
    
  69.       // and React addendums because they're too noisy.
    
  70.       if (methodName === 'error' && shouldIgnoreConsoleError(format, args)) {
    
  71.         return;
    
  72.       }
    
  73. 
    
  74.       // Capture the call stack now so we can warn about it later.
    
  75.       // The call stack has helpful information for the test author.
    
  76.       // Don't throw yet though b'c it might be accidentally caught and suppressed.
    
  77.       const stack = new Error().stack;
    
  78.       unexpectedConsoleCallStacks.push([
    
  79.         stack.slice(stack.indexOf('\n') + 1),
    
  80.         util.format(format, ...args),
    
  81.       ]);
    
  82.     };
    
  83. 
    
  84.     console[methodName] = newMethod;
    
  85. 
    
  86.     return newMethod;
    
  87.   };
    
  88. 
    
  89.   const flushUnexpectedConsoleCalls = (
    
  90.     mockMethod,
    
  91.     methodName,
    
  92.     expectedMatcher,
    
  93.     unexpectedConsoleCallStacks
    
  94.   ) => {
    
  95.     if (
    
  96.       console[methodName] !== mockMethod &&
    
  97.       !jest.isMockFunction(console[methodName])
    
  98.     ) {
    
  99.       throw new Error(
    
  100.         `Test did not tear down console.${methodName} mock properly.`
    
  101.       );
    
  102.     }
    
  103.     if (unexpectedConsoleCallStacks.length > 0) {
    
  104.       const messages = unexpectedConsoleCallStacks.map(
    
  105.         ([stack, message]) =>
    
  106.           `${chalk.red(message)}\n` +
    
  107.           `${stack
    
  108.             .split('\n')
    
  109.             .map(line => chalk.gray(line))
    
  110.             .join('\n')}`
    
  111.       );
    
  112. 
    
  113.       const message =
    
  114.         `Expected test not to call ${chalk.bold(
    
  115.           `console.${methodName}()`
    
  116.         )}.\n\n` +
    
  117.         'If the warning is expected, test for it explicitly by:\n' +
    
  118.         `1. Using the ${chalk.bold('.' + expectedMatcher + '()')} ` +
    
  119.         `matcher, or...\n` +
    
  120.         `2. Mock it out using ${chalk.bold(
    
  121.           'spyOnDev'
    
  122.         )}(console, '${methodName}') or ${chalk.bold(
    
  123.           'spyOnProd'
    
  124.         )}(console, '${methodName}'), and test that the warning occurs.`;
    
  125. 
    
  126.       throw new Error(`${message}\n\n${messages.join('\n\n')}`);
    
  127.     }
    
  128.   };
    
  129. 
    
  130.   const unexpectedErrorCallStacks = [];
    
  131.   const unexpectedWarnCallStacks = [];
    
  132. 
    
  133.   const errorMethod = patchConsoleMethod('error', unexpectedErrorCallStacks);
    
  134.   const warnMethod = patchConsoleMethod('warn', unexpectedWarnCallStacks);
    
  135. 
    
  136.   const flushAllUnexpectedConsoleCalls = () => {
    
  137.     flushUnexpectedConsoleCalls(
    
  138.       errorMethod,
    
  139.       'error',
    
  140.       'toErrorDev',
    
  141.       unexpectedErrorCallStacks
    
  142.     );
    
  143.     flushUnexpectedConsoleCalls(
    
  144.       warnMethod,
    
  145.       'warn',
    
  146.       'toWarnDev',
    
  147.       unexpectedWarnCallStacks
    
  148.     );
    
  149.     unexpectedErrorCallStacks.length = 0;
    
  150.     unexpectedWarnCallStacks.length = 0;
    
  151.   };
    
  152. 
    
  153.   const resetAllUnexpectedConsoleCalls = () => {
    
  154.     unexpectedErrorCallStacks.length = 0;
    
  155.     unexpectedWarnCallStacks.length = 0;
    
  156.   };
    
  157. 
    
  158.   beforeEach(resetAllUnexpectedConsoleCalls);
    
  159.   afterEach(flushAllUnexpectedConsoleCalls);
    
  160. 
    
  161.   if (process.env.NODE_ENV === 'production') {
    
  162.     // In production, we strip error messages and turn them into codes.
    
  163.     // This decodes them back so that the test assertions on them work.
    
  164.     // 1. `ErrorProxy` decodes error messages at Error construction time and
    
  165.     //    also proxies error instances with `proxyErrorInstance`.
    
  166.     // 2. `proxyErrorInstance` decodes error messages when the `message`
    
  167.     //    property is changed.
    
  168.     const decodeErrorMessage = function (message) {
    
  169.       if (!message) {
    
  170.         return message;
    
  171.       }
    
  172.       const re = /error-decoder.html\?invariant=(\d+)([^\s]*)/;
    
  173.       const matches = message.match(re);
    
  174.       if (!matches || matches.length !== 3) {
    
  175.         return message;
    
  176.       }
    
  177.       const code = parseInt(matches[1], 10);
    
  178.       const args = matches[2]
    
  179.         .split('&')
    
  180.         .filter(s => s.startsWith('args[]='))
    
  181.         .map(s => s.slice('args[]='.length))
    
  182.         .map(decodeURIComponent);
    
  183.       const format = errorMap[code];
    
  184.       let argIndex = 0;
    
  185.       return format.replace(/%s/g, () => args[argIndex++]);
    
  186.     };
    
  187.     const OriginalError = global.Error;
    
  188.     // V8's Error.captureStackTrace (used in Jest) fails if the error object is
    
  189.     // a Proxy, so we need to pass it the unproxied instance.
    
  190.     const originalErrorInstances = new WeakMap();
    
  191.     const captureStackTrace = function (error, ...args) {
    
  192.       return OriginalError.captureStackTrace.call(
    
  193.         this,
    
  194.         originalErrorInstances.get(error) ||
    
  195.           // Sometimes this wrapper receives an already-unproxied instance.
    
  196.           error,
    
  197.         ...args
    
  198.       );
    
  199.     };
    
  200.     const proxyErrorInstance = error => {
    
  201.       const proxy = new Proxy(error, {
    
  202.         set(target, key, value, receiver) {
    
  203.           if (key === 'message') {
    
  204.             return Reflect.set(
    
  205.               target,
    
  206.               key,
    
  207.               decodeErrorMessage(value),
    
  208.               receiver
    
  209.             );
    
  210.           }
    
  211.           return Reflect.set(target, key, value, receiver);
    
  212.         },
    
  213.       });
    
  214.       originalErrorInstances.set(proxy, error);
    
  215.       return proxy;
    
  216.     };
    
  217.     const ErrorProxy = new Proxy(OriginalError, {
    
  218.       apply(target, thisArg, argumentsList) {
    
  219.         const error = Reflect.apply(target, thisArg, argumentsList);
    
  220.         error.message = decodeErrorMessage(error.message);
    
  221.         return proxyErrorInstance(error);
    
  222.       },
    
  223.       construct(target, argumentsList, newTarget) {
    
  224.         const error = Reflect.construct(target, argumentsList, newTarget);
    
  225.         error.message = decodeErrorMessage(error.message);
    
  226.         return proxyErrorInstance(error);
    
  227.       },
    
  228.       get(target, key, receiver) {
    
  229.         if (key === 'captureStackTrace') {
    
  230.           return captureStackTrace;
    
  231.         }
    
  232.         return Reflect.get(target, key, receiver);
    
  233.       },
    
  234.     });
    
  235.     ErrorProxy.OriginalError = OriginalError;
    
  236.     global.Error = ErrorProxy;
    
  237.   }
    
  238. 
    
  239.   const expectTestToFail = async (callback, errorMsg) => {
    
  240.     if (callback.length > 0) {
    
  241.       throw Error(
    
  242.         'Gated test helpers do not support the `done` callback. Return a ' +
    
  243.           'promise instead.'
    
  244.       );
    
  245.     }
    
  246.     try {
    
  247.       const maybePromise = callback();
    
  248.       if (
    
  249.         maybePromise !== undefined &&
    
  250.         maybePromise !== null &&
    
  251.         typeof maybePromise.then === 'function'
    
  252.       ) {
    
  253.         await maybePromise;
    
  254.       }
    
  255.       // Flush unexpected console calls inside the test itself, instead of in
    
  256.       // `afterEach` like we normally do. `afterEach` is too late because if it
    
  257.       // throws, we won't have captured it.
    
  258.       flushAllUnexpectedConsoleCalls();
    
  259.     } catch (error) {
    
  260.       // Failed as expected
    
  261.       resetAllUnexpectedConsoleCalls();
    
  262.       return;
    
  263.     }
    
  264.     throw Error(errorMsg);
    
  265.   };
    
  266. 
    
  267.   const gatedErrorMessage = 'Gated test was expected to fail, but it passed.';
    
  268.   global._test_gate = (gateFn, testName, callback) => {
    
  269.     let shouldPass;
    
  270.     try {
    
  271.       const flags = getTestFlags();
    
  272.       shouldPass = gateFn(flags);
    
  273.     } catch (e) {
    
  274.       test(testName, () => {
    
  275.         throw e;
    
  276.       });
    
  277.       return;
    
  278.     }
    
  279.     if (shouldPass) {
    
  280.       test(testName, callback);
    
  281.     } else {
    
  282.       test(`[GATED, SHOULD FAIL] ${testName}`, () =>
    
  283.         expectTestToFail(callback, gatedErrorMessage));
    
  284.     }
    
  285.   };
    
  286.   global._test_gate_focus = (gateFn, testName, callback) => {
    
  287.     let shouldPass;
    
  288.     try {
    
  289.       const flags = getTestFlags();
    
  290.       shouldPass = gateFn(flags);
    
  291.     } catch (e) {
    
  292.       test.only(testName, () => {
    
  293.         throw e;
    
  294.       });
    
  295.       return;
    
  296.     }
    
  297.     if (shouldPass) {
    
  298.       test.only(testName, callback);
    
  299.     } else {
    
  300.       test.only(`[GATED, SHOULD FAIL] ${testName}`, () =>
    
  301.         expectTestToFail(callback, gatedErrorMessage));
    
  302.     }
    
  303.   };
    
  304. 
    
  305.   // Dynamic version of @gate pragma
    
  306.   global.gate = fn => {
    
  307.     const flags = getTestFlags();
    
  308.     return fn(flags);
    
  309.   };
    
  310. }