Story 27: Coverage tracking (full-stack)
Add end-to-end coverage tracking: backend collects vitest coverage, records metrics with threshold/baseline tracking, and blocks acceptance on regression. Frontend displays coverage in gate/review panels with a "Collect Coverage" button. Includes 20 Rust tests, 17 Vitest tests, and 14 Playwright E2E tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,8 @@ vi.mock("../api/workflow", () => {
|
||||
getReviewQueue: vi.fn(),
|
||||
getReviewQueueAll: vi.fn(),
|
||||
ensureAcceptance: vi.fn(),
|
||||
recordCoverage: vi.fn(),
|
||||
collectCoverage: vi.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -390,4 +392,122 @@ describe("Chat review panel", () => {
|
||||
|
||||
expect(await screen.findByText("Ready to accept")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows coverage below threshold in gate panel (AC3)", async () => {
|
||||
mockedWorkflow.getAcceptance.mockResolvedValueOnce({
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
render(<Chat projectPath="/tmp/project" onCloseProject={vi.fn()} />);
|
||||
|
||||
expect(await screen.findByText("Blocked")).toBeInTheDocument();
|
||||
expect(await screen.findByText(/Coverage: 55\.0%/)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/threshold: 80\.0%/)).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText("Coverage below threshold (55.0% < 80.0%)."),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows coverage regression in review panel (AC4)", async () => {
|
||||
const story: ReviewStory = {
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
mockedWorkflow.getReviewQueueAll.mockResolvedValueOnce({
|
||||
stories: [story],
|
||||
});
|
||||
|
||||
render(<Chat projectPath="/tmp/project" onCloseProject={vi.fn()} />);
|
||||
|
||||
expect(
|
||||
await screen.findByText(
|
||||
"Coverage regression: 90.0% → 82.0% (threshold: 80.0%).",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByText(/Coverage: 82\.0%/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows green coverage when above threshold (AC3)", async () => {
|
||||
mockedWorkflow.getAcceptance.mockResolvedValueOnce({
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
render(<Chat projectPath="/tmp/project" onCloseProject={vi.fn()} />);
|
||||
|
||||
expect(await screen.findByText("Ready to accept")).toBeInTheDocument();
|
||||
expect(await screen.findByText(/Coverage: 92\.0%/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("collect coverage button triggers collection and refreshes gate", async () => {
|
||||
const mockedCollectCoverage = vi.mocked(workflowApi.collectCoverage);
|
||||
mockedCollectCoverage.mockResolvedValueOnce({
|
||||
current_percent: 85.0,
|
||||
threshold_percent: 80.0,
|
||||
baseline_percent: null,
|
||||
});
|
||||
|
||||
mockedWorkflow.getAcceptance
|
||||
.mockResolvedValueOnce({
|
||||
can_accept: false,
|
||||
reasons: ["No test results recorded for the story."],
|
||||
warning: null,
|
||||
summary: { total: 0, passed: 0, failed: 0 },
|
||||
missing_categories: ["unit", "integration"],
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
render(<Chat projectPath="/tmp/project" onCloseProject={vi.fn()} />);
|
||||
|
||||
const collectButton = await screen.findByRole("button", {
|
||||
name: "Collect Coverage",
|
||||
});
|
||||
await userEvent.click(collectButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedCollectCoverage).toHaveBeenCalledWith({
|
||||
story_id: "26_establish_tdd_workflow_and_gates",
|
||||
});
|
||||
});
|
||||
|
||||
expect(await screen.findByText(/Coverage: 85\.0%/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user