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
+48 -6
View File
@@ -24,7 +24,7 @@ async function scan(page: Page, info: TestInfo, name: string) {
expect(result.violations).toEqual([]);
}
test("[PRES-J07 J05 W01 W02 E07 E08 E09] full Board keeps its own population, moves, focus and route", async ({ page, playwright, context }, info) => {
test("[PRES-J07 J05 W01 W02 E07 E08 E09] Board query membership matches Table and preserves moves, focus and route", async ({ page, playwright, context }, info) => {
test.setTimeout(240_000);
const admin = await playwright.request.newContext();
const ids: number[] = [];
@@ -55,10 +55,52 @@ test("[PRES-J07 J05 W01 W02 E07 E08 E09] full Board keeps its own population, mo
}
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto("/kanban?q=table-only");
// Native API membership, not just visible counts. Board uses one complete read; only this
// regression test enumerates Table pages to compare the full logical population.
for (const query of ['', 'q=Board', 'status=Saved', 'status=Take-home%20assignment', 'q=Board&status=Saved', 'q=absent', `companyId=${companyId}&location=Troms%C3%B8&sortBy=jobTitle&sortDir=asc`]) {
const boardResponse = await page.request.get(`${apiUrl}/jobapplications/board?${query}`);
expect(boardResponse.status()).toBe(200);
const board = await boardResponse.json();
const tableIds: number[] = [];
let total = 0;
for (let number = 1; ; number++) {
const response = await page.request.get(`${apiUrl}/jobapplications?${query}&page=${number}&pageSize=15`);
expect(response.status()).toBe(200);
const data = await response.json();
total = data.total;
tableIds.push(...data.items.map((item: { id: number }) => item.id));
if (tableIds.length >= total) break;
}
expect(board.map((item: { id: number }) => item.id).sort((a: number, b: number) => a - b)).toEqual([...tableIds].sort((a, b) => a - b));
expect(board.length).toBe(total);
expect(tableIds.every(id => ids.includes(id))).toBe(true);
}
await page.goto("/kanban?q=Board&status=Saved");
await expect(page.locator('[data-kanban-card-id]')).toHaveCount(24);
await capture(page, info, "board-filtered-query-light-en");
await page.getByRole('link', { name: 'Job Applications', exact: true }).click();
await expect(page).toHaveURL(url => url.pathname === '/jobs' && url.search === '?q=Board&status=Saved');
await expect(page.locator('[data-job-row-id]')).toHaveCount(15);
const visibleIds = await page.locator('[data-job-row-id]').evaluateAll(rows => rows.map(row => Number(row.getAttribute('data-job-row-id'))));
expect(visibleIds.every(id => ids.slice(3).includes(id))).toBe(true);
await page.goBack();
await expect(page.locator('[data-kanban-card-id]')).toHaveCount(24);
const filteredControl = page.locator(`[data-kanban-card-id="${ids[4]}"] [role="button"]`);
await filteredControl.focus(); await page.keyboard.press('Space');
await page.getByRole('group', { name: 'Active column' }).focus(); await page.keyboard.press('Enter');
await expect(page.locator('[data-kanban-card-id]')).toHaveCount(23);
await expect(page.locator(`[data-kanban-card-id="${ids[4]}"]`)).toHaveCount(0);
await expect(page.getByRole('group', { name: 'Not Applied column' })).toBeFocused();
expect((await page.request.patch(`${apiUrl}/jobapplications/${ids[4]}/status`, { headers, data: { status: 'Saved' } })).status()).toBe(204);
await page.goto('/kanban?q=absent');
await expect(page.locator('[data-kanban-card-id]')).toHaveCount(0);
await expect(page.getByText('No jobs in this group yet.')).toHaveCount(3);
await capture(page, info, 'board-zero-query-light-en');
await page.goto("/kanban?source=board");
const cards = page.locator("[data-kanban-card-id]");
await expect(cards).toHaveCount(27);
expect(new URL(page.url()).search).toBe("?q=table-only");
expect(new URL(page.url()).search).toBe("?source=board");
expect((await page.request.get(`${apiUrl}/jobapplications/board`)).status()).toBe(200);
const tablePage = await (await page.request.get(`${apiUrl}/jobapplications?page=1&pageSize=15&q=Board`)).json();
expect(tablePage.items.length).toBe(15);
@@ -73,7 +115,7 @@ test("[PRES-J07 J05 W01 W02 E07 E08 E09] full Board keeps its own population, mo
await expect(cards).toHaveCount(27);
await expect(page.getByRole("group", { name: t.kanbanColumnLabel.replace("{label}", t.pipelineGroupNotApplied) })).toBeVisible();
await expect(page.getByRole("group", { name: t.kanbanColumnNotDropTarget.replace("{label}", t.kanbanGroupOther) })).toBeVisible();
await expect(page.getByRole("link", { name: t.jobApplications })).toHaveAttribute("href", "/jobs");
await expect(page.getByRole("link", { name: t.jobApplications })).toHaveAttribute("href", "/jobs?source=board");
await capture(page, info, `board-desktop-${mode}-${language}`);
await page.setViewportSize({ width: 390, height: 844 });
await expect(page.getByRole("group", { name: t.kanbanColumnLabel.replace("{label}", t.pipelineGroupNotApplied) })).toBeVisible();
@@ -155,7 +197,7 @@ test("[PRES-J07 J05 W01 W02 E07 E08 E09] full Board keeps its own population, mo
await expect(page).toHaveURL(new RegExp(`/jobs/${ids[0]}$`));
await expect(page.getByRole("heading", { name: /Seniorrådgiver/ })).toBeVisible();
await page.goBack();
await expect(page).toHaveURL(url => url.pathname === "/kanban" && url.search === "?q=table-only");
await expect(page).toHaveURL(url => url.pathname === "/kanban" && url.search === "?source=board");
await expect(savedControl).toBeFocused();
} finally {
await admin.dispose();
@@ -171,7 +213,7 @@ test("[PRES-J07 E08] Board loading, retryable error and fully empty columns", as
await expect(page).toHaveURL(/\/dashboard$/);
let release!: () => void;
let state: "loading" | "error" | "empty" = "loading";
await page.route("**/api/jobapplications/board", async route => {
await page.route("**/api/jobapplications/board?**", async route => {
if (state === "loading") await new Promise<void>(resolve => { release = resolve; });
if (state === "error") await route.fulfill({ status: 503, json: { message: "Synthetic board outage" } });
else await route.fulfill({ json: [] });