import React from 'react'; import '@testing-library/jest-dom'; import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react'; import { createMemoryRouter, RouterProvider } from 'react-router-dom'; import DashboardView from './components/DashboardView'; import { I18nProvider } from './i18n/I18nProvider'; import { ConfirmProvider } from './confirm'; import { api } from './api'; const mocked = api as jest.Mocked; let stored: any; const stats = { total: 2, active: 2, byStatus: { Applied: 2 }, appliedLast30Days: 2 }; function response(url: string) { if (url.endsWith('/preferences/dashboard')) return stored; if (url.endsWith('/stats')) return stats; if (url.endsWith('/analytics-overview')) return { funnel: [{ label: 'Applied', count: 2 }], totalActive: 2, totalResponses: 0, responseRateBySource: [], topCompanies: [], timeInStage: [], salaryInsights: [] }; if (url.endsWith('/tag-trends')) return { months: [], series: [] }; return []; } function open() { const router = createMemoryRouter([{ path: '/dashboard', element: }, { path: '/jobs', element:
Jobs destination
}], { initialEntries: ['/dashboard'] }); return render(); } beforeEach(() => { localStorage.clear(); stored = { version: 1, revision: 0, widgets: [] }; mocked.get.mockImplementation((url: string) => Promise.resolve({ data: response(url) }) as any); mocked.put.mockReset(); mocked.put.mockImplementation((_url: string, body: any) => { stored = { ...body, revision: body.revision + 1 }; return Promise.resolve({ data: stored }) as any; }); }); async function edit() { fireEvent.click(await screen.findByRole('button', { name: 'Customize dashboard' })); return screen.findByRole('region', { name: 'Editing dashboard' }); } test('visibility is a preview; cancel restores the saved layout without a write', async () => { open(); await screen.findByText('Tracked jobs'); const editor = await edit(); fireEvent.click(within(editor).getByRole('checkbox', { name: 'Summary cards' })); expect(screen.getByRole('region', { name: 'Dashboard preview' })).toBeInTheDocument(); expect(screen.queryByText('Tracked jobs')).not.toBeInTheDocument(); fireEvent.click(within(editor).getByRole('button', { name: 'Cancel' })); const dialog = await screen.findByRole('dialog'); fireEvent.click(within(dialog).getByRole('button', { name: 'Discard changes' })); await screen.findByText('Tracked jobs'); expect(mocked.put).not.toHaveBeenCalled(); await waitFor(() => expect(screen.getByRole('button', { name: 'Customize dashboard' })).toHaveFocus()); }); test('failed save keeps the draft and retry persists visibility, order and size', async () => { open(); const editor = await edit(); fireEvent.click(within(editor).getByRole('checkbox', { name: 'Summary cards' })); fireEvent.click(within(editor).getByRole('button', { name: 'Move Today earlier' })); fireEvent.change(within(editor).getByLabelText('Size for Today'), { target: { value: 'full' } }); mocked.put.mockRejectedValueOnce(new Error('offline')); fireEvent.click(within(editor).getByRole('button', { name: 'Save layout' })); await screen.findByText('Your layout could not be saved. Your changes are still here. Try again.'); expect(within(editor).getByRole('checkbox', { name: 'Summary cards' })).not.toBeChecked(); fireEvent.click(within(editor).getByRole('button', { name: 'Save layout' })); await waitFor(() => expect(screen.queryByRole('region', { name: 'Editing dashboard' })).not.toBeInTheDocument()); expect(stored.widgets[0]).toEqual({ id: 'attention', visible: true, size: 'full' }); expect(stored.widgets.find((widget: any) => widget.id === 'cards').visible).toBe(false); expect(screen.queryByText('Tracked jobs')).not.toBeInTheDocument(); }); test('all hidden widgets leave an accessible recovery and reset remains cancellable', async () => { open(); const editor = await edit(); for (const checkbox of within(editor).getAllByRole('checkbox')) fireEvent.click(checkbox); await screen.findByText('Your dashboard sections are hidden.'); fireEvent.click(within(editor).getByRole('button', { name: 'Reset to default' })); const dialog = await screen.findByRole('dialog'); fireEvent.click(within(dialog).getByRole('button', { name: 'Reset to default' })); await waitFor(() => expect(within(editor).getAllByRole('checkbox').every(item => (item as HTMLInputElement).checked)).toBe(true)); expect(mocked.put).not.toHaveBeenCalled(); }); test('keyboard ordering keeps focus and announces the resulting position without refetching reports', async () => { open(); const editor = await edit(); const before = mocked.get.mock.calls.length; const move = within(editor).getByRole('button', { name: 'Move Today earlier' }); move.focus(); fireEvent.click(move); await waitFor(() => expect(within(editor).getByRole('button', { name: 'Move Today later' })).toHaveFocus()); expect(screen.getByText('Today moved to position 1 of 9.')).toBeInTheDocument(); expect(mocked.get.mock.calls).toHaveLength(before); }); test('dirty route navigation uses the existing confirmation and can stay on the dashboard', async () => { open(); const editor = await edit(); fireEvent.click(within(editor).getByRole('checkbox', { name: 'Job tags' })); fireEvent.click(screen.getByRole('link', { name: 'Tracked jobs: 2' })); const dialog = await screen.findByRole('dialog'); fireEvent.click(within(dialog).getByRole('button', { name: 'Keep editing' })); expect(await screen.findByRole('region', { name: 'Editing dashboard' })).toBeVisible(); expect(mocked.put).not.toHaveBeenCalled(); }); test('navigation waits for an in-flight save instead of promising to discard a possible commit', async () => { open(); const editor = await edit(); fireEvent.click(within(editor).getByRole('checkbox', { name: 'Job tags' })); let complete!: (value: any) => void; mocked.put.mockImplementationOnce((_url: string, body: any) => new Promise(resolve => { complete = resolve; stored = { ...body, revision: 1 }; }) as any); fireEvent.click(within(editor).getByRole('button', { name: 'Save layout' })); fireEvent.click(screen.getByRole('link', { name: 'Tracked jobs: 2' })); expect(await screen.findByText('Please wait for your layout to finish saving before leaving.')).toBeVisible(); expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); expect(screen.queryByText('Jobs destination')).not.toBeInTheDocument(); await act(async () => { complete({ data: stored }); }); expect(await screen.findByRole('button', { name: 'Customize dashboard' })).toBeVisible(); });