go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/test_verdict/legacy/invocation_page/invocation_default_tab.test.tsx (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 { cleanup, render } from '@testing-library/react';
    16  import type { NavigateFunction } from 'react-router-dom';
    17  import * as reactRouterDom from 'react-router-dom';
    18  
    19  import { InvocationDefaultTab } from './invocation_default_tab';
    20  
    21  jest.mock('react-router-dom', () => {
    22    return createSelectiveMockFromModule<typeof import('react-router-dom')>(
    23      'react-router-dom',
    24      ['useNavigate'],
    25    );
    26  });
    27  
    28  describe('InvocationDefaultTab', () => {
    29    let useNavigateSpy: jest.MockedFunction<
    30      () => jest.MockedFunction<NavigateFunction>
    31    >;
    32  
    33    beforeEach(() => {
    34      jest.useFakeTimers();
    35      const navigateSpies = new Map<
    36        NavigateFunction,
    37        jest.MockedFunction<NavigateFunction>
    38      >();
    39      useNavigateSpy = jest
    40        .mocked(
    41          // We will return a mocked `navigate` function so we can intercept the
    42          // `navigate` calls.
    43          reactRouterDom.useNavigate as () => jest.MockedFunction<NavigateFunction>,
    44        )
    45        .mockImplementation(() => {
    46          const navigate = (
    47            jest.requireActual('react-router-dom') as typeof reactRouterDom
    48          ).useNavigate();
    49          // Return the same mock reference if the reference to `navigate` is the
    50          // same. This is to ensure the dependency checks having the same result.
    51          const navigateSpy =
    52            navigateSpies.get(navigate) ||
    53            (jest.fn(
    54              navigate,
    55              // `jest.fn` isn't smart enough to infer the function type when
    56              // mocking an overloaded function. Use manual casting instead.
    57            ) as unknown as jest.MockedFunction<NavigateFunction>);
    58          navigateSpies.set(navigate, navigateSpy);
    59          return navigateSpy;
    60        });
    61    });
    62  
    63    afterEach(() => {
    64      cleanup();
    65      jest.useRealTimers();
    66      useNavigateSpy.mockRestore();
    67    });
    68  
    69    test('should redirect to the default tab', async () => {
    70      const router = reactRouterDom.createMemoryRouter(
    71        [
    72          {
    73            path: 'path/prefix',
    74            children: [
    75              { index: true, element: <InvocationDefaultTab /> },
    76              { path: 'test-results', element: <></> },
    77            ],
    78          },
    79        ],
    80        { initialEntries: ['/path/prefix?param#hash'] },
    81      );
    82  
    83      render(<reactRouterDom.RouterProvider router={router} />);
    84  
    85      expect(useNavigateSpy).toHaveBeenCalledTimes(1);
    86      const useNavigateSpyResult = useNavigateSpy.mock.results[0];
    87      expect(useNavigateSpyResult.type).toEqual('return');
    88      // Won't happen. Useful for type inference.
    89      if (useNavigateSpyResult.type !== 'return') {
    90        throw new Error('unreachable');
    91      }
    92      const navigateSpy = useNavigateSpyResult.value;
    93      expect(navigateSpy).toHaveBeenCalledTimes(1);
    94      expect(navigateSpy.mock.calls[0]).toMatchObject([
    95        // The type definition for `.toMatchObject` is incomplete. Cast to any to
    96        // make TSC happy.
    97        // eslint-disable-next-line @typescript-eslint/no-explicit-any
    98        '/path/prefix/test-results?param#hash' as any,
    99        { replace: true },
   100      ]);
   101    });
   102  
   103    test("should work with '/' suffix", async () => {
   104      const router = reactRouterDom.createMemoryRouter(
   105        [
   106          {
   107            path: 'path/prefix',
   108            children: [
   109              { index: true, element: <InvocationDefaultTab /> },
   110              { path: 'test-results', element: <></> },
   111            ],
   112          },
   113        ],
   114        { initialEntries: ['/path/prefix/?param#hash'] },
   115      );
   116  
   117      render(<reactRouterDom.RouterProvider router={router} />);
   118  
   119      expect(useNavigateSpy).toHaveBeenCalledTimes(1);
   120      const useNavigateSpyResult = useNavigateSpy.mock.results[0];
   121      expect(useNavigateSpyResult.type).toEqual('return');
   122      // Won't happen. Useful for type inference.
   123      if (useNavigateSpyResult.type !== 'return') {
   124        throw new Error('unreachable');
   125      }
   126      const navigateSpy = useNavigateSpyResult.value;
   127      expect(navigateSpy).toHaveBeenCalledTimes(1);
   128      expect(navigateSpy.mock.calls[0]).toMatchObject([
   129        // The type definition for `.toMatchObject` is incomplete. Cast to any to
   130        // make TSC happy.
   131        // eslint-disable-next-line @typescript-eslint/no-explicit-any
   132        '/path/prefix/test-results?param#hash' as any,
   133        { replace: true },
   134      ]);
   135    });
   136  });