Add a vitest suite that enforces critical layout invariants for the quote detail page, and include accompanying Playwright trace snapshots and console logs from the test execution.
84 lines
3.6 KiB
TypeScript
84 lines
3.6 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { readFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
|
|
/**
|
|
* Layout contract regression test for the quote detail page.
|
|
*
|
|
* The page is a large Server Component that pulls from many PocketBase
|
|
* collections, calls multiple API endpoints, and has many modals — too
|
|
* heavy to mount in a unit test. Instead, this test guards the layout
|
|
* invariants that were negotiated with the operator during the
|
|
* redesign so a future edit can't silently regress them:
|
|
*
|
|
* 1. Customer info lives in a header strip (not a sidebar card).
|
|
* 2. The original email is a collapsible card that sits ABOVE the
|
|
* items table (the operator's main complaint — "email right at
|
|
* the bottom?").
|
|
* 3. The right sidebar consolidates History + Email Status into a
|
|
* single tabbed "Activity" card so the column is shorter.
|
|
* 4. The original email section does NOT also appear at the bottom
|
|
* of the page (was duplicated).
|
|
*
|
|
* Each invariant is anchored to a stable `data-testid` or
|
|
* `aria-controls` marker that the page already emits — so the source
|
|
* test reads as a contract, not as a regex grep over prose comments.
|
|
*/
|
|
|
|
const SOURCE = readFileSync(
|
|
resolve(__dirname, "page.tsx"),
|
|
"utf8",
|
|
);
|
|
|
|
function linesContaining(marker: string): number[] {
|
|
const hits: number[] = [];
|
|
SOURCE.split("\n").forEach((line, i) => {
|
|
if (line.includes(marker)) hits.push(i + 1);
|
|
});
|
|
return hits;
|
|
}
|
|
|
|
describe("quote detail page — layout contract", () => {
|
|
it("exposes the original-email toggle via aria-controls", () => {
|
|
// The email card above items uses a button with aria-controls so the
|
|
// operator can show/hide the original message without scrolling
|
|
// away from the items table.
|
|
const hits = linesContaining('aria-controls="quote-email-body"');
|
|
expect(hits.length).toBeGreaterThanOrEqual(1);
|
|
});
|
|
|
|
it("renders the email toggle BEFORE the items table marker", () => {
|
|
// The user complaint was "email right at the bottom". Lock the
|
|
// DOM ordering so the toggle card stays in the upper flow.
|
|
const emailToggle = linesContaining('aria-controls="quote-email-body"')[0];
|
|
const itemsHeading = SOURCE.indexOf("Items (");
|
|
expect(itemsHeading).toBeGreaterThan(-1);
|
|
// Convert char offset to a line number for the assertion message.
|
|
const itemsLine = SOURCE.slice(0, itemsHeading).split("\n").length;
|
|
expect(emailToggle).toBeLessThan(itemsLine);
|
|
});
|
|
|
|
it("consolidates History + Email Status into a tabbed Activity card", () => {
|
|
// The Activity card is what replaced the two standalone cards.
|
|
// Both triggers need a stable data-testid so the regression is
|
|
// locatable by future tests / e2e checks.
|
|
expect(linesContaining('data-testid="tab-email-status"').length).toBe(1);
|
|
expect(linesContaining('data-testid="tab-history"').length).toBe(1);
|
|
expect(SOURCE).toContain("Activity");
|
|
});
|
|
|
|
it("does NOT render the original email a second time at the bottom of the page", () => {
|
|
// During the redesign we moved the original email to a collapsible
|
|
// card above the items and removed the old bottom section. Lock
|
|
// that removal: only ONE `quote-original-request` anchor is allowed.
|
|
const matches = SOURCE.match(/quote-original-request/g) ?? [];
|
|
expect(matches.length).toBe(0);
|
|
});
|
|
|
|
it("history rows render inside a data-testid=history-list anchor", () => {
|
|
// The Activity > History tab's <ul> needs a stable marker so a
|
|
// future test can count the rows without depending on Tailwind
|
|
// class strings.
|
|
expect(linesContaining('data-testid="history-list"').length).toBe(1);
|
|
});
|
|
});
|