Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/coverage-acks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,34 @@
# coverage-ack: frontend/tests/Foo.spec.ts::some describe > * — reason
#
# Lines starting with `#` and blank lines are ignored.

# Proposal H parked-spec port wave (2026-07-24, issue #272) - owner-ruled drops (file deleted,
# not just re-skipped). Full parity/upstreamability tables live in this wave's own PR body, not
# duplicated here - one line per exception, per this file's own convention above.

# Owner-ruled DROP: the aria-snapshot coverage of the classic grid's per-slot common-cardback
# swatch has no unified-page replacement to port onto (CommonCardback.tsx's own editor mount is
# classic-/editor-only, fully unrouted since the Proposal H swap) - the owner's replacement UX
# (an export-time reminder) is a separate, not-yet-built design, tracked apart from this wave.
coverage-ack: frontend/tests/visual/CommonCardback.visual.spec.ts::* — owner-ruled DROP, replacement UX is a separate not-yet-built design (see PR body)

# Owner-ruled DROP: all 12 tests cover bulk multi-select editing (select N slots, then bulk
# change-query/change-image/delete/select-similar/select-all) - a real, tracked capability gap on
# the unified /editor page (issue #272 item 6, no bulk-select surface exists there at all today),
# not a redundant or obsolete test. Same finding CardSlot.spec.ts's own wave-3 port comment
# already documents independently.
coverage-ack: frontend/tests/SelectedImagesRibbon.spec.ts::* — owner-ruled DROP, bulk multi-select has no unified-page equivalent yet (tracked gap, issue #272 item 6 - see PR body)

# Owner-ruled DROP: all 4 tests assert CSS/testids (left-panel/right-panel/add-cards-panel,
# OverflowCol's chainScroll overscroll-behavior-y) that only exist on the classic, fully-unrouted
# grid layout. The unified page's own mobile fix (issue #266) is a bottom-sheet drawer, not
# stacked-panel scroll chaining, and already has its own dedicated coverage in DisplayPage.spec.ts's
# "phone viewport (issue #266)" describe block.
coverage-ack: frontend/tests/ProjectEditorMobileScroll.spec.ts::* — owner-ruled DROP, subject CSS/testids only exist on the classic unrouted layout; #266's own mobile fix has separate coverage (see PR body)

# Un-skip + re-home port of PostExportContributionPrompt.spec.ts (issue #166) onto /print's own
# PDFGenerator mount - renamed off its stale "classic Print! tab" title (the classic grid is
# fully unrouted; PDF generation lives solely on /print now) in the same change, same convention
# PR #337 established for this file's title staleness pattern. The new title carries the
# unchanged behavior forward (un-skipped, not removed) at a new, non-stale title.
coverage-ack: frontend/tests/PostExportContributionPrompt.spec.ts::Post-export contribution prompt (issue #166) - classic Print! tab > also appears after a successful export from PDFGenerator.tsx's own classic tab — renamed + un-skipped in the same change, see the file's own new title
5 changes: 4 additions & 1 deletion .github/scripts/tests/test_coverage_delta.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,10 @@ def test_real_manifest_is_non_empty_and_has_known_skips(self):
manifest = cd.build_manifest_from_worktree(REPO_ROOT)
self.assertGreater(len(manifest), 100)
skipped_files = {e.file for e in manifest.values() if e.skip}
self.assertIn("frontend/tests/PDFGenerator.spec.ts", skipped_files)
# PDFGenerator.spec.ts/PagePreview.spec.ts/PostExportContributionPrompt.spec.ts were
# un-skipped by the 2026-07-24 parked-spec port wave (issue #272) - CardImageStates.spec.ts
# is still fully skipped pending its own port, so it's the fixture here now.
self.assertIn("frontend/tests/CardImageStates.spec.ts", skipped_files)

def test_perf_dir_is_excluded(self):
manifest = cd.build_manifest_from_worktree(REPO_ROOT)
Expand Down
25 changes: 16 additions & 9 deletions docs/features/pdf-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,17 @@ reached from `/display`'s Finish footer via a pre-print save gate; see
Footer](../proposals/proposal-h-display-layout-spec.md#finish-footer-save-before-print)
and [Print-Page
Funnel](../proposals/proposal-h-display-layout-spec.md#print-page-funnel-destination)
decisions), so
this component's mounts are now: this tab, `PDFGeneratorModal.tsx`, and
`ProjectEditor.tsx` — one implementation, not a forked second copy. See
`docs/features/printing-tags.md`'s own entry for the full detail (why
`/whatsthat` and not a new route, the `sessionStorage`-backed "never
repeats within a session" rule) and `docs/features/print-export-page.md`
for the classic "Print!" tab's side of this.
decisions). The later Proposal H route swap (2026-07-23, issues #231/#272)
fully unrouted the classic grid `ProjectEditor.tsx` as well (component kept
in-tree, deletion is a separate later decision) — this component's only
LIVE mounts today are `FinishedMyProject.tsx`'s PDF tab (reached solely via
the standalone `/print` route, `pages/print.tsx`) and `PDFGeneratorModal.tsx`
(mounted globally via `Modals.tsx`, route-independent); one implementation
either way, not a forked second copy. See `docs/features/printing-tags.md`'s
own entry for the full detail (why `/whatsthat` and not a new route, the
`sessionStorage`-backed "never repeats within a session" rule) and
`docs/features/print-export-page.md` for the classic "Print!" tab's own
(now unrouted) history.

## Key files

Expand All @@ -202,8 +206,11 @@ for the classic "Print!" tab's side of this.
bug 4 (preview warning, confirm-gated download/cancel, and a real-image
success-path regression check)
- `frontend/tests/PostExportContributionPrompt.spec.ts` — issue #166
coverage across both real export surfaces (`/display` and the classic
"Print!" tab)
coverage against this component's one remaining live mount, `/print`'s
PDF tab (re-homed there from the classic "Print!" tab in the 2026-07-24
parked-spec port wave, issue #272 — the second, `/display`-inline-export
surface this file used to also cover was already retired by issue #275,
above)

## Status

Expand Down
12 changes: 8 additions & 4 deletions docs/features/print-export-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,14 @@ dismissible `Alert` shown once per session after a genuine "Generate
PDF"/"Save PDF to Google Drive" success, linking to `/whatsthat`. One shared
implementation (`frontend/src/features/export/ usePostExportContributionPrompt.ts` + `PostExportContributionPrompt.tsx`),
mounted from `PDFGenerator.tsx` itself so every real caller of that
component — this tab (reachable both via the classic editor's "Print!" tab
and, since issue #275, standalone at `pages/print.tsx`), `PDFGeneratorModal.tsx`,
`ProjectEditor.tsx` — gets it for free, rather than wiring it into
`FinishedMyProject.tsx` separately. (Issue #275 also retired the unified
component gets it for free, rather than wiring it into `FinishedMyProject.tsx`
separately. This tab was reachable both via the classic editor's "Print!"
tab and, since issue #275, standalone at `pages/print.tsx` — the later
Proposal H route swap (2026-07-23, issues #231/#272) fully unrouted the
classic grid `ProjectEditor.tsx` (component kept in-tree, deletion is a
separate later decision), so `pages/print.tsx` is this tab's only live
route today; `PDFGeneratorModal.tsx` remains a second, route-independent
mount (via `Modals.tsx`). (Issue #275 also retired the unified
`/display` page's OWN separate inline export pipeline and its own mount of
this same prompt — PDF generation now lives solely here, reached from
`/display`'s Finish footer via a pre-print save gate; see
Expand Down
52 changes: 52 additions & 0 deletions frontend/tests/CardSlot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import { test } from "../playwright.setup";
import {
changeQueries,
ensureDisplayFace,
expectDisplaySheetSlotState,
expectDisplaySheetSlotToExist,
expectDisplaySheetSlotToNotExist,
Expand Down Expand Up @@ -215,6 +216,57 @@ test.describe("CardSlot", () => {
await expectDisplaySheetSlotState(page, 1, "back", cardDocument2.name);
});

// Cluster-8 ruling (2026-07-24, issue #272 parked-spec port wave): CardbackToolbarButton's
// modal (the test directly above) is the project-wide canonical entry - picking there changes
// EVERY slot's default back. The rail's own per-slot Select Version section is the other
// supported entry, and is a genuine PER-SLOT OVERRIDE instead - both are supported, but they're
// not the same operation, and nothing before this test asserted the rail path leaves sibling
// slots (or the project-wide default itself) alone.
test("CardSlot's own rail Select Version section overrides just one slot's back, leaving the project-wide cardback (and sibling slots) untouched", async ({
page,
network,
}) => {
network.use(
cardDocumentsThreeResults,
cardbacksTwoResults,
sourceDocumentsOneResult,
...defaultHandlers
);
await loadPageWithDefaultBackend(page);

// Two members, both front-only queries - both backs default to the project cardback
// (cardDocument1, cardbacksTwoResults' first entry), same starting point as the toolbar test
// above.
await importTextOnEditorLanding(page, "2x my search query");
await expectDisplaySheetSlotState(page, 1, "back", cardDocument1.name);
await expectDisplaySheetSlotState(page, 2, "back", cardDocument1.name);

// Open slot 1's rail on its back face and pick the OTHER cardback candidate via Select
// Version - not the toolbar picker (openDisplayCardbackGridSelector above).
await ensureDisplayFace(page, "back");
await page.getByTestId("page-preview-slot").first().click();
await page
.getByTestId(`select-version-tile-${cardDocument2.identifier}`)
.locator(".mpccard")
.click();

// Slot 1's back changed...
await expectDisplaySheetSlotState(page, 1, "back", cardDocument2.name);
// ...but slot 2's back (still on the project-wide default) did not - a real per-slot
// override, not a second way to reach the same project-wide change.
await expectDisplaySheetSlotState(page, 2, "back", cardDocument1.name);

// Confirming from the OTHER direction too: the toolbar's own picker still shows the
// untouched project-wide default as selected (the glowing-border highlight class), not
// slot 1's override.
const gridSelector = await openDisplayCardbackGridSelector(page);
await expect(
gridSelector.locator(
`[data-card-identifier="${cardDocument1.identifier}"]`
)
).toHaveClass(/mpccard-highlight/);
});

test("changing a card slot's query", async ({ page, network }) => {
network.use(
cardDocumentsThreeResults,
Expand Down
52 changes: 16 additions & 36 deletions frontend/tests/PDFGenerator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,15 @@ import {
} from "@/mocks/handlers";

import { test } from "../playwright.setup";
import {
importTextOnEditorLanding,
loadPageWithDefaultBackend,
} from "./test-utils";

// Proposal H switchover (2026-07-23, issues #231/#272) - /editor now serves the unified
// sheet+rail page (`DisplayPage.tsx`); the classic grid `ProjectEditor` this file's own setup
// depends on (via testids/interaction patterns like `front-slot`/`back-slot`/`common-cardback`/
// the "Add Cards" right-panel dropdown/the classic "Print!" tab, or a component with no rendered
// equivalent on the new page yet - see issue #272's own tracked parity gaps) is fully unrouted,
// not just delisted from the nav. Skipped here rather than deleted (component files themselves
// are untouched, per this swap's own scope) or silently left red - porting this coverage to
// DisplayPage's DOM is real, non-mechanical work tracked against #272, not done as part of the
// route swap itself (the owner's directive was to proceed with the swap regardless of the
// checklist's open items).
test.beforeEach(async ({}, testInfo) => {
testInfo.skip(
true,
"Proposal H switchover (2026-07-23): tests classic /editor-only UI, now unrouted - see issue #272"
);
});
import { navigateToPrintPDFTab } from "./test-utils";

// Parked-spec port wave (2026-07-24, issue #272). Re-homed onto the standalone /print route
// (D10, pages/print.tsx) - PDFGenerator.tsx itself is completely unchanged by the Proposal H
// route swap (its only live mount moved from the classic grid's "Print!" tab to /print's
// FinishedMyProject, both the same unforked component), so every assertion below (testids,
// modal/toast copy, localStorage key, timing) is unchanged from the pre-swap version of this
// file - only the navigation helper (navigateToPrintPDFTab, test-utils.ts) differs.
test.describe.configure({ timeout: 60_000 });

// Matches the domains configured via NEXT_PUBLIC_IMAGE_WORKER_URL /
// NEXT_PUBLIC_IMAGE_BUCKET_URL in playwright.config.ts's webServer env.
Expand Down Expand Up @@ -75,12 +63,8 @@ const imageBucketSuccess = http.get(
})
);

const addCardAndOpenPDFTab = async (page: Page) => {
await loadPageWithDefaultBackend(page);
await importTextOnEditorLanding(page, "my search query");
await page.getByRole("tab", { name: "Print!" }).click();
await page.getByRole("tab", { name: "PDF" }).click();
};
const addCardAndOpenPDFTab = async (page: Page) =>
navigateToPrintPDFTab(page, "my search query");

test.describe("PDFGenerator - card image fetch failures", () => {
test("warns in the live preview when a card image fails to fetch", async ({
Expand Down Expand Up @@ -289,16 +273,12 @@ test.describe("PDFGenerator - manual bleed override (Proposal B PR-2)", () => {
.toBe(JSON.stringify({ [cardDocument1.identifier]: "force-bleed" }));

// A fresh navigation rather than page.reload() - reload() alone was observed to hang past
// the test timeout in this app; waiting for "domcontentloaded" rather than the default
// "load" avoids a second hang, both unrelated to anything this PR touches (this app's
// the test timeout in this app, unrelated to anything this PR touches (this app's
// webworkers appear not to settle a second "load" event cleanly within one Playwright page).
await page.goto("/editor?server=http://127.0.0.1:8000", {
waitUntil: "domcontentloaded",
});
await page.getByText("Choose Art").click();
await importTextOnEditorLanding(page, "my search query");
await page.getByRole("tab", { name: "Print!" }).click();
await page.getByRole("tab", { name: "PDF" }).click();
// The project itself doesn't persist across this reload (only the standalone localStorage
// override does), so the whole navigateToPrintPDFTab journey - including re-importing - runs
// again from scratch, same as every other test in this file.
await navigateToPrintPDFTab(page, "my search query");
await page.getByText("Bleed Overrides").click();

await expect(
Expand Down
55 changes: 29 additions & 26 deletions frontend/tests/PagePreview.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,37 @@ import {
} from "@/mocks/handlers";

import { test } from "../playwright.setup";
import {
importTextOnEditorLanding,
loadPageWithDefaultBackend,
} from "./test-utils";
import { navigateToPrintPDFTab } from "./test-utils";

// Proposal H switchover (2026-07-23, issues #231/#272) - /editor now serves the unified
// sheet+rail page (`DisplayPage.tsx`); the classic grid `ProjectEditor` this file's own setup
// depends on (via testids/interaction patterns like `front-slot`/`back-slot`/`common-cardback`/
// the "Add Cards" right-panel dropdown/the classic "Print!" tab, or a component with no rendered
// equivalent on the new page yet - see issue #272's own tracked parity gaps) is fully unrouted,
// not just delisted from the nav. Skipped here rather than deleted (component files themselves
// are untouched, per this swap's own scope) or silently left red - porting this coverage to
// DisplayPage's DOM is real, non-mechanical work tracked against #272, not done as part of the
// route swap itself (the owner's directive was to proceed with the swap regardless of the
// checklist's open items).
test.beforeEach(async ({}, testInfo) => {
testInfo.skip(
true,
"Proposal H switchover (2026-07-23): tests classic /editor-only UI, now unrouted - see issue #272"
);
});
// Parked-spec port wave (2026-07-24, issue #272). Re-homed onto the standalone /print route
// (D10, pages/print.tsx) - see PDFGenerator.spec.ts's own module comment for the full rationale
// (this file exercises the identical PDFGenerator.tsx mount, unchanged by the route swap).
//
// Ported selectively, not verbatim - deduped against the unified page's own sheet coverage
// (DisplayPage.spec.ts/OrphanRendering.spec.ts, both dozens of `page-preview-slot` assertions
// against the SAME PagePreview.tsx component, just mounted directly on /editor's sheet region
// rather than behind the Print page's PDF tab). None of this file's 3 tests turned out to be a
// true duplicate once checked against that existing coverage - all 3 ported as-is (see this PR's
// own body for the full dedup table):
// - "shows the fast DOM preview by default..." asserts the `page-preview` CONTAINER testid,
// which neither DisplayPage.spec.ts nor OrphanRendering.spec.ts ever reference (both only
// ever assert against individual `page-preview-slot` children) - not a duplicate.
// - "toggling to exact preview switches to the pdf.js canvas render, and back" exercises
// `preview-mode-toggle`, which only exists on PDFGenerator.tsx's own mount - DisplayPage's
// sheet has no toggle at all (it's permanently in fast-preview mode). Print-page-only
// behavior, not portable elsewhere, not a duplicate.
// - "the fast preview reflows live when page margins change..." exercises the "Spacing &
// Margins" NumericField section, which is also PDFGenerator.tsx-only - DisplayPage has no
// margin controls of its own (grep-confirmed: "Spacing & Margins"/"page margin" only appear
// in PDFGenerator.tsx). Not a duplicate.
//
// Generous file-level timeout (not the 30s default) - navigateToPrintPDFTab's own retry against
// /print's cold-compile race (test-utils.ts's own comment) needs headroom beyond a single 30s
// test timeout to actually get a second attempt in, same precedent PDFGenerator.spec.ts uses.
test.describe.configure({ timeout: 60_000 });

const addCardAndOpenPDFTab = async (page: Page) => {
await loadPageWithDefaultBackend(page);
await importTextOnEditorLanding(page, "my search query");
await page.getByRole("tab", { name: "Print!" }).click();
await page.getByRole("tab", { name: "PDF" }).click();
};
const addCardAndOpenPDFTab = async (page: Page) =>
navigateToPrintPDFTab(page, "my search query");

test.describe("PDFGenerator - fast page preview (Proposal A)", () => {
test("shows the fast DOM preview by default, with a slot rendered and no spinner", async ({
Expand Down
Loading
Loading