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 });