feat(onboarding): migrate source-backed setup guidance

This commit is contained in:
cesnimda
2026-10-12 01:54:24 +02:00
parent ad7b4196af
commit d21501aa7e
16 changed files with 9415 additions and 89 deletions
@@ -1,12 +1,12 @@
import React from 'react';
import '@testing-library/jest-dom';
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { MemoryRouter, useLocation } from 'react-router-dom';
import DashboardView from './components/DashboardView';
import { I18nProvider } from './i18n/I18nProvider';
import { api } from './api';
jest.mock('./components/OnboardingChecklist', () => () => <div>Setup retained</div>);
jest.mock('./components/OnboardingChecklist', () => ({ hasJobs }: { hasJobs: boolean | null }) => <><div>Setup retained</div><output aria-label="Setup job status">{hasJobs === null ? 'unknown' : String(hasJobs)}</output></>);
const mockedApi = api as jest.Mocked<typeof api>;
const stats = { total: 9, active: 8, deleted: 1, byStatus: { Applied: 3, Interview: 2, Offer: 1, 'Faglig vurdering': 2 }, appliedLast30Days: 4 };
const overview = { funnel: [{ label: 'Applied', count: 3 }, { label: 'Interview', count: 2 }, { label: 'Offer', count: 1 }], totalActive: 8, totalResponses: 3, responseRateBySource: [{ label: 'NAV', total: 8, responses: 3, rate: 37.5 }], topCompanies: [{ companyId: 7, company: 'Nordlys AS', count: 8, responses: 3, responseRate: 37.5 }], timeInStage: [{ stage: 'Applied', medianDays: 4, count: 3 }], salaryInsights: [{ currency: 'NOK', period: 'year', count: 2, minimum: 600000, maximum: 800000, averageMidpoint: 700000 }] };
@@ -99,3 +99,26 @@ test('[PRES-S01] activity chart uses one shared zero-based scale and a readable
const firstY = (path: Element) => Number(path.getAttribute('d')!.split(' ')[2]);
expect(firstY(paths[0])).toBeLessThan(firstY(paths[1]));
});
test('[P7-02 PRES-S01 S02] setup guidance treats pending and failed summaries as unknown until their owner retries', async () => {
let resolveStats!: (response: { data: typeof stats }) => void;
let fail = false;
const pending = new Promise<{ data: typeof stats }>(resolve => { resolveStats = resolve; });
mockedApi.get.mockImplementation((url: string) => url.endsWith('/stats')
? fail ? Promise.reject(new Error('summary unavailable')) : pending
: Promise.resolve({ data: data(url) }) as any);
const view = open();
expect(screen.getByLabelText('Setup job status')).toHaveTextContent('unknown');
await screen.findByRole('region', { name: 'Application activity' });
await act(async () => { resolveStats({ data: { ...stats, total: 0 } }); });
await waitFor(() => expect(screen.getByLabelText('Setup job status')).toHaveTextContent('false'));
view.unmount();
fail = true;
open();
await screen.findByText('Unable to load dashboard summary');
expect(screen.getByLabelText('Setup job status')).toHaveTextContent('unknown');
mockedApi.get.mockImplementation((url: string) => Promise.resolve({ data: data(url) }) as any);
fireEvent.click(screen.getByRole('button', { name: 'Retry' }));
await waitFor(() => expect(screen.getByLabelText('Setup job status')).toHaveTextContent('true'));
expect(mockedApi.get).toHaveBeenCalledWith('/jobapplications/stats');
});