login.test.tsx78 lines · main
| 1 | import { waitFor } from '@testing-library/dom' |
| 2 | import { expect, test, vi } from 'vitest' |
| 3 | |
| 4 | import * as cliLogin from '@/data/cli/login' |
| 5 | import { ProfileContextType } from '@/lib/profile' |
| 6 | import { CliLoginScreen } from '@/pages/cli/login' |
| 7 | import { customRender } from '@/tests/lib/custom-render' |
| 8 | |
| 9 | const DEFAULT_PROFILE_CONTEXT: ProfileContextType = { |
| 10 | profile: { |
| 11 | id: 1, |
| 12 | auth0_id: 'auth0|test', |
| 13 | gotrue_id: 'gotrue-test', |
| 14 | username: 'testuser', |
| 15 | primary_email: 'test@example.com', |
| 16 | first_name: null, |
| 17 | last_name: null, |
| 18 | mobile: null, |
| 19 | is_alpha_user: false, |
| 20 | is_sso_user: false, |
| 21 | disabled_features: [], |
| 22 | free_project_limit: null, |
| 23 | }, |
| 24 | error: null, |
| 25 | isLoading: false, |
| 26 | isError: false, |
| 27 | isSuccess: true, |
| 28 | } |
| 29 | |
| 30 | test('still navigates after parent re-renders during an in-flight POST', async () => { |
| 31 | // Resolve manually so we can inject a re-render while the POST is in flight. |
| 32 | let resolveSession: (value: { nonce: string }) => void = () => {} |
| 33 | const createCliLoginSessionMock = vi.spyOn(cliLogin, 'createCliLoginSession').mockImplementation( |
| 34 | () => |
| 35 | new Promise<{ nonce: string }>((resolve) => { |
| 36 | resolveSession = resolve |
| 37 | }) |
| 38 | ) |
| 39 | const initialNavigate = vi.fn() |
| 40 | const { rerender } = customRender( |
| 41 | <CliLoginScreen |
| 42 | isLoggedIn |
| 43 | routerReady |
| 44 | sessionId="session-test" |
| 45 | publicKey="public-key-test" |
| 46 | tokenName="local-dev" |
| 47 | navigate={initialNavigate} |
| 48 | />, |
| 49 | { profileContext: DEFAULT_PROFILE_CONTEXT } |
| 50 | ) |
| 51 | |
| 52 | await waitFor(() => { |
| 53 | expect(createCliLoginSessionMock).toHaveBeenCalledTimes(1) |
| 54 | }) |
| 55 | |
| 56 | // Parent re-renders mid-POST with a brand-new navigate ref. This was the |
| 57 | // production hang: the cleanup would invalidate the success handler and |
| 58 | // the ref guard would skip the retry, leaving the screen on the loader. |
| 59 | const laterNavigate = vi.fn() |
| 60 | rerender( |
| 61 | <CliLoginScreen |
| 62 | isLoggedIn |
| 63 | routerReady |
| 64 | sessionId="session-test" |
| 65 | publicKey="public-key-test" |
| 66 | tokenName="local-dev" |
| 67 | navigate={laterNavigate} |
| 68 | /> |
| 69 | ) |
| 70 | |
| 71 | resolveSession({ nonce: 'ABCDEFGH12345678' }) |
| 72 | |
| 73 | await waitFor(() => { |
| 74 | expect(laterNavigate).toHaveBeenCalledWith('/cli/login?device_code=ABCDEFGH') |
| 75 | }) |
| 76 | expect(initialNavigate).not.toHaveBeenCalled() |
| 77 | expect(createCliLoginSessionMock).toHaveBeenCalledTimes(1) |
| 78 | }) |