Job workspace } /> @@ -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 +});