feat(12-07): gate /setup route on setupComplete (gap 5)
- Reverse-gate the /setup route: setupComplete===true → SetupPage alreadyLocked (Surface 8 'Setup already complete'); loading → no-flash placeholder; else wizard - Add App.test.tsx reverse-gate tests (already-complete surface + active wizard on /setup) - SetupPage mock now respects the alreadyLocked prop
This commit is contained in:
@@ -25,8 +25,17 @@ vi.mock('./components/CalendarShell.js', () => ({
|
||||
CalendarShell: () => <div data-testid="calendar-shell">CalendarShell</div>,
|
||||
}));
|
||||
|
||||
// SetupPage mock respects the `alreadyLocked` prop so the reverse-gate test (gap 5)
|
||||
// can distinguish the active wizard (Step 1 "Welcome to FamilySync Setup") from the
|
||||
// Surface 8 "Setup already complete" terminal surface. The real SetupPage renders
|
||||
// these two surfaces based on this exact prop — see routes/SetupPage.tsx.
|
||||
vi.mock('./routes/SetupPage.js', () => ({
|
||||
SetupPage: () => <div data-testid="setup-page">SetupPage</div>,
|
||||
SetupPage: ({ alreadyLocked }: { alreadyLocked?: boolean }) =>
|
||||
alreadyLocked ? (
|
||||
<div data-testid="setup-page">Setup already complete</div>
|
||||
) : (
|
||||
<div data-testid="setup-page">Welcome to FamilySync Setup</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('./components/AppNav.js', () => ({
|
||||
@@ -177,6 +186,41 @@ describe('App — setup-status gate', () => {
|
||||
expect(screen.getByTestId('setup-page')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
// gap 5 (T-12-04): manually visiting /setup AFTER setup is complete must show the
|
||||
// "Setup already complete" surface (alreadyLocked), NOT re-mount the active wizard.
|
||||
it('renders the "already complete" surface (not the wizard) on /setup when setupComplete is true', async () => {
|
||||
mockFetchSetupStatus.mockResolvedValue({ setupComplete: true });
|
||||
// Navigate directly to /setup (URL-isolation pattern — beforeEach reset to /)
|
||||
window.history.pushState({}, '', '/setup');
|
||||
|
||||
const queryClient = makeQueryClient();
|
||||
renderApp(queryClient);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('setup-page')).toHaveTextContent('Setup already complete');
|
||||
});
|
||||
|
||||
// The active wizard's Step 1 heading must NOT render when setup is complete
|
||||
expect(screen.queryByText('Welcome to FamilySync Setup')).toBeNull();
|
||||
// Standalone wizard surface — no AppNav shell on /setup
|
||||
expect(screen.queryByTestId('app-nav')).toBeNull();
|
||||
});
|
||||
|
||||
// gap 5 counterpart: /setup with setupComplete false still mounts the active wizard.
|
||||
it('renders the active wizard on /setup when setupComplete is false', async () => {
|
||||
mockFetchSetupStatus.mockResolvedValue({ setupComplete: false });
|
||||
window.history.pushState({}, '', '/setup');
|
||||
|
||||
const queryClient = makeQueryClient();
|
||||
renderApp(queryClient);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('setup-page')).toHaveTextContent('Welcome to FamilySync Setup');
|
||||
});
|
||||
|
||||
expect(screen.queryByText('Setup already complete')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('App — setupStatus and route presence', () => {
|
||||
|
||||
Reference in New Issue
Block a user