96 lines
6.6 KiB
TypeScript
96 lines
6.6 KiB
TypeScript
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<typeof api>;
|
|
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: <DashboardView /> }, { path: '/jobs', element: <div>Jobs destination</div> }], { initialEntries: ['/dashboard'] });
|
|
return render(<I18nProvider><ConfirmProvider><RouterProvider router={router} /></ConfirmProvider></I18nProvider>);
|
|
}
|
|
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();
|
|
});
|