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.  * @flow
    
  8.  */
    
  9. 
    
  10. import {CustomConsole} from '@jest/console';
    
  11. 
    
  12. import type {
    
  13.   BackendBridge,
    
  14.   FrontendBridge,
    
  15. } from 'react-devtools-shared/src/bridge';
    
  16. 
    
  17. // Argument is serialized when passed from jest-cli script through to setupTests.
    
  18. const compactConsole = process.env.compactConsole === 'true';
    
  19. if (compactConsole) {
    
  20.   const formatter = (type, message) => {
    
  21.     switch (type) {
    
  22.       case 'error':
    
  23.         return '\x1b[31m' + message + '\x1b[0m';
    
  24.       case 'warn':
    
  25.         return '\x1b[33m' + message + '\x1b[0m';
    
  26.       case 'log':
    
  27.       default:
    
  28.         return message;
    
  29.     }
    
  30.   };
    
  31. 
    
  32.   global.console = new CustomConsole(process.stdout, process.stderr, formatter);
    
  33. }
    
  34. 
    
  35. beforeEach(() => {
    
  36.   global.mockClipboardCopy = jest.fn();
    
  37. 
    
  38.   // Test environment doesn't support document methods like execCommand()
    
  39.   // Also once the backend components below have been required,
    
  40.   // it's too late for a test to mock the clipboard-js modules.
    
  41.   jest.mock('clipboard-js', () => ({copy: global.mockClipboardCopy}));
    
  42. 
    
  43.   // These files should be required (and re-required) before each test,
    
  44.   // rather than imported at the head of the module.
    
  45.   // That's because we reset modules between tests,
    
  46.   // which disconnects the DevTool's cache from the current dispatcher ref.
    
  47.   const Agent = require('react-devtools-shared/src/backend/agent').default;
    
  48.   const {initBackend} = require('react-devtools-shared/src/backend');
    
  49.   const Bridge = require('react-devtools-shared/src/bridge').default;
    
  50.   const Store = require('react-devtools-shared/src/devtools/store').default;
    
  51.   const {installHook} = require('react-devtools-shared/src/hook');
    
  52.   const {
    
  53.     getDefaultComponentFilters,
    
  54.     setSavedComponentFilters,
    
  55.   } = require('react-devtools-shared/src/utils');
    
  56. 
    
  57.   // Fake timers let us flush Bridge operations between setup and assertions.
    
  58.   jest.useFakeTimers();
    
  59. 
    
  60.   // Use utils.js#withErrorsOrWarningsIgnored instead of directly mutating this array.
    
  61.   global._ignoredErrorOrWarningMessages = [];
    
  62.   function shouldIgnoreConsoleErrorOrWarn(args) {
    
  63.     let firstArg = args[0];
    
  64.     if (
    
  65.       firstArg !== null &&
    
  66.       typeof firstArg === 'object' &&
    
  67.       String(firstArg).indexOf('Error: Uncaught [') === 0
    
  68.     ) {
    
  69.       firstArg = String(firstArg);
    
  70.     } else if (typeof firstArg !== 'string') {
    
  71.       return false;
    
  72.     }
    
  73.     const shouldFilter = global._ignoredErrorOrWarningMessages.some(
    
  74.       errorOrWarningMessage => {
    
  75.         return firstArg.indexOf(errorOrWarningMessage) !== -1;
    
  76.       },
    
  77.     );
    
  78. 
    
  79.     return shouldFilter;
    
  80.   }
    
  81. 
    
  82.   const originalConsoleError = console.error;
    
  83.   console.error = (...args) => {
    
  84.     const firstArg = args[0];
    
  85.     if (
    
  86.       firstArg === 'Warning: React instrumentation encountered an error: %s'
    
  87.     ) {
    
  88.       // Rethrow errors from React.
    
  89.       throw args[1];
    
  90.     } else if (
    
  91.       typeof firstArg === 'string' &&
    
  92.       (firstArg.startsWith(
    
  93.         "Warning: It looks like you're using the wrong act()",
    
  94.       ) ||
    
  95.         firstArg.startsWith(
    
  96.           'Warning: The current testing environment is not configured to support act',
    
  97.         ) ||
    
  98.         firstArg.startsWith(
    
  99.           'Warning: You seem to have overlapping act() calls',
    
  100.         ))
    
  101.     ) {
    
  102.       // DevTools intentionally wraps updates with acts from both DOM and test-renderer,
    
  103.       // since test updates are expected to impact both renderers.
    
  104.       return;
    
  105.     } else if (shouldIgnoreConsoleErrorOrWarn(args)) {
    
  106.       // Allows testing how DevTools behaves when it encounters console.error without cluttering the test output.
    
  107.       // Errors can be ignored by running in a special context provided by utils.js#withErrorsOrWarningsIgnored
    
  108.       return;
    
  109.     }
    
  110.     originalConsoleError.apply(console, args);
    
  111.   };
    
  112.   const originalConsoleWarn = console.warn;
    
  113.   console.warn = (...args) => {
    
  114.     if (shouldIgnoreConsoleErrorOrWarn(args)) {
    
  115.       // Allows testing how DevTools behaves when it encounters console.warn without cluttering the test output.
    
  116.       // Warnings can be ignored by running in a special context provided by utils.js#withErrorsOrWarningsIgnored
    
  117.       return;
    
  118.     }
    
  119.     originalConsoleWarn.apply(console, args);
    
  120.   };
    
  121. 
    
  122.   // Initialize filters to a known good state.
    
  123.   setSavedComponentFilters(getDefaultComponentFilters());
    
  124.   global.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = getDefaultComponentFilters();
    
  125. 
    
  126.   // Also initialize inline warnings so that we can test them.
    
  127.   global.__REACT_DEVTOOLS_SHOW_INLINE_WARNINGS_AND_ERRORS__ = true;
    
  128. 
    
  129.   installHook(global);
    
  130. 
    
  131.   const bridgeListeners = [];
    
  132.   const bridge = new Bridge({
    
  133.     listen(callback) {
    
  134.       bridgeListeners.push(callback);
    
  135.       return () => {
    
  136.         const index = bridgeListeners.indexOf(callback);
    
  137.         if (index >= 0) {
    
  138.           bridgeListeners.splice(index, 1);
    
  139.         }
    
  140.       };
    
  141.     },
    
  142.     send(event: string, payload: any, transferable?: Array<any>) {
    
  143.       bridgeListeners.forEach(callback => callback({event, payload}));
    
  144.     },
    
  145.   });
    
  146. 
    
  147.   const agent = new Agent(((bridge: any): BackendBridge));
    
  148. 
    
  149.   const hook = global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
    
  150. 
    
  151.   initBackend(hook, agent, global);
    
  152. 
    
  153.   const store = new Store(((bridge: any): FrontendBridge));
    
  154. 
    
  155.   global.agent = agent;
    
  156.   global.bridge = bridge;
    
  157.   global.store = store;
    
  158. 
    
  159.   const readFileSync = require('fs').readFileSync;
    
  160.   async function mockFetch(url) {
    
  161.     return {
    
  162.       ok: true,
    
  163.       status: 200,
    
  164.       text: async () => readFileSync(__dirname + url, 'utf-8'),
    
  165.     };
    
  166.   }
    
  167.   global.fetch = mockFetch;
    
  168. });
    
  169. afterEach(() => {
    
  170.   delete global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
    
  171. 
    
  172.   // It's important to reset modules between test runs;
    
  173.   // Without this, ReactDOM won't re-inject itself into the new hook.
    
  174.   // It's also important to reset after tests, rather than before,
    
  175.   // so that we don't disconnect the ReactCurrentDispatcher ref.
    
  176.   jest.resetModules();
    
  177. });