mirror of
https://github.com/BradGroux/veritas-kanban.git
synced 2026-10-06 11:47:51 +00:00
Backend tests (9 new files): - summary-service, metrics-helpers, template-service - status-history-service, trace-service, diff-service - conflict-service, preview-service, digest-service - Coverage: 44.27% → 53.52% (897 tests passing) E2E tests (fixed + hardened): - Removed debug spec left by sub-agent - API seed calls now hit backend directly (port 3001) to avoid IPv6/Vite proxy issues on macOS - task-status tests use unique names (prevent strict mode) - Added waitForResponse for status change assertions - Enabled webServer auto-start in playwright.config.ts - All 19 E2E tests passing
128 lines
4.4 KiB
TypeScript
128 lines
4.4 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { bypassAuth, seedTestTask, deleteTask, cleanupRoutes } from './helpers/auth';
|
|
|
|
test.describe('Task Status Change', () => {
|
|
let testTaskId: string | null = null;
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await bypassAuth(page);
|
|
});
|
|
|
|
test.afterEach(async ({ page }) => {
|
|
if (testTaskId) {
|
|
await deleteTask(page, testTaskId).catch(() => {});
|
|
testTaskId = null;
|
|
}
|
|
await cleanupRoutes(page);
|
|
});
|
|
|
|
test('change task status via detail panel dropdown', async ({ page }) => {
|
|
const uniqueTitle = `E2E Status Change ${Date.now()}`;
|
|
|
|
const task = await seedTestTask(page, {
|
|
title: uniqueTitle,
|
|
status: 'todo',
|
|
priority: 'medium',
|
|
});
|
|
testTaskId = (task as { id: string }).id;
|
|
|
|
await page.goto('/');
|
|
|
|
// Verify the task is in the To Do column
|
|
const todoColumn = page.getByRole('region', { name: /To Do column/ });
|
|
await expect(todoColumn.locator(`text=${uniqueTitle}`)).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
|
|
// Click the task to open the detail panel
|
|
await todoColumn.locator(`text=${uniqueTitle}`).click();
|
|
|
|
const detailPanel = page.locator('[role="dialog"]');
|
|
await expect(detailPanel).toBeVisible({ timeout: 5_000 });
|
|
|
|
// The metadata section has a grid: Status | Type | Priority
|
|
// Status is the first Select in the grid
|
|
const statusSection = detailPanel.locator('label:has-text("Status")').locator('..');
|
|
const statusTrigger = statusSection.locator('button[role="combobox"]');
|
|
await expect(statusTrigger).toBeVisible();
|
|
await statusTrigger.click();
|
|
|
|
// Select "In Progress" and wait for the API PATCH response
|
|
const inProgressOption = page.getByRole('option', { name: 'In Progress' });
|
|
await expect(inProgressOption).toBeVisible();
|
|
|
|
const [patchResponse] = await Promise.all([
|
|
page.waitForResponse(
|
|
(resp) => resp.url().includes('/api/tasks/') && resp.request().method() === 'PATCH',
|
|
{ timeout: 10_000 }
|
|
),
|
|
inProgressOption.click(),
|
|
]);
|
|
|
|
// Ensure the PATCH succeeded
|
|
expect(patchResponse.status()).toBeLessThan(400);
|
|
|
|
// Close the detail panel
|
|
await page.keyboard.press('Escape');
|
|
await expect(detailPanel).not.toBeVisible({ timeout: 3_000 });
|
|
|
|
// Wait for the task to move to the In Progress column
|
|
const inProgressColumn = page.getByRole('region', { name: /In Progress column/ });
|
|
await expect(inProgressColumn.locator(`text=${uniqueTitle}`)).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
|
|
// Verify it's no longer in the To Do column
|
|
await expect(todoColumn.locator(`text=${uniqueTitle}`)).not.toBeVisible();
|
|
});
|
|
|
|
test('change task status to done via detail panel', async ({ page }) => {
|
|
const uniqueTitle = `E2E Done Task ${Date.now()}`;
|
|
|
|
const task = await seedTestTask(page, {
|
|
title: uniqueTitle,
|
|
status: 'in-progress',
|
|
priority: 'low',
|
|
});
|
|
testTaskId = (task as { id: string }).id;
|
|
|
|
await page.goto('/');
|
|
|
|
// Verify the task starts in In Progress
|
|
const inProgressCol = page.getByRole('region', { name: /In Progress column/ });
|
|
await expect(inProgressCol.locator(`text=${uniqueTitle}`)).toBeVisible({ timeout: 15_000 });
|
|
|
|
// Open the detail panel
|
|
await inProgressCol.locator(`text=${uniqueTitle}`).click();
|
|
|
|
const detailPanel = page.locator('[role="dialog"]');
|
|
await expect(detailPanel).toBeVisible({ timeout: 5_000 });
|
|
|
|
// Find the Status dropdown
|
|
const statusSection = detailPanel.locator('label:has-text("Status")').locator('..');
|
|
const statusTrigger = statusSection.locator('button[role="combobox"]');
|
|
await expect(statusTrigger).toBeVisible();
|
|
await statusTrigger.click();
|
|
|
|
// Select "Done" and wait for the API PATCH
|
|
const doneOption = page.getByRole('option', { name: 'Done' });
|
|
await expect(doneOption).toBeVisible();
|
|
|
|
const [patchResponse] = await Promise.all([
|
|
page.waitForResponse(
|
|
(resp) => resp.url().includes('/api/tasks/') && resp.request().method() === 'PATCH',
|
|
{ timeout: 10_000 }
|
|
),
|
|
doneOption.click(),
|
|
]);
|
|
|
|
expect(patchResponse.status()).toBeLessThan(400);
|
|
|
|
// Close panel
|
|
await page.keyboard.press('Escape');
|
|
|
|
// Verify the task moved to Done
|
|
const doneCol = page.getByRole('region', { name: /Done column/ });
|
|
await expect(doneCol.locator(`text=${uniqueTitle}`)).toBeVisible({ timeout: 10_000 });
|
|
});
|
|
});
|