diff --git a/frontend/tests/e2e/review-panel.spec.ts b/frontend/tests/e2e/smoke.spec.ts similarity index 100% rename from frontend/tests/e2e/review-panel.spec.ts rename to frontend/tests/e2e/smoke.spec.ts diff --git a/frontend/tests/e2e/story-todos.spec.ts b/frontend/tests/e2e/story-todos.spec.ts deleted file mode 100644 index f84663c..0000000 --- a/frontend/tests/e2e/story-todos.spec.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { expect, test } from "@playwright/test"; -import type { - AcceptanceResponse, - ReviewListResponse, - TodoListResponse, -} from "../../src/api/workflow"; - -function mockChatApis( - page: import("@playwright/test").Page, - overrides: { - acceptance?: AcceptanceResponse; - reviewQueue?: ReviewListResponse; - todos?: TodoListResponse; - } = {}, -) { - const acceptance: AcceptanceResponse = overrides.acceptance ?? { - can_accept: false, - reasons: ["No test results recorded for the story."], - warning: null, - summary: { total: 0, passed: 0, failed: 0 }, - missing_categories: ["unit", "integration"], - }; - - const reviewQueue: ReviewListResponse = overrides.reviewQueue ?? { - stories: [], - }; - - const todos: TodoListResponse = overrides.todos ?? { - stories: [], - }; - - return Promise.all([ - page.route("**/api/projects", (route) => - route.fulfill({ json: ["/tmp/test-project"] }), - ), - page.route("**/api/io/fs/home", (route) => - route.fulfill({ json: "/tmp" }), - ), - page.route("**/api/project", (route) => { - if (route.request().method() === "POST") { - return route.fulfill({ json: "/tmp/test-project" }); - } - if (route.request().method() === "DELETE") { - return route.fulfill({ json: true }); - } - return route.fulfill({ json: null }); - }), - page.route("**/api/ollama/models**", (route) => - route.fulfill({ json: ["llama3.1"] }), - ), - page.route("**/api/anthropic/key/exists", (route) => - route.fulfill({ json: false }), - ), - page.route("**/api/anthropic/models", (route) => - route.fulfill({ json: [] }), - ), - page.route("**/api/model", (route) => { - if (route.request().method() === "POST") { - return route.fulfill({ json: true }); - } - return route.fulfill({ json: null }); - }), - page.route("**/api/workflow/acceptance", (route) => { - if (route.request().url().includes("/ensure")) return route.fallback(); - return route.fulfill({ json: acceptance }); - }), - page.route("**/api/workflow/review/all", (route) => - route.fulfill({ json: reviewQueue }), - ), - page.route("**/api/workflow/acceptance/ensure", (route) => - route.fulfill({ json: true }), - ), - page.route("**/api/io/fs/list/absolute**", (route) => - route.fulfill({ json: [] }), - ), - page.route("**/api/workflow/todos", (route) => - route.fulfill({ json: todos }), - ), - ]); -} - -async function openProject(page: import("@playwright/test").Page) { - await page.goto("/"); - await page.getByPlaceholder("/path/to/project").fill("/tmp/test-project"); - await page.getByRole("button", { name: "Open Project" }).click(); - await expect(page.getByText("Story TODOs", { exact: true })).toBeVisible(); -} - -test.describe("Story TODOs panel", () => { - test("shows unchecked acceptance criteria", async ({ page }) => { - await mockChatApis(page, { - todos: { - stories: [ - { - story_id: "28_ui_show_test_todos", - story_name: "Show Remaining Test TODOs in the UI", - todos: [ - "The UI lists unchecked acceptance criteria.", - "Each TODO is displayed as its full text.", - ], - error: null, - }, - ], - }, - }); - - await openProject(page); - - await expect( - page.getByText("The UI lists unchecked acceptance criteria."), - ).toBeVisible(); - await expect( - page.getByText("Each TODO is displayed as its full text."), - ).toBeVisible(); - await expect(page.getByText("2 remaining")).toBeVisible(); - }); - - test("shows completion message when all criteria are checked", async ({ - page, - }) => { - await mockChatApis(page, { - todos: { - stories: [ - { - story_id: "28_ui_show_test_todos", - story_name: "Show Remaining Test TODOs in the UI", - todos: [], - error: null, - }, - ], - }, - }); - - await openProject(page); - - await expect( - page.getByText("All acceptance criteria complete."), - ).toBeVisible(); - await expect(page.getByText("0 remaining")).toBeVisible(); - }); - - test("shows per-story front matter error", async ({ page }) => { - await mockChatApis(page, { - todos: { - stories: [ - { - story_id: "28_ui_show_test_todos", - story_name: null, - todos: [], - error: "Missing front matter", - }, - ], - }, - }); - - await openProject(page); - - await expect(page.getByText("Missing front matter")).toBeVisible(); - await expect(page.getByText("28_ui_show_test_todos")).toBeVisible(); - }); - - test("shows TODO items from multiple stories", async ({ page }) => { - await mockChatApis(page, { - todos: { - stories: [ - { - story_id: "28_ui_show_test_todos", - story_name: "Show TODOs", - todos: ["First criterion."], - error: null, - }, - { - story_id: "29_another_story", - story_name: "Another Story", - todos: ["Second criterion."], - error: null, - }, - ], - }, - }); - - await openProject(page); - - await expect(page.getByText("First criterion.")).toBeVisible(); - await expect(page.getByText("Second criterion.")).toBeVisible(); - await expect(page.getByText("2 remaining")).toBeVisible(); - await expect(page.getByText("Show TODOs")).toBeVisible(); - await expect(page.getByText("Another Story")).toBeVisible(); - }); -}); diff --git a/frontend/tests/e2e/tdd-gates.spec.ts b/frontend/tests/e2e/tdd-gates.spec.ts deleted file mode 100644 index 0f24d6f..0000000 --- a/frontend/tests/e2e/tdd-gates.spec.ts +++ /dev/null @@ -1,347 +0,0 @@ -import { expect, test } from "@playwright/test"; -import type { - AcceptanceResponse, - ReviewListResponse, -} from "../../src/api/workflow"; - -/** - * Helper: mock all API routes needed to render the Chat view. - * Accepts overrides for the workflow-specific endpoints. - */ -function mockChatApis( - page: import("@playwright/test").Page, - overrides: { - acceptance?: AcceptanceResponse; - reviewQueue?: ReviewListResponse; - ensureAcceptance?: { status: number; body: unknown }; - } = {}, -) { - const acceptance: AcceptanceResponse = overrides.acceptance ?? { - can_accept: false, - reasons: ["No test results recorded for the story."], - warning: null, - summary: { total: 0, passed: 0, failed: 0 }, - missing_categories: ["unit", "integration"], - }; - - const reviewQueue: ReviewListResponse = overrides.reviewQueue ?? { - stories: [], - }; - - const ensureResp = overrides.ensureAcceptance ?? { - status: 200, - body: true, - }; - - return Promise.all([ - // Selection screen APIs - page.route("**/api/projects", (route) => - route.fulfill({ json: ["/tmp/test-project"] }), - ), - page.route("**/api/io/fs/home", (route) => route.fulfill({ json: "/tmp" })), - page.route("**/api/project", (route) => { - if (route.request().method() === "POST") { - return route.fulfill({ json: "/tmp/test-project" }); - } - if (route.request().method() === "DELETE") { - return route.fulfill({ json: true }); - } - return route.fulfill({ json: null }); - }), - // Chat init APIs - page.route("**/api/ollama/models**", (route) => - route.fulfill({ json: ["llama3.1"] }), - ), - page.route("**/api/anthropic/key/exists", (route) => - route.fulfill({ json: false }), - ), - page.route("**/api/anthropic/models", (route) => - route.fulfill({ json: [] }), - ), - page.route("**/api/model", (route) => { - if (route.request().method() === "POST") { - return route.fulfill({ json: true }); - } - return route.fulfill({ json: null }); - }), - // Workflow APIs - page.route("**/api/workflow/acceptance", (route) => { - if (route.request().url().includes("/ensure")) return route.fallback(); - return route.fulfill({ json: acceptance }); - }), - page.route("**/api/workflow/review/all", (route) => - route.fulfill({ json: reviewQueue }), - ), - page.route("**/api/workflow/acceptance/ensure", (route) => - route.fulfill({ - status: ensureResp.status, - json: ensureResp.body, - }), - ), - page.route("**/api/io/fs/list/absolute**", (route) => - route.fulfill({ json: [] }), - ), - page.route("**/api/workflow/todos", (route) => - route.fulfill({ json: { stories: [] } }), - ), - ]); -} - -/** Navigate past the selection screen into the Chat view. */ -async function openProject(page: import("@playwright/test").Page) { - await page.goto("/"); - await page.getByPlaceholder("/path/to/project").fill("/tmp/test-project"); - await page.getByRole("button", { name: "Open Project" }).click(); - // Wait for the Chat view to appear - await expect(page.getByText("Workflow Gates", { exact: true })).toBeVisible(); -} - -test.describe("TDD gate panel (AC1/AC3)", () => { - test("shows Blocked status with reasons when no test plan is approved", async ({ - page, - }) => { - await mockChatApis(page, { - acceptance: { - can_accept: false, - reasons: ["No approved test plan for the story."], - warning: null, - summary: { total: 0, passed: 0, failed: 0 }, - missing_categories: ["unit", "integration"], - }, - }); - - await openProject(page); - - await expect( - page.getByText("Workflow Gates", { exact: true }), - ).toBeVisible(); - await expect(page.getByText("Blocked").first()).toBeVisible(); - await expect( - page.getByText("No approved test plan for the story."), - ).toBeVisible(); - await expect( - page.getByText("Missing: unit, integration").first(), - ).toBeVisible(); - }); - - test("shows Ready to accept when all gates pass", async ({ page }) => { - await mockChatApis(page, { - acceptance: { - can_accept: true, - reasons: [], - warning: null, - summary: { total: 5, passed: 5, failed: 0 }, - missing_categories: [], - }, - }); - - await openProject(page); - - await expect(page.getByText("Ready to accept")).toBeVisible(); - await expect(page.getByText(/5\/5 passing, 0 failing/)).toBeVisible(); - }); -}); - -test.describe("Acceptance blocked by failing tests (AC4)", () => { - test("Proceed button is disabled and shows Blocked when tests fail", async ({ - page, - }) => { - await mockChatApis(page, { - acceptance: { - can_accept: false, - reasons: ["Tests are failing."], - warning: null, - summary: { total: 4, passed: 2, failed: 2 }, - missing_categories: [], - }, - reviewQueue: { - stories: [ - { - story_id: "26_establish_tdd_workflow_and_gates", - can_accept: false, - reasons: ["Tests are failing."], - warning: null, - summary: { total: 4, passed: 2, failed: 2 }, - missing_categories: [], - }, - ], - }, - }); - - await openProject(page); - - const blockedButton = page.getByRole("button", { name: "Blocked" }); - await expect(blockedButton).toBeVisible(); - await expect(blockedButton).toBeDisabled(); - - await expect(page.getByText("Tests are failing.").first()).toBeVisible(); - await expect(page.getByText(/2\/4 passing/).first()).toBeVisible(); - }); - - test("Proceed button is disabled when missing test categories", async ({ - page, - }) => { - await mockChatApis(page, { - reviewQueue: { - stories: [ - { - story_id: "26_establish_tdd_workflow_and_gates", - can_accept: false, - reasons: ["Missing required test categories."], - warning: null, - summary: { total: 0, passed: 0, failed: 0 }, - missing_categories: ["unit", "integration"], - }, - ], - }, - }); - - await openProject(page); - - const blockedButton = page.getByRole("button", { name: "Blocked" }); - await expect(blockedButton).toBeDisabled(); - await expect(page.getByText("Missing").first()).toBeVisible(); - }); -}); - -test.describe("Red test count and warnings (AC5)", () => { - test("shows Failing badge with count when tests fail", async ({ page }) => { - await mockChatApis(page, { - reviewQueue: { - stories: [ - { - story_id: "26_establish_tdd_workflow_and_gates", - can_accept: false, - reasons: ["3 tests are failing."], - warning: "Multiple tests failing — fix one at a time.", - summary: { total: 5, passed: 2, failed: 3 }, - missing_categories: [], - }, - ], - }, - }); - - await openProject(page); - - await expect(page.getByText("Failing 3")).toBeVisible(); - await expect(page.getByText("Warning")).toBeVisible(); - await expect( - page.getByText("Multiple tests failing — fix one at a time."), - ).toBeVisible(); - }); - - test("gate panel shows warning for multiple failing tests", async ({ - page, - }) => { - await mockChatApis(page, { - acceptance: { - can_accept: false, - reasons: ["2 tests are failing."], - warning: "Multiple tests failing — fix one at a time.", - summary: { total: 4, passed: 2, failed: 2 }, - missing_categories: [], - }, - }); - - await openProject(page); - - await expect( - page.getByText("Multiple tests failing — fix one at a time."), - ).toBeVisible(); - await expect(page.getByText(/2\/4 passing, 2 failing/)).toBeVisible(); - }); -}); - -test.describe("Blocked actions do not execute (E2E)", () => { - test("clicking a blocked Proceed button does not call ensureAcceptance", async ({ - page, - }) => { - let ensureCalled = false; - - await mockChatApis(page, { - reviewQueue: { - stories: [ - { - story_id: "26_establish_tdd_workflow_and_gates", - can_accept: false, - reasons: ["Tests are failing."], - warning: null, - summary: { total: 3, passed: 1, failed: 2 }, - missing_categories: [], - }, - ], - }, - }); - - // Override the ensure route to track calls - await page.route("**/api/workflow/acceptance/ensure", (route) => { - ensureCalled = true; - return route.fulfill({ json: true }); - }); - - await openProject(page); - - const blockedButton = page.getByRole("button", { name: "Blocked" }); - await expect(blockedButton).toBeDisabled(); - - // Force-click to attempt bypass — should still not fire - await blockedButton.click({ force: true }); - - // Give a moment for any potential network call - await page.waitForTimeout(500); - - expect(ensureCalled).toBe(false); - }); - - test("ready story proceeds successfully", async ({ page }) => { - let ensureCalled = false; - - await mockChatApis(page, { - acceptance: { - can_accept: true, - reasons: [], - warning: null, - summary: { total: 3, passed: 3, failed: 0 }, - missing_categories: [], - }, - reviewQueue: { - stories: [ - { - story_id: "26_establish_tdd_workflow_and_gates", - can_accept: true, - reasons: [], - warning: null, - summary: { total: 3, passed: 3, failed: 0 }, - missing_categories: [], - }, - ], - }, - ensureAcceptance: { status: 200, body: true }, - }); - - // Intercept ensure to track it was called - await page.route("**/api/workflow/acceptance/ensure", (route) => { - ensureCalled = true; - return route.fulfill({ json: true }); - }); - - await openProject(page); - - const proceedButton = page.getByRole("button", { name: "Proceed" }); - await expect(proceedButton).toBeEnabled(); - - // Before clicking, swap review queue to return empty on next fetch - await page.unroute("**/api/workflow/review/all"); - await page.route("**/api/workflow/review/all", (route) => - route.fulfill({ json: { stories: [] } }), - ); - - await proceedButton.click(); - - // After proceed, the review queue refreshes — now returns empty - await expect( - page.getByText("No stories waiting for review."), - ).toBeVisible(); - expect(ensureCalled).toBe(true); - }); -}); diff --git a/frontend/tests/e2e/test-protection.spec.ts b/frontend/tests/e2e/test-protection.spec.ts deleted file mode 100644 index f4834bd..0000000 --- a/frontend/tests/e2e/test-protection.spec.ts +++ /dev/null @@ -1,250 +0,0 @@ -import { expect, test } from "@playwright/test"; -import type { - AcceptanceResponse, - ReviewListResponse, -} from "../../src/api/workflow"; - -/** - * Helper: mock all API routes needed to render the Chat view. - */ -function mockChatApis( - page: import("@playwright/test").Page, - overrides: { - acceptance?: AcceptanceResponse; - reviewQueue?: ReviewListResponse; - } = {}, -) { - const acceptance: AcceptanceResponse = overrides.acceptance ?? { - can_accept: false, - reasons: ["No test results recorded for the story."], - warning: null, - summary: { total: 0, passed: 0, failed: 0 }, - missing_categories: ["unit", "integration"], - }; - - const reviewQueue: ReviewListResponse = overrides.reviewQueue ?? { - stories: [], - }; - - return Promise.all([ - page.route("**/api/projects", (route) => - route.fulfill({ json: ["/tmp/test-project"] }), - ), - page.route("**/api/io/fs/home", (route) => route.fulfill({ json: "/tmp" })), - page.route("**/api/project", (route) => { - if (route.request().method() === "POST") { - return route.fulfill({ json: "/tmp/test-project" }); - } - if (route.request().method() === "DELETE") { - return route.fulfill({ json: true }); - } - return route.fulfill({ json: null }); - }), - page.route("**/api/ollama/models**", (route) => - route.fulfill({ json: ["llama3.1"] }), - ), - page.route("**/api/anthropic/key/exists", (route) => - route.fulfill({ json: false }), - ), - page.route("**/api/anthropic/models", (route) => - route.fulfill({ json: [] }), - ), - page.route("**/api/model", (route) => { - if (route.request().method() === "POST") { - return route.fulfill({ json: true }); - } - return route.fulfill({ json: null }); - }), - page.route("**/api/workflow/acceptance", (route) => { - if (route.request().url().includes("/ensure")) return route.fallback(); - return route.fulfill({ json: acceptance }); - }), - page.route("**/api/workflow/review/all", (route) => - route.fulfill({ json: reviewQueue }), - ), - page.route("**/api/workflow/acceptance/ensure", (route) => - route.fulfill({ json: true }), - ), - page.route("**/api/io/fs/list/absolute**", (route) => - route.fulfill({ json: [] }), - ), - page.route("**/api/workflow/todos", (route) => - route.fulfill({ json: { stories: [] } }), - ), - ]); -} - -async function openProject(page: import("@playwright/test").Page) { - await page.goto("/"); - await page.getByPlaceholder("/path/to/project").fill("/tmp/test-project"); - await page.getByRole("button", { name: "Open Project" }).click(); - await expect(page.getByText("Workflow Gates", { exact: true })).toBeVisible(); -} - -test.describe("Coverage threshold (AC1)", () => { - test("shows blocked status when coverage is below threshold", async ({ - page, - }) => { - await mockChatApis(page, { - acceptance: { - can_accept: false, - reasons: ["Coverage below threshold (55.0% < 80.0%)."], - warning: null, - summary: { total: 3, passed: 3, failed: 0 }, - missing_categories: [], - coverage_report: { - current_percent: 55.0, - threshold_percent: 80.0, - baseline_percent: null, - }, - }, - }); - - await openProject(page); - - await expect(page.getByText("Blocked").first()).toBeVisible(); - await expect(page.getByText(/Coverage: 55\.0%/)).toBeVisible(); - await expect(page.getByText(/threshold: 80\.0%/)).toBeVisible(); - await expect( - page.getByText("Coverage below threshold (55.0% < 80.0%)."), - ).toBeVisible(); - }); - - test("shows green coverage when above threshold", async ({ page }) => { - await mockChatApis(page, { - acceptance: { - can_accept: true, - reasons: [], - warning: null, - summary: { total: 5, passed: 5, failed: 0 }, - missing_categories: [], - coverage_report: { - current_percent: 92.0, - threshold_percent: 80.0, - baseline_percent: 90.0, - }, - }, - }); - - await openProject(page); - - await expect(page.getByText("Ready to accept")).toBeVisible(); - await expect(page.getByText(/Coverage: 92\.0%/)).toBeVisible(); - }); -}); - -test.describe("Coverage regression reporting (AC2)", () => { - test("review panel shows coverage regression reason", async ({ page }) => { - await mockChatApis(page, { - reviewQueue: { - stories: [ - { - story_id: "27_protect_tests_and_coverage", - can_accept: false, - reasons: ["Coverage regression: 90.0% → 82.0% (threshold: 80.0%)."], - warning: null, - summary: { total: 4, passed: 4, failed: 0 }, - missing_categories: [], - coverage_report: { - current_percent: 82.0, - threshold_percent: 80.0, - baseline_percent: 90.0, - }, - }, - ], - }, - }); - - await openProject(page); - - await expect( - page.getByText(/Coverage regression.*90\.0%.*82\.0%/), - ).toBeVisible(); - await expect(page.getByText(/Coverage: 82\.0%/)).toBeVisible(); - - const blockedButton = page.getByRole("button", { name: "Blocked" }); - await expect(blockedButton).toBeDisabled(); - }); - - test("blocked acceptance with coverage and test failures combined", async ({ - page, - }) => { - await mockChatApis(page, { - acceptance: { - can_accept: false, - reasons: [ - "1 test(s) are failing; acceptance is blocked.", - "Coverage below threshold (65.0% < 80.0%).", - ], - warning: null, - summary: { total: 4, passed: 3, failed: 1 }, - missing_categories: [], - coverage_report: { - current_percent: 65.0, - threshold_percent: 80.0, - baseline_percent: null, - }, - }, - }); - - await openProject(page); - - await expect(page.getByText("Blocked").first()).toBeVisible(); - await expect( - page.getByText("Coverage below threshold (65.0% < 80.0%)."), - ).toBeVisible(); - await expect(page.getByText(/Coverage: 65\.0%/)).toBeVisible(); - await expect(page.getByText(/1 test\(s\) are failing/)).toBeVisible(); - }); -}); - -test.describe("Coverage collection (E2E)", () => { - test("collect coverage button triggers collection and displays result", async ({ - page, - }) => { - await mockChatApis(page); - - // Mock the collect coverage endpoint - await page.route("**/api/workflow/coverage/collect", (route) => - route.fulfill({ - json: { - current_percent: 85.0, - threshold_percent: 80.0, - baseline_percent: null, - }, - }), - ); - - await openProject(page); - - // Click the Collect Coverage button - const collectButton = page.getByRole("button", { - name: "Collect Coverage", - }); - await expect(collectButton).toBeVisible(); - - // Override acceptance to return coverage data after collection - await page.unroute("**/api/workflow/acceptance"); - await page.route("**/api/workflow/acceptance", (route) => { - if (route.request().url().includes("/ensure")) return route.fallback(); - return route.fulfill({ - json: { - can_accept: true, - reasons: [], - warning: null, - summary: { total: 5, passed: 5, failed: 0 }, - missing_categories: [], - coverage_report: { - current_percent: 85.0, - threshold_percent: 80.0, - baseline_percent: null, - }, - }, - }); - }); - - await collectButton.click(); - - await expect(page.getByText(/Coverage: 85\.0%/)).toBeVisible(); - }); -});