feat(jobs): integrate table and board workspace

This commit is contained in:
cesnimda
2026-09-29 23:01:05 +02:00
parent 6fc8937701
commit 384dfa8a4b
35 changed files with 1191 additions and 60 deletions
+73 -5
View File
@@ -78,13 +78,39 @@ test("[PRES-J07 J05 W01 W02 E07 E08 E09] Board query membership matches Table an
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 page.getByRole('link', { name: 'Table', exact: true }).click();
await expect(page).toHaveURL(url => url.pathname === '/jobs' && url.search === '?q=Board&status=Saved&view=table');
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);
await page.goForward();
await expect(page.locator('[data-job-row-id]')).toHaveCount(15);
await page.getByRole('button', { name: 'Go to next page' }).click();
await expect(page.locator('[data-job-row-id]')).toHaveCount(9);
const secondPageIds = await page.locator('[data-job-row-id]').evaluateAll(rows => rows.map(row => row.getAttribute('data-job-row-id')));
const viewReads: string[] = [];
const recordRead = (request: import('@playwright/test').Request) => {
if (request.method() === 'GET' && /^\/api\/jobapplications(?:\/board)?$/.test(new URL(request.url()).pathname)) viewReads.push(new URL(request.url()).pathname);
};
page.on('request', recordRead);
const boardSwitch = page.getByRole('link', { name: 'Board', exact: true });
await boardSwitch.focus(); await page.keyboard.press('Enter');
await expect(page.locator('[data-kanban-card-id]')).toHaveCount(24);
await expect(boardSwitch).toBeFocused();
await expect(boardSwitch).toHaveAttribute('aria-current', 'page');
expect(viewReads).toEqual(['/api/jobapplications/board']);
expect((await page.locator('[data-kanban-card-id]').evaluateAll(cards => cards.map(card => Number(card.getAttribute('data-kanban-card-id')))))).toEqual(expect.arrayContaining(ids.slice(3)));
await capture(page, info, 'integrated-filtered-board-keyboard');
await page.getByRole('link', { name: 'Table', exact: true }).click();
await expect(page.locator('[data-job-row-id]')).toHaveCount(9);
expect(await page.locator('[data-job-row-id]').evaluateAll(rows => rows.map(row => row.getAttribute('data-job-row-id')))).toEqual(secondPageIds);
expect(viewReads).toEqual(['/api/jobapplications/board', '/api/jobapplications']);
page.off('request', recordRead);
await capture(page, info, 'integrated-filtered-table-page-two');
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');
@@ -96,11 +122,24 @@ test("[PRES-J07 J05 W01 W02 E07 E08 E09] Board query membership matches Table an
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.getByRole('link', { name: 'Table', exact: true }).click();
await expect(page.getByText('No jobs found.')).toBeVisible();
await expect(page.locator('[data-job-row-id]')).toHaveCount(0);
await page.getByRole('link', { name: 'Board', exact: true }).click();
await expect(page.getByText('No jobs in this group yet.')).toHaveCount(3);
await page.goto('/jobs?q=Board&status=Take-home%20assignment&view=table');
await expect(page.locator('[data-job-row-id]')).toHaveCount(1);
await expect(page.locator(`[data-job-row-id="${ids[2]}"]`)).toBeVisible();
await page.getByRole('link', { name: 'Board', exact: true }).click();
await expect(page.locator('[data-kanban-card-id]')).toHaveCount(1);
await expect(page.locator(`[data-kanban-card-id="${ids[2]}"]`)).toBeVisible();
await capture(page, info, 'integrated-custom-status-board');
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("?source=board");
expect(new URL(page.url()).search).toBe("?source=board&view=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);
@@ -115,11 +154,21 @@ test("[PRES-J07 J05 W01 W02 E07 E08 E09] Board query membership matches Table an
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?source=board");
await expect(page.getByRole("link", { name: t.jobsTableView, exact: true })).toHaveAttribute("href", "/jobs?source=board&view=table");
await capture(page, info, `board-desktop-${mode}-${language}`);
await page.getByRole("link", { name: t.jobsTableView, exact: true }).click();
await expect(page.locator('[data-job-row-id]')).toHaveCount(15);
await capture(page, info, `integrated-table-desktop-${mode}-${language}`);
await page.getByRole("link", { name: t.jobsBoardView, exact: true }).click();
await expect(cards).toHaveCount(27);
await page.setViewportSize({ width: 390, height: 844 });
await expect(page.getByRole("group", { name: t.kanbanColumnLabel.replace("{label}", t.pipelineGroupNotApplied) })).toBeVisible();
await capture(page, info, `board-phone-${mode}-${language}`);
await page.getByRole("link", { name: t.jobsTableView, exact: true }).click();
await expect(page.locator('[data-job-row-id]')).toHaveCount(15);
await capture(page, info, `integrated-table-phone-${mode}-${language}`);
await page.getByRole("link", { name: t.jobsBoardView, exact: true }).click();
await expect(cards).toHaveCount(27);
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1)).toBe(true);
if (language === "en") await scan(page, info, `board-phone-${mode}`);
await page.setViewportSize({ width: 1440, height: 900 });
@@ -197,13 +246,30 @@ test("[PRES-J07 J05 W01 W02 E07 E08 E09] Board query membership matches Table an
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 === "?source=board");
await expect(page).toHaveURL(url => url.pathname === "/jobs" && url.search === "?source=board&view=board");
await expect(savedControl).toBeFocused();
} finally {
await admin.dispose();
}
});
test("[PRES-J05 J07 E07 W01] legacy Board replaces history and preserves query, hash and route state; default Jobs stays Table", async ({ page }) => {
await login(page.request, "e2e@example.test");
await page.goto('/jobs?view=unknown&q=absent');
await expect(page.getByRole('link', { name: 'Table', exact: true })).toHaveAttribute('aria-current', 'page');
await expect(page.getByText('No jobs found.')).toBeVisible();
await page.evaluate(() => history.pushState({ ...history.state, usr: { integrationSentinel: 'preserved' } }, '', '/kanban?q=absent&status=Custom+review&page=2&view=table#retained'));
const length = await page.evaluate(() => history.length);
await page.reload();
await expect(page).toHaveURL(url => url.pathname === '/jobs' && url.searchParams.get('view') === 'board' && url.searchParams.get('status') === 'Custom review' && url.searchParams.get('q') === 'absent' && url.searchParams.get('page') === '2' && url.hash === '#retained');
await expect(page.getByRole('link', { name: 'Board', exact: true })).toHaveAttribute('aria-current', 'page');
expect(await page.evaluate(() => history.length)).toBe(length);
expect(await page.evaluate(() => history.state.usr.integrationSentinel)).toBe('preserved');
await page.goBack();
await expect(page).toHaveURL(/\/jobs\?view=unknown&q=absent$/);
await expect(page.getByRole('link', { name: 'Table', exact: true })).toHaveAttribute('aria-current', 'page');
});
test("[PRES-J07 E08] Board loading, retryable error and fully empty columns", async ({ page }, info) => {
test.setTimeout(90_000);
await page.goto("/login");
@@ -219,6 +285,8 @@ test("[PRES-J07 E08] Board loading, retryable error and fully empty columns", as
else await route.fulfill({ json: [] });
});
await page.goto("/kanban", { waitUntil: "domcontentloaded" });
// The shared workspace also has a lazy-renderer loading state. Wait for the actual read.
await expect.poll(() => typeof release).toBe("function");
await expect(page.getByRole("progressbar")).toBeVisible();
await capture(page, info, "board-loading-light-en");
state = "error"; release();