fix(jobs): align table and board query contracts

This commit is contained in:
cesnimda
2026-09-29 17:26:08 +02:00
parent 42a696bc86
commit 6fc8937701
21 changed files with 541 additions and 100 deletions
@@ -34,11 +34,11 @@ function job(id: number, jobTitle: string, status: string, overrides: Partial<Jo
} as JobApplication;
}
function renderBoard() {
function renderBoard(entry = '/kanban?source=board') {
return render(
<ToastProvider>
<I18nProvider>
<MemoryRouter initialEntries={['/kanban?source=board']}>
<MemoryRouter initialEntries={[entry]}>
<Routes>
<Route path="/kanban" element={<KanbanBoard />} />
<Route path="/jobs/:id" element={<div role="main">Job workspace <RouteState /></div>} />
@@ -201,8 +201,8 @@ test('full-board population keeps jobs beyond the table page and offers a separa
renderBoard();
expect(await screen.findByText('Role 26')).toBeVisible();
expect(screen.getAllByRole('button', { name: /Press Space to pick up/ })).toHaveLength(27);
expect(mockedApi.get).toHaveBeenCalledWith('/jobapplications/board');
expect(screen.getByRole('link', { name: /Job Applications/ })).toHaveAttribute('href', '/jobs');
expect(mockedApi.get).toHaveBeenCalledWith('/jobapplications/board', expect.objectContaining({ params: expect.objectContaining({ includeDeleted: false }) }));
expect(screen.getByRole('link', { name: /Job Applications/ })).toHaveAttribute('href', '/jobs?source=board');
});
test('keyboard Escape cancels movement without a write and successful movement restores card focus', async () => {
@@ -238,3 +238,39 @@ test('keyboard activation of the exact-status menu does not open the job', async
expect(await screen.findByRole('menu')).toBeVisible();
expect(screen.queryByText('Job workspace')).not.toBeInTheDocument();
});
test('Board sends the shared logical URL query, excluding Table paging and client ownership fields', async () => {
mockedApi.get.mockResolvedValue({ data: [] } as any);
renderBoard('/kanban?q=Board&status=Custom%20review&companyId=7&location=Troms%C3%B8&needsFollowUp=1&readiness=interview&includeDeleted=1&sortBy=jobTitle&sortDir=asc&page=3&pageSize=999&userId=other');
await screen.findByRole('group', { name: 'Not Applied column' });
expect(mockedApi.get).toHaveBeenCalledWith('/jobapplications/board', { params: {
q: 'Board', status: 'Custom review', companyId: 7, location: 'Tromsø', needsFollowUp: true,
readiness: 'interview', includeDeleted: true, sortBy: 'jobTitle', sortDir: 'asc',
} });
expect(screen.getByRole('link', { name: /Job Applications/ })).toHaveAttribute('href', expect.stringContaining('q=Board&status=Custom%20review'));
});
test('a successful filtered move reloads membership and focuses the board when the job leaves it', async () => {
mockedApi.get.mockResolvedValueOnce({ data: [job(11, 'Filtered Role', 'Saved')] } as any)
.mockResolvedValueOnce({ data: [] } as any);
renderBoard('/kanban?status=Saved');
fireEvent.keyDown(await screen.findByRole('button', { name: /^Filtered Role/ }), { key: ' ' });
fireEvent.keyDown(screen.getByRole('group', { name: 'Active column' }), { key: 'Enter' });
await waitFor(() => expect(mockedApi.get).toHaveBeenCalledTimes(2));
await waitFor(() => expect(screen.queryByText('Filtered Role')).not.toBeInTheDocument());
await waitFor(() => expect(screen.getByRole('group', { name: 'Not Applied column' })).toHaveFocus());
expect(mockedApi.get).toHaveBeenLastCalledWith('/jobapplications/board', { params: expect.objectContaining({ status: 'Saved' }) });
});
test('a committed move followed by a failed query hides stale membership until retry succeeds', async () => {
mockedApi.get.mockResolvedValueOnce({ data: [job(11, 'Filtered Role', 'Saved')] } as any)
.mockRejectedValueOnce(new Error('read failed')).mockResolvedValueOnce({ data: [] } as any);
renderBoard('/kanban?status=Saved');
fireEvent.click(await screen.findByRole('button', { name: 'Quick status: Filtered Role' }));
fireEvent.click(await screen.findByRole('menuitem', { name: 'Set Applied' }));
expect(await screen.findByText('Unable to load the kanban board')).toBeVisible();
expect(screen.queryByText('Filtered Role')).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Retry' }));
expect(await screen.findByRole('group', { name: 'Not Applied column' })).toBeVisible();
expect(mockedApi.patch).toHaveBeenCalledTimes(1); // retry only the read, never replay the write
});