go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/testing_tools/setup_after_env.ts (about)

     1  // Copyright 2023 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  import crypto from 'crypto';
    16  import { TextDecoder, TextEncoder } from 'util';
    17  
    18  import '@testing-library/jest-dom';
    19  import 'isomorphic-fetch';
    20  import 'intersection-observer';
    21  import { matchers as emotionMatchers } from '@emotion/jest';
    22  import * as dotenv from 'dotenv';
    23  import * as idbKeyVal from 'idb-keyval';
    24  import { configure } from 'mobx';
    25  
    26  import { assertNonNullable } from '@/generic_libs/tools/utils';
    27  import '@/proto_utils/duration_patch';
    28  
    29  import {
    30    createSelectiveMockFromModule,
    31    createSelectiveSpiesFromModule,
    32  } from './jest_utils';
    33  
    34  expect.extend(emotionMatchers);
    35  
    36  // TODO(crbug/1347294): encloses all state modifying actions in mobx actions
    37  // then delete this.
    38  configure({ enforceActions: 'never' });
    39  
    40  dotenv.config({
    41    path: './.env.development',
    42  });
    43  
    44  // Those variables are declared as `const` so their value won't be accidentally
    45  // changed. But they are actually injected by `/configs.js` to `self` in
    46  // production environment. Here we need to do the same so they are available to
    47  // code run under the test environment.
    48  const configJSVars = self as unknown as {
    49    VERSION: typeof VERSION;
    50    SETTINGS: typeof SETTINGS;
    51  };
    52  configJSVars.VERSION = assertNonNullable(process.env['VITE_MILO_VERSION']);
    53  configJSVars.SETTINGS = Object.freeze({
    54    buildbucket: {
    55      host: assertNonNullable(process.env['VITE_BUILDBUCKET_HOST']),
    56    },
    57    swarming: {
    58      defaultHost: assertNonNullable(process.env['VITE_SWARMING_DEFAULT_HOST']),
    59    },
    60    resultdb: {
    61      host: assertNonNullable(process.env['VITE_RESULT_DB_HOST']),
    62    },
    63    luciAnalysis: {
    64      host: assertNonNullable(process.env['VITE_LUCI_ANALYSIS_HOST']),
    65    },
    66    luciBisection: {
    67      host: assertNonNullable(process.env['VITE_LUCI_BISECTION_HOST']),
    68    },
    69    sheriffOMatic: {
    70      host: assertNonNullable(process.env['VITE_SHERIFF_O_MATIC_HOST']),
    71    },
    72    luciTreeStatus: {
    73      host: assertNonNullable(process.env['VITE_TREE_STATUS_HOST']),
    74    },
    75    luciNotify: {
    76      host: assertNonNullable(process.env['VITE_LUCI_NOTIFY_HOST']),
    77    },
    78  });
    79  
    80  // `jest.mock` calls are automatically moved to the beginning of a test file by
    81  // the jest test runner (i.e. before any import statements), making it
    82  // impossible to use imported symbols in the module factory.
    83  //
    84  // Make the following functions accessible through `self` so they can be used in
    85  // the module factory in a
    86  // `jest.mock('module-name', () => { /* module factory */ })` call.
    87  self.createSelectiveMockFromModule = createSelectiveMockFromModule;
    88  self.createSelectiveSpiesFromModule = createSelectiveSpiesFromModule;
    89  
    90  self.TextEncoder = TextEncoder;
    91  self.TextDecoder = TextDecoder as typeof self.TextDecoder;
    92  
    93  self.CSSStyleSheet.prototype.replace = () => Promise.race([]);
    94  
    95  jest.mock('idb-keyval');
    96  const idbMockStore = new Map();
    97  jest.mocked(idbKeyVal.get).mockImplementation(async (k) => {
    98    return idbMockStore.get(k);
    99  });
   100  jest.mocked(idbKeyVal.set).mockImplementation(async (k, v) => {
   101    idbMockStore.set(k, v);
   102  });
   103  
   104  Object.defineProperty(self, 'crypto', {
   105    value: {
   106      subtle: crypto.webcrypto.subtle,
   107      // GetRandomValues is required by the nanoid package to run tests.
   108      getRandomValues: (arr: unknown[]) => crypto.randomBytes(arr.length),
   109    },
   110  });
   111  
   112  // jsdom does not support `scrollIntoView`.
   113  // See https://github.com/jsdom/jsdom/issues/1695
   114  window.HTMLElement.prototype.scrollIntoView = jest.fn();
   115  
   116  jest.mock('lit/decorators.js', () => ({
   117    ...jest.requireActual('lit/decorators.js'),
   118    customElement(name: string) {
   119      return function (eleCon: CustomElementConstructor) {
   120        // jest's module mocking may cause the module to be initialized multiple
   121        // times and causing the element to be registered multiple times, leading
   122        // to error: 'NotSupportedError: This name has already been registered in
   123        // the registry.'
   124        //
   125        // Register the element conditionally to avoid the error.
   126        if (!customElements.get(name)) {
   127          customElements.define(name, eleCon);
   128        }
   129      };
   130    },
   131  }));