diff --git a/frontend/tests/AddCardToFavorites.spec.ts b/frontend/tests/AddCardToFavorites.spec.ts index b111dbf5e..412523c48 100644 --- a/frontend/tests/AddCardToFavorites.spec.ts +++ b/frontend/tests/AddCardToFavorites.spec.ts @@ -12,37 +12,17 @@ import { import { test } from "../playwright.setup"; import { - expectCardGridSlotState, + closeDetailedView, importTextOnEditorLanding, loadPageWithDefaultBackend, + openDetailedView, } 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" - ); -}); - -const openDetailedView = async (page: any, cardName: string) => { - await page.getByAltText(cardName).click(); - await expect(page.getByText("Card Details")).toBeVisible(); -}; - -const closeDetailedView = async (page: any) => { - await page.getByTestId("detailed-view").getByLabel("Close").click(); - await expect(page.getByText("Card Details")).not.toBeVisible(); -}; +// Proposal H parity port (2026-07-23, issue #272 wave 1): ported onto the unified /editor page - +// AddCardToFavorites (via CardDownloadFavorite) lives inside CardDetailedViewModal's own "Card +// Details" region, reached the same way the rest of this cluster reaches the modal - see +// openDetailedView's own module comment (test-utils.ts) for the Browse-mode route and the "Card +// details" text-collision fix. test.describe("AddCardToFavorites tests", () => { test("renders Add to Favorites button when card is not a favorite", async ({ @@ -62,9 +42,8 @@ test.describe("AddCardToFavorites tests", () => { page, `my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await expectCardGridSlotState(page, 1, "front", cardDocument1.name, 1, 1); - await openDetailedView(page, cardDocument1.name); + await openDetailedView(page, "my search query", cardDocument1.identifier); const button = page.getByRole("button", { name: /Add to Favorites/i }); await expect(button).toBeVisible(); @@ -85,9 +64,8 @@ test.describe("AddCardToFavorites tests", () => { page, `my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await expectCardGridSlotState(page, 1, "front", cardDocument1.name, 1, 1); - await openDetailedView(page, cardDocument1.name); + await openDetailedView(page, "my search query", cardDocument1.identifier); // Initially shows "Add to Favorites" const button = page.getByRole("button", { name: /Add to Favorites/i }); @@ -127,9 +105,8 @@ test.describe("AddCardToFavorites tests", () => { page, `my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await expectCardGridSlotState(page, 1, "front", cardDocument1.name, 1, 1); - await openDetailedView(page, cardDocument1.name); + await openDetailedView(page, "my search query", cardDocument1.identifier); // First add to favorites const addButton = page.getByRole("button", { name: /Add to Favorites/i }); @@ -175,9 +152,8 @@ test.describe("AddCardToFavorites tests", () => { page, `my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await expectCardGridSlotState(page, 1, "front", cardDocument1.name, 1, 1); - await openDetailedView(page, cardDocument1.name); + await openDetailedView(page, "my search query", cardDocument1.identifier); // First click: Add to favorites await page.getByRole("button", { name: /Add to Favorites/i }).click(); @@ -215,10 +191,9 @@ test.describe("AddCardToFavorites tests", () => { page, `my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await expectCardGridSlotState(page, 1, "front", cardDocument1.name, 1, 1); // Open detailed view and add to favorites - await openDetailedView(page, cardDocument1.name); + await openDetailedView(page, "my search query", cardDocument1.identifier); await page.getByRole("button", { name: /Add to Favorites/i }).click(); await expect( page.getByRole("button", { name: /Remove from Favorites/i }) @@ -228,7 +203,7 @@ test.describe("AddCardToFavorites tests", () => { await closeDetailedView(page); // Reopen the detailed view - await openDetailedView(page, cardDocument1.name); + await openDetailedView(page, "my search query", cardDocument1.identifier); // Should still show as a favorite await expect( diff --git a/frontend/tests/AddCardToProjectForm.spec.ts b/frontend/tests/AddCardToProjectForm.spec.ts index 7586d65ed..936814699 100644 --- a/frontend/tests/AddCardToProjectForm.spec.ts +++ b/frontend/tests/AddCardToProjectForm.spec.ts @@ -12,27 +12,19 @@ import { import { test } from "../playwright.setup"; import { - expectCardGridSlotStates, + closeDetailedView, + expectDisplaySheetSlotStates, importTextOnEditorLanding, loadPageWithDefaultBackend, + openDetailedView, } 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" - ); -}); +// Proposal H parity port (2026-07-23, issue #272 wave 1): ported onto the unified /editor page - +// AddCardToProjectForm is unforked and mounted the same way the rest of the card-detail modal +// cluster reaches it (Browse mode - see openDetailedView's own module comment, test-utils.ts). +// Per-slot assertions ported via expectDisplaySheetSlotStates - see that helper's own comment for +// why dropping the selectedImage/totalImages numeric checks doesn't weaken what this test actually +// verifies (the right card landing in the right slot, the right number of times). test.describe("AddCardToProjectForm tests", () => { for (const quantity of [1, 2, 3]) { @@ -55,77 +47,50 @@ test.describe("AddCardToProjectForm tests", () => { `card one${SelectedImageSeparator}${cardDocument1.identifier}` ); - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, - [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - ], - [ - { - slot: 1, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - ] + [{ slot: 1, name: cardDocument1.name }], + [{ slot: 1, name: cardDocument2.name }] ); - // Click on the card to open detailed view - await page.getByAltText(cardDocument1.name).click(); - await expect(page.getByText("Card Details")).toBeVisible(); + // Open the detail view (via Browse mode) for the same card already in the project + await openDetailedView(page, "card one", cardDocument1.identifier); - // Fill in the quantity - const quantityInput = page.getByAltText( + // Fill in the quantity - scoped to the modal itself, since Browse mode's own catalog tile + // (still in the DOM behind the modal) mounts its own, unforked AddCardToProjectForm too + // (CatalogBrowseResults.tsx's own "+Add" affordance) - a bare page-wide locator collides + // with both. + const detailedView = page.getByTestId("detailed-view"); + const quantityInput = detailedView.getByAltText( "Quantity of card to add to project" ); await quantityInput.clear(); await quantityInput.fill(quantity.toString()); // Add to project - await page.getByRole("button", { name: "Add to Project" }).click(); + await detailedView + .getByRole("button", { name: "Add to Project" }) + .click(); // Close the detailed view - await page.getByTestId("detailed-view").getByLabel("Close").click(); + await closeDetailedView(page); + + // openDetailedView's own Browse-mode route (test-utils.ts) left the shared Add/Browse + // toggle on Browse - the center region renders catalog results, not the print-sheet stack, + // while that's active (DisplayPage.tsx's own module comment), so the sheet assertions below + // need Add mode back first. + await page.getByTestId("display-search-mode-add").click(); // Verify that the cards were added (original slot + new slots) - const expectedFronts = [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - ]; - const expectedBacks = [ - { - slot: 1, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - ]; + const expectedFronts = [{ slot: 1, name: cardDocument1.name }]; + const expectedBacks = [{ slot: 1, name: cardDocument2.name }]; for (let i = 0; i < quantity; i++) { - expectedFronts.push({ - slot: i + 2, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }); - expectedBacks.push({ - slot: i + 2, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }); + expectedFronts.push({ slot: i + 2, name: cardDocument1.name }); + expectedBacks.push({ slot: i + 2, name: cardDocument2.name }); } - await expectCardGridSlotStates(page, expectedFronts, expectedBacks); + await expectDisplaySheetSlotStates(page, expectedFronts, expectedBacks); }); } }); diff --git a/frontend/tests/ArtistSupportLink.spec.ts b/frontend/tests/ArtistSupportLink.spec.ts index 0d3526c62..7754f6283 100644 --- a/frontend/tests/ArtistSupportLink.spec.ts +++ b/frontend/tests/ArtistSupportLink.spec.ts @@ -17,32 +17,15 @@ import { test } from "../playwright.setup"; import { importTextOnEditorLanding, loadPageWithDefaultBackend, + openDetailedView, } 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" - ); -}); - -const openDetailedView = async (page: any, cardName: string) => { - await page.getByAltText(cardName).click(); - await expect(page.getByText("Card Details")).toBeVisible(); -}; - -// Artist Support Links v1, surface 1: the Card Detail Modal's "Canonical Aritst" row (see -// docs/features/artist-support-links.md). Surface 2 (the /whatsthat post-answer moment) is +// Proposal H parity port (2026-07-23, issue #272 wave 1): ported onto the unified /editor page. +// CardDetailedViewModal (the shared, unforked component this whole cluster exercises) is reached +// via Browse mode - see openDetailedView's own module comment (test-utils.ts) for why that's the +// one surface on this page that still opens it, and the "Card details" text-collision fix that +// cluster needed. Its own "Canonical Aritst" table row is what this file's own assertions target +// (docs/features/artist-support-links.md). Surface 2 (the /whatsthat post-answer moment) is // covered in QuestionFeed.spec.ts. test.describe("Artist Support Link - Card Detail Modal", () => { test("a known canonical artist renders as an Artist Support Link, built deterministically from their name", async ({ @@ -64,7 +47,7 @@ test.describe("Artist Support Link - Card Detail Modal", () => { page, `my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await openDetailedView(page, cardDocument1.name); + await openDetailedView(page, "my search query", cardDocument1.identifier); const link = page.getByTestId("artist-support-link"); await expect(link).toBeVisible(); @@ -98,7 +81,7 @@ test.describe("Artist Support Link - Card Detail Modal", () => { page, `my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await openDetailedView(page, cardDocument1.name); + await openDetailedView(page, "my search query", cardDocument1.identifier); await expect(page.getByTestId("artist-support-link")).toHaveCount(0); await expect( diff --git a/frontend/tests/ExportDecklist.spec.ts b/frontend/tests/ExportDecklist.spec.ts index 43a282302..d2c4c0dce 100644 --- a/frontend/tests/ExportDecklist.spec.ts +++ b/frontend/tests/ExportDecklist.spec.ts @@ -17,30 +17,23 @@ import { import { test } from "../playwright.setup"; import { - downloadDecklist, - expectCardbackSlotState, - expectCardGridSlotState, + downloadDecklistFromDisplayToolbar, + expectDisplaySheetSlotStates, importTextOnEditorLanding, loadPageWithDefaultBackend, normaliseString, } 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" - ); -}); +// Proposal H parity port (2026-07-23, issue #272 wave 1): ported onto the unified /editor page - +// downloadDecklist itself is unchanged (ExportDecklist.tsx/ExportImages.test.tsx already cover +// its own internals); only the trigger changed - see downloadDecklistFromDisplayToolbar's own +// module comment (test-utils.ts). Per-slot setup assertions ported via +// expectDisplaySheetSlotStates - see that helper's own comment for why dropping the +// selectedImage/totalImages numeric checks doesn't weaken the name check every test here actually +// depends on. The classic grid's standalone "common cardback" preview tile +// (`expectCardbackSlotState`) has no equivalent on this page and isn't part of what a decklist +// export even reads (decklists never include the cardback - each test's own comment says so) - not +// ported. test.describe("ExportDecklist", () => { test("the decklist representation of a simple project with no custom backs", async ({ @@ -57,11 +50,16 @@ test.describe("ExportDecklist", () => { await loadPageWithDefaultBackend(page); await importTextOnEditorLanding(page, "query 1\nquery 2\nt:query 5"); - await expectCardGridSlotState(page, 1, "front", cardDocument1.name, 1, 1); - await expectCardGridSlotState(page, 2, "front", cardDocument2.name, 1, 1); - await expectCardbackSlotState(page, cardDocument5.name, 1, 1); + await expectDisplaySheetSlotStates( + page, + [ + { slot: 1, name: cardDocument1.name }, + { slot: 2, name: cardDocument2.name }, + ], + [] + ); - const [content, filename] = await downloadDecklist(page); + const [content, filename] = await downloadDecklistFromDisplayToolbar(page); // note: tokens are not included in decklists expect(normaliseString(content)).toBe( @@ -90,12 +88,16 @@ test.describe("ExportDecklist", () => { page, `query 1\nquery 2${FaceSeparator}t:query 6` ); - await expectCardGridSlotState(page, 1, "front", cardDocument1.name, 1, 1); - await expectCardGridSlotState(page, 2, "front", cardDocument2.name, 1, 1); - await expectCardGridSlotState(page, 2, "back", cardDocument6.name, 1, 1); - await expectCardbackSlotState(page, cardDocument5.name, 1, 1); + await expectDisplaySheetSlotStates( + page, + [ + { slot: 1, name: cardDocument1.name }, + { slot: 2, name: cardDocument2.name }, + ], + [{ slot: 2, name: cardDocument6.name }] + ); - const [content, filename] = await downloadDecklist(page); + const [content, filename] = await downloadDecklistFromDisplayToolbar(page); // note: the custom cardback is not included here because only cards are included in decklists expect(normaliseString(content)).toBe( @@ -124,13 +126,17 @@ test.describe("ExportDecklist", () => { page, `2x query 1\nquery 2${FaceSeparator}query 1` ); - await expectCardGridSlotState(page, 1, "front", cardDocument1.name, 1, 1); - await expectCardGridSlotState(page, 2, "front", cardDocument1.name, 1, 1); - await expectCardGridSlotState(page, 3, "front", cardDocument2.name, 1, 1); - await expectCardGridSlotState(page, 3, "back", cardDocument1.name, 1, 1); - await expectCardbackSlotState(page, cardDocument5.name, 1, 1); + await expectDisplaySheetSlotStates( + page, + [ + { slot: 1, name: cardDocument1.name }, + { slot: 2, name: cardDocument1.name }, + { slot: 3, name: cardDocument2.name }, + ], + [{ slot: 3, name: cardDocument1.name }] + ); - const [content, filename] = await downloadDecklist(page); + const [content, filename] = await downloadDecklistFromDisplayToolbar(page); expect(normaliseString(content)).toBe( normaliseString( diff --git a/frontend/tests/ExportXML.spec.ts b/frontend/tests/ExportXML.spec.ts index 022b9eb74..1b4e02cde 100644 --- a/frontend/tests/ExportXML.spec.ts +++ b/frontend/tests/ExportXML.spec.ts @@ -18,30 +18,23 @@ import { import { test } from "../playwright.setup"; import { - downloadXML, - expectCardbackSlotState, - expectCardGridSlotState, + downloadXMLFromDisplayToolbar, + expectDisplaySheetSlotStates, importTextOnEditorLanding, loadPageWithDefaultBackend, normaliseString, } 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" - ); -}); +// Proposal H parity port (2026-07-23, issue #272 wave 1): ported onto the unified /editor page - +// downloadXML itself is unchanged (ExportXML.tsx/downloadXML.test.ts already cover its own +// internals); only the trigger changed - see downloadXMLFromDisplayToolbar's own module comment +// (test-utils.ts). Per-slot setup assertions ported via expectDisplaySheetSlotStates - see that +// helper's own comment for why dropping the selectedImage/totalImages numeric checks doesn't +// weaken the name check every test here actually depends on. The classic grid's standalone +// "common cardback" preview tile (`expectCardbackSlotState`) has no equivalent on this page - not +// ported; the cardback IS part of the exported XML (unlike the decklist export), but that's +// already asserted against the downloaded file content itself below, independent of any preview +// widget. test.describe("ExportXML", () => { test("the XML representation of a simple project with no custom backs", async ({ @@ -58,11 +51,16 @@ test.describe("ExportXML", () => { await loadPageWithDefaultBackend(page); await importTextOnEditorLanding(page, "query 1\nquery 2"); - await expectCardGridSlotState(page, 1, "front", cardDocument1.name, 1, 1); - await expectCardGridSlotState(page, 2, "front", cardDocument2.name, 1, 1); - await expectCardbackSlotState(page, cardDocument5.name, 1, 1); + await expectDisplaySheetSlotStates( + page, + [ + { slot: 1, name: cardDocument1.name }, + { slot: 2, name: cardDocument2.name }, + ], + [] + ); - const [content, filename] = await downloadXML(page); + const [content, filename] = await downloadXMLFromDisplayToolbar(page); expect(normaliseString(content)).toBe( normaliseString( @@ -112,12 +110,16 @@ test.describe("ExportXML", () => { page, `query 1\nquery 2${FaceSeparator}t:query 6` ); - await expectCardGridSlotState(page, 1, "front", cardDocument1.name, 1, 1); - await expectCardGridSlotState(page, 2, "front", cardDocument2.name, 1, 1); - await expectCardGridSlotState(page, 2, "back", cardDocument6.name, 1, 1); - await expectCardbackSlotState(page, cardDocument5.name, 1, 1); + await expectDisplaySheetSlotStates( + page, + [ + { slot: 1, name: cardDocument1.name }, + { slot: 2, name: cardDocument2.name }, + ], + [{ slot: 2, name: cardDocument6.name }] + ); - const [content, filename] = await downloadXML(page); + const [content, filename] = await downloadXMLFromDisplayToolbar(page); expect(normaliseString(content)).toBe( normaliseString( @@ -176,13 +178,17 @@ test.describe("ExportXML", () => { page, `2x query 1\nquery 2${FaceSeparator}query 1` ); - await expectCardGridSlotState(page, 1, "front", cardDocument1.name, 1, 1); - await expectCardGridSlotState(page, 2, "front", cardDocument1.name, 1, 1); - await expectCardGridSlotState(page, 3, "front", cardDocument2.name, 1, 1); - await expectCardGridSlotState(page, 3, "back", cardDocument1.name, 1, 1); - await expectCardbackSlotState(page, cardDocument5.name, 1, 1); + await expectDisplaySheetSlotStates( + page, + [ + { slot: 1, name: cardDocument1.name }, + { slot: 2, name: cardDocument1.name }, + { slot: 3, name: cardDocument2.name }, + ], + [{ slot: 3, name: cardDocument1.name }] + ); - const [content, filename] = await downloadXML(page); + const [content, filename] = await downloadXMLFromDisplayToolbar(page); expect(normaliseString(content)).toBe( normaliseString( diff --git a/frontend/tests/ImportCSV.spec.ts b/frontend/tests/ImportCSV.spec.ts index 9c3ba8f6b..d250b0529 100644 --- a/frontend/tests/ImportCSV.spec.ts +++ b/frontend/tests/ImportCSV.spec.ts @@ -21,28 +21,21 @@ import { import { test } from "../playwright.setup"; import { - expectCardbackSlotState, - expectCardGridSlotStates, - importCSV, + expectDisplaySheetSlotStates, + importCSVOnEmptyLanding, 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" - ); -}); +// Proposal H parity port (2026-07-23, issue #272 wave 1): ported onto the unified /editor page. +// DisplayPage's empty-project landing mounts the same plain ImportCSV component verbatim, inline +// inside a collapsed "Import a File or URL" accordion rather than behind the classic dropdown- +// triggered modal - see importCSVOnEmptyLanding (test-utils.ts) for the DOM difference. All 6 +// tests here import into an empty project, so that's the only surface this file needs; see +// ImportXML.spec.ts's own "into a non-empty project" test for the populated-toolbar counterpart. +// Per-slot assertions are ported via expectDisplaySheetSlotStates - see that helper's own comment +// for why dropping the selectedImage/totalImages numeric checks is still a faithful port of every +// test below (each fixture card here has its own distinct name, so the name check alone already +// proves which specific candidate got selected). test.describe("ImportCSV", () => { test("importing one card by CSV into an empty project", async ({ @@ -58,32 +51,17 @@ test.describe("ImportCSV", () => { ); await loadPageWithDefaultBackend(page); - await importCSV( + await importCSVOnEmptyLanding( page, `Quantity,Front ,my search query` ); - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, - [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - ], - [ - { - slot: 1, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - ] + [{ slot: 1, name: cardDocument1.name }], + [{ slot: 1, name: cardDocument2.name }] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); }); test("importing multiple instances of one card by CSV into an empty project", async ({ @@ -99,44 +77,23 @@ test.describe("ImportCSV", () => { ); await loadPageWithDefaultBackend(page); - await importCSV( + await importCSVOnEmptyLanding( page, `Quantity,Front 2,my search query` ); - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - { - slot: 2, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, + { slot: 1, name: cardDocument1.name }, + { slot: 2, name: cardDocument1.name }, ], [ - { - slot: 1, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - { - slot: 2, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, + { slot: 1, name: cardDocument2.name }, + { slot: 2, name: cardDocument2.name }, ] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); }); test("importing one specific card version by CSV into an empty project", async ({ @@ -152,32 +109,17 @@ test.describe("ImportCSV", () => { ); await loadPageWithDefaultBackend(page); - await importCSV( + await importCSVOnEmptyLanding( page, `Quantity,Front,Front ID ,my search query,${cardDocument3.identifier}` ); - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, - [ - { - slot: 1, - name: cardDocument3.name, - selectedImage: 3, - totalImages: 3, - }, - ], - [ - { - slot: 1, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - ] + [{ slot: 1, name: cardDocument3.name }], + [{ slot: 1, name: cardDocument2.name }] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); }); test("importing one card of each type into an empty project", async ({ @@ -193,56 +135,25 @@ test.describe("ImportCSV", () => { ); await loadPageWithDefaultBackend(page); - await importCSV( + await importCSVOnEmptyLanding( page, `Quantity,Front ,query 1\n,t:query 6\n,b:query 5` ); - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - { - slot: 2, - name: cardDocument6.name, - selectedImage: 1, - totalImages: 1, - }, - { - slot: 3, - name: cardDocument5.name, - selectedImage: 1, - totalImages: 1, - }, + { slot: 1, name: cardDocument1.name }, + { slot: 2, name: cardDocument6.name }, + { slot: 3, name: cardDocument5.name }, ], [ - { - slot: 1, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - { - slot: 2, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - { - slot: 3, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, + { slot: 1, name: cardDocument2.name }, + { slot: 2, name: cardDocument2.name }, + { slot: 3, name: cardDocument2.name }, ] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); }); test("importing a more complex CSV into an empty project", async ({ @@ -258,57 +169,26 @@ test.describe("ImportCSV", () => { ); await loadPageWithDefaultBackend(page); - await importCSV( + await importCSVOnEmptyLanding( page, `Quantity,Front,Front ID,Back,Back ID 2,my search query,${cardDocument3.identifier},my search query,${cardDocument4.identifier} ,my search query` ); - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, [ - { - slot: 1, - name: cardDocument3.name, - selectedImage: 3, - totalImages: 4, - }, - { - slot: 2, - name: cardDocument3.name, - selectedImage: 3, - totalImages: 4, - }, - { - slot: 3, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 4, - }, + { slot: 1, name: cardDocument3.name }, + { slot: 2, name: cardDocument3.name }, + { slot: 3, name: cardDocument1.name }, ], [ - { - slot: 1, - name: cardDocument4.name, - selectedImage: 4, - totalImages: 4, - }, - { - slot: 2, - name: cardDocument4.name, - selectedImage: 4, - totalImages: 4, - }, - { - slot: 3, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, + { slot: 1, name: cardDocument4.name }, + { slot: 2, name: cardDocument4.name }, + { slot: 3, name: cardDocument2.name }, ] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); }); test("CSV header has spaces", async ({ page, network }) => { @@ -321,31 +201,16 @@ test.describe("ImportCSV", () => { ); await loadPageWithDefaultBackend(page); - await importCSV( + await importCSVOnEmptyLanding( page, `Quantity, Front , Front ID ,my search query,${cardDocument3.identifier}` ); - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, - [ - { - slot: 1, - name: cardDocument3.name, - selectedImage: 3, - totalImages: 3, - }, - ], - [ - { - slot: 1, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - ] + [{ slot: 1, name: cardDocument3.name }], + [{ slot: 1, name: cardDocument2.name }] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); }); }); diff --git a/frontend/tests/ImportText.spec.ts b/frontend/tests/ImportText.spec.ts index 301fcb7a0..52b1679e8 100644 --- a/frontend/tests/ImportText.spec.ts +++ b/frontend/tests/ImportText.spec.ts @@ -29,32 +29,22 @@ import { import { test } from "../playwright.setup"; import { - expectCardbackSlotState, - expectCardGridSlotState, - expectCardGridSlotStates, - expectCardSlotToExist, - expectCardSlotToNotExist, - importText, + expectDisplaySheetSlotStates, + expectDisplaySheetSlotToNotExist, + importTextInline, + importTextOnEditorLanding, loadPageWithDefaultBackend, - openImportTextModal, } 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" - ); -}); +// Proposal H parity port (2026-07-23, issue #272 wave 1): ported onto the unified /editor page. +// DisplayPage mounts the same plain ImportText component verbatim on its empty-project landing +// (see loadPageWithDefaultBackend/importTextOnEditorLanding, test-utils.ts), and a non-empty +// project's toolbar mounts ImportText's own "inline" variant for adding more (importTextInline). +// Per-slot state assertions are ported via expectDisplaySheetSlotStates - see that helper's own +// comment for the one thing it deliberately doesn't check (selectedImage/totalImages counts, +// which the unified page's sheet has no inline readout for) and why that's still a faithful port +// of every test below's actual point (each result set in this suite gives every candidate its own +// distinct name, so the name check alone already proves the right one landed). test.describe("ImportText", () => { test("importing one card by text into an empty project", async ({ @@ -69,25 +59,11 @@ test.describe("ImportText", () => { ...defaultHandlers ); await loadPageWithDefaultBackend(page); - await importText(page, "my search query"); - await expectCardGridSlotStates( + await importTextOnEditorLanding(page, "my search query"); + await expectDisplaySheetSlotStates( page, - [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - ], - [ - { - slot: 1, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - ] + [{ slot: 1, name: cardDocument1.name }], + [{ slot: 1, name: cardDocument2.name }] ); }); @@ -105,44 +81,20 @@ test.describe("ImportText", () => { // import two instances of a card await loadPageWithDefaultBackend(page); - - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); - - await importText(page, "2x my search query"); + await importTextOnEditorLanding(page, "2x my search query"); // two card slots should have been created - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - { - slot: 2, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, + { slot: 1, name: cardDocument1.name }, + { slot: 2, name: cardDocument1.name }, ], [ - { - slot: 1, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - { - slot: 2, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, + { slot: 1, name: cardDocument2.name }, + { slot: 2, name: cardDocument2.name }, ] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); // should not have changed }); test("importing multiple instances of one card without an x by text into an empty project", async ({ @@ -159,41 +111,20 @@ test.describe("ImportText", () => { await loadPageWithDefaultBackend(page); // import two instances of a card without an x - await importText(page, "2 my search query"); + await importTextOnEditorLanding(page, "2 my search query"); // two card slots should have been created - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - { - slot: 2, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, + { slot: 1, name: cardDocument1.name }, + { slot: 2, name: cardDocument1.name }, ], [ - { - slot: 1, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - { - slot: 2, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, + { slot: 1, name: cardDocument2.name }, + { slot: 2, name: cardDocument2.name }, ] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); }); test("importing multiple instances of one card with a capital X by text into an empty project", async ({ @@ -209,44 +140,21 @@ test.describe("ImportText", () => { ); await loadPageWithDefaultBackend(page); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); - // import two instances of a card with a capital X - await importText(page, "2X my search query"); + await importTextOnEditorLanding(page, "2X my search query"); // two card slots should have been created - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - { - slot: 2, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, + { slot: 1, name: cardDocument1.name }, + { slot: 2, name: cardDocument1.name }, ], [ - { - slot: 1, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - { - slot: 2, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, + { slot: 1, name: cardDocument2.name }, + { slot: 2, name: cardDocument2.name }, ] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); // should not have changed }); test("importing multiple instances of one card by text into a non-empty project", async ({ @@ -264,64 +172,29 @@ test.describe("ImportText", () => { // this used to preload the redux state, but with the shift to listeners, // we have to add the first card manually like this. - await importText( + await importTextOnEditorLanding( page, `1x my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, - [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - ], - [ - { - slot: 1, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - ] + [{ slot: 1, name: cardDocument1.name }], + [{ slot: 1, name: cardDocument2.name }] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); - // import two instances of a card - await importText(page, "2x my search query"); + // import two instances of a card via the populated toolbar's inline search bar + await importTextInline(page, "2x my search query"); - // two card slots should have been created - await expectCardGridSlotStates( + // two more card slots should have been created + await expectDisplaySheetSlotStates( page, [ - { - slot: 2, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - { - slot: 3, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, + { slot: 2, name: cardDocument1.name }, + { slot: 3, name: cardDocument1.name }, ], [ - { - slot: 2, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - { - slot: 3, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, + { slot: 2, name: cardDocument2.name }, + { slot: 3, name: cardDocument2.name }, ] ); }); @@ -339,58 +212,23 @@ test.describe("ImportText", () => { ); await loadPageWithDefaultBackend(page); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); - // import one card of each type - await importText(page, "query 1\nt:query 6\nb:query 5"); + await importTextOnEditorLanding(page, "query 1\nt:query 6\nb:query 5"); // three card slots should have been created - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - { - slot: 2, - name: cardDocument6.name, - selectedImage: 1, - totalImages: 1, - }, - { - slot: 3, - name: cardDocument5.name, - selectedImage: 1, - totalImages: 1, - }, + { slot: 1, name: cardDocument1.name }, + { slot: 2, name: cardDocument6.name }, + { slot: 3, name: cardDocument5.name }, ], [ - { - slot: 1, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - { - slot: 2, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - - { - slot: 3, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, + { slot: 1, name: cardDocument2.name }, + { slot: 2, name: cardDocument2.name }, + { slot: 3, name: cardDocument2.name }, ] ); - - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); // should not have changed }); test("importing one DFC-paired card by text into an empty project", async ({ @@ -407,32 +245,15 @@ test.describe("ImportText", () => { ); await loadPageWithDefaultBackend(page); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); - // import one instance of a double faced card - await importText(page, "my search query"); + await importTextOnEditorLanding(page, "my search query"); // we should now have both sides of that DFC pair in slot 1 - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, - [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - ], - [ - { - slot: 1, - name: cardDocument4.name, - selectedImage: 1, - totalImages: 1, - }, - ] + [{ slot: 1, name: cardDocument1.name }], + [{ slot: 1, name: cardDocument4.name }] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); // should not have changed }); test("importing an empty string by text into an empty project", async ({ @@ -448,9 +269,14 @@ test.describe("ImportText", () => { ); await loadPageWithDefaultBackend(page); - await importText(page, ""); - - await expectCardSlotToNotExist(page, 1); + // the "import-text-submit" button requires non-empty input to submit at all (Form.Control + // `required`) - unlike the classic modal, which stayed open and let this test observe "no + // slot got created", the empty-landing page has nothing further to observe here besides + // staying on the empty-state landing itself. + await page.getByRole("textbox", { name: "import-text" }).fill(""); + await page.getByRole("button", { name: "import-text-submit" }).click(); + await expect(page.getByTestId("display-empty-state")).toBeVisible(); + await expectDisplaySheetSlotToNotExist(page, 1); }); test("the placeholder text of the text importer", async ({ @@ -458,18 +284,23 @@ test.describe("ImportText", () => { network, }) => { network.use(sampleCards, ...defaultHandlers); - page.addInitScript({ content: "Math.random = () => 1;" }); + // Fix round (2026-07-23, this port): the pre-existing classic-editor version of this test + // never `await`ed this call, racing addInitScript's own CDP registration against the very + // next line's `page.goto()` - harmless there (empirically fine against the classic route in + // whatever timing that page happened to hydrate on), but flaky against this page's own + // hydration path, observed here as a genuinely real (non-1) Math.random() reaching + // formatPlaceholderText. Awaiting it removes the race outright rather than papering over a + // flake with a retry. + await page.addInitScript({ content: "Math.random = () => 1;" }); await loadPageWithDefaultBackend(page); - await openImportTextModal(page); - await expect(page.getByTestId("import-text")).toMatchAriaSnapshot(` - - paragraph: Type the names of the cards you'd like to add to your order and hit Submit. One card per line. - - heading "Syntax Guide" [level=2]: - - button "Syntax Guide" - - textbox "import-text": - - /placeholder: "4x ${cardDocument1.name}\\n4x ${cardDocument2.name}\\n4x ${cardDocument3.name}\\n4x ${cardDocument4.name}\\n\\n4x t:${cardDocument6.name}\\n\\n4x b:${cardDocument5.name}" - - paragraph: "Hint: Submit with Control+Enter." - `); + await expect(page.getByTestId("display-empty-state")).toBeVisible(); + await expect( + page.getByRole("textbox", { name: "import-text" }) + ).toHaveAttribute( + "placeholder", + `4x ${cardDocument1.name}\n4x ${cardDocument2.name}\n4x ${cardDocument3.name}\n4x ${cardDocument4.name}\n\n4x t:${cardDocument6.name}\n\n4x b:${cardDocument5.name}` + ); }); test("the textbox should clear itself after submitting a list", async ({ @@ -485,18 +316,20 @@ test.describe("ImportText", () => { ); await loadPageWithDefaultBackend(page); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); - // import a card - await importText(page, "my search query"); + await importTextOnEditorLanding(page, "my search query"); // a card slot should have been created - expectCardSlotToExist(page, 1); + await expectDisplaySheetSlotStates( + page, + [{ slot: 1, name: cardDocument1.name }], + [] + ); - // open the window again and assert that the textbox is empty - await openImportTextModal(page); + // the populated toolbar's own inline search bar should be empty (onImportComplete clears + // searchBarText - DisplayPage.tsx's own comment on the inline ImportText mount) await expect( - page.getByRole("textbox", { name: "import-text" }) + page.getByRole("textbox", { name: "import-text-inline" }) ).toHaveValue(""); }); @@ -513,15 +346,12 @@ test.describe("ImportText", () => { ); await loadPageWithDefaultBackend(page); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); - - // open the window and set some text - await openImportTextModal(page); + // set some text without submitting await page.getByRole("textbox", { name: "import-text" }).fill("big test"); - // close the window and reopen it - await page.getByRole("button", { name: "import-text-close" }).click(); - await openImportTextModal(page); + // reloading the same landing page (still empty, nothing was submitted) keeps whatever the + // uncontrolled textarea holds - the classic modal's own "close and reopen" affordance has no + // equivalent here (there's no modal to close), so this just re-reads the same field. await expect( page.getByRole("textbox", { name: "import-text" }) ).toHaveValue("big test"); @@ -540,10 +370,7 @@ test.describe("ImportText", () => { ); await loadPageWithDefaultBackend(page); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); - // import a card, submitting the form with ctrl+enter - await openImportTextModal(page); await page.getByRole("textbox", { name: "import-text" }).click(); await page .getByRole("textbox", { name: "import-text" }) @@ -551,26 +378,11 @@ test.describe("ImportText", () => { await page.keyboard.press("Control+Enter"); // a card slot should have been created - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, - [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - ], - [ - { - slot: 1, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - ] + [{ slot: 1, name: cardDocument1.name }], + [{ slot: 1, name: cardDocument2.name }] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); // should not have changed }); test("importing a decklist line with a set code selects the community-resolved printing match and shows the indicator", async ({ @@ -585,10 +397,23 @@ test.describe("ImportText", () => { ...defaultHandlers ); await loadPageWithDefaultBackend(page); - await importText(page, "1 Lightning Bolt (2ED) 162"); - await expectCardGridSlotState(page, 1, "front", cardDocument12.name, 1, 1); - await expect( - page.getByTestId("front-slot0").getByTestId("printing-match-indicator") - ).toBeVisible(); + await importTextOnEditorLanding(page, "1 Lightning Bolt (2ED) 162"); + await expectDisplaySheetSlotStates( + page, + [{ slot: 1, name: cardDocument12.name }], + [] + ); + // The classic grid's own "printing-match-indicator" badge (this test's other assertion, + // pre-port) is rendered by Card.tsx's small-thumbnail CardImage path only - PagePreview's + // sheet slots (this page's own image render, see this file's module comment) don't mount that + // component, so there's no equivalent element to check here. The unified page's own signal for + // this same condition (a resolved, non-degraded printing-specific import) is the rail's + // `requested-printing-badge`, already covered end-to-end by DisplayPage.spec.ts's "the + // requested-printing badge shows the plain style..." test - not duplicated here. + await page.getByTestId("page-preview-slot").first().click(); + await expect(page.getByTestId("requested-printing-badge")).toHaveAttribute( + "data-degraded", + "false" + ); }); }); diff --git a/frontend/tests/ImportXML.spec.ts b/frontend/tests/ImportXML.spec.ts index b8f3cf9f1..ea3cce1d7 100644 --- a/frontend/tests/ImportXML.spec.ts +++ b/frontend/tests/ImportXML.spec.ts @@ -24,30 +24,30 @@ import { import { test } from "../playwright.setup"; import { - expectCardbackSlotState, - expectCardGridSlotStates, - expectCardSlotToExist, + expectDisplaySheetSlotStates, + expectDisplaySheetSlotToExist, importTextOnEditorLanding, - importXML, + importXMLFromToolbar, + importXMLOnEmptyLanding, 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" - ); -}); +// Proposal H parity port (2026-07-23, issue #272 wave 1): ported onto the unified /editor page. +// 9 of these 10 tests import into an EMPTY project - DisplayPage's empty-project landing mounts +// the same plain ImportXML component verbatim, inline inside a collapsed "Import a File or URL" +// accordion (importXMLOnEmptyLanding, test-utils.ts). The remaining test ("into a non-empty +// project") uses the populated toolbar's own "Add Cards" dropdown instead - the SAME classic +// Import.tsx dropdown, unforked, just mounted in a different container (importXMLFromToolbar). +// Per-slot assertions are ported via expectDisplaySheetSlotStates - see that helper's own comment +// for why dropping the selectedImage/totalImages numeric checks is still a faithful port here +// (every fixture card has its own distinct name). +// +// The classic grid's standalone "common cardback" preview tile (`expectCardbackSlotState`, this +// file's own pre-port version called it both before AND after every import) has no landing-page +// equivalent at all (there's no sheet, hence no cardback slot, until a project actually exists) - +// dropped rather than faked; its post-import state is already covered by the `backs` array of the +// relevant `expectDisplaySheetSlotStates` call in every test below that touches the project +// cardback. test.describe("ImportXML", () => { test("importing one card by XML into an empty project", async ({ @@ -63,9 +63,7 @@ test.describe("ImportXML", () => { ); await loadPageWithDefaultBackend(page); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); - - await importXML( + await importXMLOnEmptyLanding( page, `
@@ -86,26 +84,11 @@ test.describe("ImportXML", () => { ` ); - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, - [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - ], - [ - { - slot: 1, - name: cardDocument3.name, - selectedImage: 2, - totalImages: 2, - }, - ] + [{ slot: 1, name: cardDocument1.name }], + [{ slot: 1, name: cardDocument3.name }] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); }); test("importing multiple instances of one card by XML into an empty project", async ({ @@ -121,9 +104,7 @@ test.describe("ImportXML", () => { ); await loadPageWithDefaultBackend(page); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); - - await importXML( + await importXMLOnEmptyLanding( page, `
@@ -144,38 +125,17 @@ test.describe("ImportXML", () => { ` ); - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - { - slot: 2, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, + { slot: 1, name: cardDocument1.name }, + { slot: 2, name: cardDocument1.name }, ], [ - { - slot: 1, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - { - slot: 2, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, + { slot: 1, name: cardDocument2.name }, + { slot: 2, name: cardDocument2.name }, ] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); }); test("importing one specific card version by XML into an empty project", async ({ @@ -191,9 +151,7 @@ test.describe("ImportXML", () => { ); await loadPageWithDefaultBackend(page); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); - - await importXML( + await importXMLOnEmptyLanding( page, `
@@ -214,26 +172,11 @@ test.describe("ImportXML", () => { ` ); - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, - [ - { - slot: 1, - name: cardDocument3.name, - selectedImage: 3, - totalImages: 3, - }, - ], - [ - { - slot: 1, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - ] + [{ slot: 1, name: cardDocument3.name }], + [{ slot: 1, name: cardDocument2.name }] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); }); test("importing one card of each type into an empty project", async ({ @@ -249,9 +192,7 @@ test.describe("ImportXML", () => { ); await loadPageWithDefaultBackend(page); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); - - await importXML( + await importXMLOnEmptyLanding( page, `
@@ -284,50 +225,19 @@ test.describe("ImportXML", () => { ` ); - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - { - slot: 2, - name: cardDocument6.name, - selectedImage: 1, - totalImages: 1, - }, - { - slot: 3, - name: cardDocument5.name, - selectedImage: 1, - totalImages: 1, - }, + { slot: 1, name: cardDocument1.name }, + { slot: 2, name: cardDocument6.name }, + { slot: 3, name: cardDocument5.name }, ], [ - { - slot: 1, - name: cardDocument3.name, - selectedImage: 2, - totalImages: 2, - }, - { - slot: 2, - name: cardDocument3.name, - selectedImage: 2, - totalImages: 2, - }, - { - slot: 3, - name: cardDocument3.name, - selectedImage: 2, - totalImages: 2, - }, + { slot: 1, name: cardDocument3.name }, + { slot: 2, name: cardDocument3.name }, + { slot: 3, name: cardDocument3.name }, ] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); }); test("importing a more complex XML into an empty project", async ({ @@ -343,9 +253,7 @@ test.describe("ImportXML", () => { ); await loadPageWithDefaultBackend(page); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); - - await importXML( + await importXMLOnEmptyLanding( page, `
@@ -380,50 +288,19 @@ test.describe("ImportXML", () => { ` ); - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, [ - { - slot: 1, - name: cardDocument3.name, - selectedImage: 3, - totalImages: 4, - }, - { - slot: 2, - name: cardDocument3.name, - selectedImage: 3, - totalImages: 4, - }, - { - slot: 3, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 4, - }, + { slot: 1, name: cardDocument3.name }, + { slot: 2, name: cardDocument3.name }, + { slot: 3, name: cardDocument1.name }, ], [ - { - slot: 1, - name: cardDocument4.name, - selectedImage: 4, - totalImages: 4, - }, - { - slot: 2, - name: cardDocument4.name, - selectedImage: 4, - totalImages: 4, - }, - { - slot: 3, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, + { slot: 1, name: cardDocument4.name }, + { slot: 2, name: cardDocument4.name }, + { slot: 3, name: cardDocument2.name }, ] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); }); test("importing an XML with gaps into an empty project", async ({ @@ -439,9 +316,7 @@ test.describe("ImportXML", () => { ); await loadPageWithDefaultBackend(page); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); - - await importXML( + await importXMLOnEmptyLanding( page, `
@@ -476,52 +351,21 @@ test.describe("ImportXML", () => { ` ); - await expectCardSlotToExist(page, 1); - await expectCardGridSlotStates( + await expectDisplaySheetSlotToExist(page, 1); + await expectDisplaySheetSlotStates( page, [ - { - slot: 1, - name: cardDocument3.name, - selectedImage: 3, - totalImages: 4, - }, - { - slot: 2, - name: cardDocument3.name, - selectedImage: 3, - totalImages: 4, - }, - { - slot: 4, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 4, - }, + { slot: 1, name: cardDocument3.name }, + { slot: 2, name: cardDocument3.name }, + { slot: 4, name: cardDocument1.name }, ], [ - { - slot: 1, - name: cardDocument4.name, - selectedImage: 4, - totalImages: 4, - }, - { - slot: 2, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - { - slot: 4, - name: cardDocument4.name, - selectedImage: 4, - totalImages: 4, - }, + { slot: 1, name: cardDocument4.name }, + { slot: 2, name: cardDocument2.name }, + { slot: 4, name: cardDocument4.name }, ] ); - await expectCardSlotToExist(page, 3); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); + await expectDisplaySheetSlotToExist(page, 3); }); test("importing a more complex XML into a non-empty project", async ({ @@ -541,29 +385,14 @@ test.describe("ImportXML", () => { page, `1x my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, - [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 4, - }, - ], - [ - { - slot: 1, - name: cardDocument2.name, - selectedImage: 1, - totalImages: 2, - }, - ] + [{ slot: 1, name: cardDocument1.name }], + [{ slot: 1, name: cardDocument2.name }] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); - // import a few more cards - await importXML( + // import a few more cards via the populated toolbar's "Add Cards" dropdown + await importXMLFromToolbar( page, `
@@ -598,50 +427,19 @@ test.describe("ImportXML", () => { ` ); - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, [ - { - slot: 2, - name: cardDocument3.name, - selectedImage: 3, - totalImages: 4, - }, - { - slot: 3, - name: cardDocument3.name, - selectedImage: 3, - totalImages: 4, - }, - { - slot: 4, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 4, - }, + { slot: 2, name: cardDocument3.name }, + { slot: 3, name: cardDocument3.name }, + { slot: 4, name: cardDocument1.name }, ], [ - { - slot: 2, - name: cardDocument4.name, - selectedImage: 4, - totalImages: 4, - }, - { - slot: 3, - name: cardDocument4.name, - selectedImage: 4, - totalImages: 4, - }, - { - slot: 4, - name: cardDocument3.name, - selectedImage: 2, - totalImages: 2, - }, + { slot: 2, name: cardDocument4.name }, + { slot: 3, name: cardDocument4.name }, + { slot: 4, name: cardDocument3.name }, ] ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); }); test("import an XML and use its cardback", async ({ page, network }) => { @@ -654,9 +452,7 @@ test.describe("ImportXML", () => { ); await loadPageWithDefaultBackend(page); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); - - await importXML( + await importXMLOnEmptyLanding( page, `
@@ -678,26 +474,11 @@ test.describe("ImportXML", () => { true ); - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, - [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - ], - [ - { - slot: 1, - name: cardDocument3.name, // the cardback specified in XML - selectedImage: 2, - totalImages: 2, - }, - ] + [{ slot: 1, name: cardDocument1.name }], + [{ slot: 1, name: cardDocument3.name }] // the cardback specified in XML ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); // the project cardback should not have changed }); test("import an XML and use the project cardback", async ({ @@ -713,9 +494,7 @@ test.describe("ImportXML", () => { ); await loadPageWithDefaultBackend(page); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); - - await importXML( + await importXMLOnEmptyLanding( page, `
@@ -737,26 +516,11 @@ test.describe("ImportXML", () => { false ); - await expectCardGridSlotStates( + await expectDisplaySheetSlotStates( page, - [ - { - slot: 1, - name: cardDocument1.name, - selectedImage: 1, - totalImages: 1, - }, - ], - [ - { - slot: 1, - name: cardDocument2.name, // the cardback configured for the project - selectedImage: 1, - totalImages: 2, - }, - ] + [{ slot: 1, name: cardDocument1.name }], + [{ slot: 1, name: cardDocument2.name }] // the cardback configured for the project ); - await expectCardbackSlotState(page, cardDocument2.name, 1, 2); // cardback should not have changed }); // Foreign-order resilience Phase 1 follow-up (issue #324, owner-observed 2026-07-23): a BRAND @@ -770,6 +534,17 @@ test.describe("ImportXML", () => { // initialised from the import when nothing was selected before it) and its own gate against // regressing the "should not have changed" tests above (an EXISTING project cardback is never // touched by a later import). + // + // The assertion below was rewritten (2026-07-23, following PR #398's PagePreview orphan-badge + // parity fix) to check the /editor sheet's own per-slot corner badge instead of the classic + // "Common Cardback" panel - DisplayPage has no equivalent persistent tile (only the + // CardbackToolbarButton picker, per this file's own header comment), so the fix's underlying + // behaviour (a BRAND NEW project's cardback initialises from the import, orphan or not) is now + // observed the same way OrphanRendering.spec.ts's own "b:null" case observes it: the sheet's + // `page-preview-slot`, toggled to its back face, showing both the resolved orphan image and the + // `orphan-badge` corner treatment (PagePreview.tsx's `orphanLabel`, same testid/text as + // Card.tsx's classic-editor badge). Same testid convention, same "Your file" text, kept + // consistent with OrphanRendering.spec.ts so the two files don't drift. test("importing an XML into a brand new project with no cardback yet initialises the Common Cardback panel from the file's own - even an orphan", async ({ page, network, @@ -780,7 +555,7 @@ test.describe("ImportXML", () => { const orphanFrontId = "1FItgPw7VK_Tbv6dMiqdy5zd-jAoEC9mn"; const orphanBackId = "1LrVX0pUcye9n_0RtaDNVl2xPrQgn7CYf"; - await importXML( + await importXMLOnEmptyLanding( page, `
@@ -801,8 +576,24 @@ test.describe("ImportXML", () => { ` ); - const commonCardback = page.getByTestId("common-cardback"); - await expect(commonCardback.getByTestId("orphan-badge")).toBeVisible(); - await expect(commonCardback).not.toContainText("Card Not Found"); + const slot = page.getByTestId("page-preview-slot").first(); + const frontImage = slot.locator("img"); + await expect(frontImage).toHaveCount(1, { timeout: 45_000 }); + await expect(frontImage).toHaveAttribute( + "src", + `https://lh4.googleusercontent.com/d/${orphanFrontId}=h800` + ); + + // The imported (an orphan here) is this test's own point - toggle to the back + // face and confirm the project's cardback initialised from it, including the sheet's own + // orphan corner badge (see the comment above). + await page.getByRole("button", { name: /Showing: Fronts/ }).click(); + const backImage = slot.locator("img"); + await expect(backImage).toHaveCount(1, { timeout: 45_000 }); + await expect(backImage).toHaveAttribute( + "src", + `https://lh4.googleusercontent.com/d/${orphanBackId}=h800` + ); + await expect(slot.getByTestId("orphan-badge")).toHaveText("Your file"); }); }); diff --git a/frontend/tests/MatureContentToggle.spec.ts b/frontend/tests/MatureContentToggle.spec.ts index 5917cd460..abea1ac44 100644 --- a/frontend/tests/MatureContentToggle.spec.ts +++ b/frontend/tests/MatureContentToggle.spec.ts @@ -12,28 +12,16 @@ import { import { test } from "../playwright.setup"; import { - expectCardGridSlotState, + expectDisplaySheetSlotState, importTextOnEditorLanding, loadPageWithDefaultBackend, openSearchSettingsModal, } 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" - ); -}); +// Proposal H parity port (2026-07-23, issue #272 wave 1): ported onto the unified /editor page - +// the Search Settings modal (openSearchSettingsModal) is the same self-contained, unforked +// component DisplayPage.tsx's own toolbar mounts (see DisplayPage.spec.ts's own precedent test), +// reachable directly with no other DOM changes needed. // saving search settings only re-triggers a search when the project has queries to re-run, // so each test imports one card first @@ -43,7 +31,7 @@ const loadPageWithOneCard = async (page: any) => { page, `my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await expectCardGridSlotState(page, 1, "front", cardDocument1.name, 1, 1); + await expectDisplaySheetSlotState(page, 1, "front", cardDocument1.name); }; test.describe("show mature content toggle", () => { diff --git a/frontend/tests/ReportCard.spec.ts b/frontend/tests/ReportCard.spec.ts index 26638eefe..08cf9844c 100644 --- a/frontend/tests/ReportCard.spec.ts +++ b/frontend/tests/ReportCard.spec.ts @@ -14,32 +14,16 @@ import { import { test } from "../playwright.setup"; import { - expectCardGridSlotState, importTextOnEditorLanding, loadPageWithDefaultBackend, + openDetailedView, } 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" - ); -}); - -const openDetailedView = async (page: any, cardName: string) => { - await page.getByAltText(cardName).click(); - await expect(page.getByText("Card Details")).toBeVisible(); -}; +// Proposal H parity port (2026-07-23, issue #272 wave 1): ported onto the unified /editor page - +// ReportCardPanel lives inside CardDetailedViewModal's own "Report" region (ReportBlock, +// CardDetailedViewBody.tsx), reached the same way the rest of this cluster reaches the modal - see +// openDetailedView's own module comment (test-utils.ts) for the Browse-mode route and the "Card +// details" text-collision fix. const setUpCardAndOpenModal = async (page: any) => { await loadPageWithDefaultBackend(page); @@ -47,8 +31,7 @@ const setUpCardAndOpenModal = async (page: any) => { page, `my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await expectCardGridSlotState(page, 1, "front", cardDocument1.name, 1, 1); - await openDetailedView(page, cardDocument1.name); + await openDetailedView(page, "my search query", cardDocument1.identifier); }; test.describe("report card flow", () => { diff --git a/frontend/tests/VotePickers.spec.ts b/frontend/tests/VotePickers.spec.ts index 0be7dc6f5..25d3cca1f 100644 --- a/frontend/tests/VotePickers.spec.ts +++ b/frontend/tests/VotePickers.spec.ts @@ -29,39 +29,15 @@ import { test } from "../playwright.setup"; import { importTextOnEditorLanding, loadPageWithDefaultBackend, + openDetailedView, } 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" - ); -}); - -// Shared by all three picker suites below (identical across their source files). -// -// Proposal H switchover (2026-07-23, issues #231/#272) note: /editor now resolves to the -// unified page, whose own left rail ALSO carries "Card details" text (offcanvas title, handle -// button) in addition to CardDetailedViewBody's own "Card Details" heading - a plain -// getByText("Card Details") (case-insensitive substring by default) matched all three and threw -// a strict-mode violation. The heading role disambiguates to the one that's actually -// CardDetailedViewBody's own content, which is what this helper always meant to assert on. -const openDetailedView = async (page: any, cardName: string) => { - await page.getByAltText(cardName).click(); - await expect( - page.getByRole("heading", { name: "Card Details" }) - ).toBeVisible(); -}; +// Proposal H parity port (2026-07-23, issue #272 wave 1): ported onto the unified /editor page. +// Shared by all three picker suites below (identical across their source files) - the pickers +// themselves live inside CardDetailedViewModal's own "Printing Tags" region (PrintingTagsBlock, +// CardDetailedViewBody.tsx), reached the same way the rest of this cluster reaches the modal - see +// openDetailedView's own module comment (test-utils.ts) for the Browse-mode route and the "Card +// details" text-collision fix. test.describe("ArtistVotePicker tests", () => { test("shows the attribute-voting panel once printing consensus is unresolved, listing candidate artists", async ({ @@ -86,7 +62,7 @@ test.describe("ArtistVotePicker tests", () => { page, `my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await openDetailedView(page, cardDocument1.name); + await openDetailedView(page, "my search query", cardDocument1.identifier); // longer than the default 5s: showing this panel is gated behind a chain of fetches // (printing consensus resolves -> CardDetailedViewModal re-renders -> the panel mounts @@ -124,7 +100,7 @@ test.describe("ArtistVotePicker tests", () => { page, `my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await openDetailedView(page, cardDocument1.name); + await openDetailedView(page, "my search query", cardDocument1.identifier); const artistPicker = page.getByTestId("artist-vote-picker"); await expect(artistPicker.getByText("Not yet resolved")).toBeVisible(); @@ -163,7 +139,7 @@ test.describe("TagVotePicker tests", () => { page, `my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await openDetailedView(page, cardDocument1.name); + await openDetailedView(page, "my search query", cardDocument1.identifier); const tagPicker = page.getByTestId("tag-vote-picker"); await expect(tagPicker.getByText("Borderless")).toBeVisible(); @@ -193,7 +169,7 @@ test.describe("TagVotePicker tests", () => { page, `my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await openDetailedView(page, cardDocument1.name); + await openDetailedView(page, "my search query", cardDocument1.identifier); const tagPicker = page.getByTestId("tag-vote-picker"); // "Borderless" has displayName "Frameless Border" set - shown instead of the raw name @@ -228,7 +204,7 @@ test.describe("TagVotePicker tests", () => { page, `my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await openDetailedView(page, cardDocument1.name); + await openDetailedView(page, "my search query", cardDocument1.identifier); const tagPicker = page.getByTestId("tag-vote-picker"); await tagPicker.getByText("Borderless").click(); @@ -259,7 +235,7 @@ test.describe("PrintingTagPicker tests", () => { page, `my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await openDetailedView(page, cardDocument1.name); + await openDetailedView(page, "my search query", cardDocument1.identifier); await expect( page.getByTestId("detailed-view").getByText("What's That Card?") @@ -337,7 +313,7 @@ test.describe("PrintingTagPicker tests", () => { page, `my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await openDetailedView(page, cardDocument1.name); + await openDetailedView(page, "my search query", cardDocument1.identifier); const picker = page.getByTestId("printing-tag-picker"); await expect(page.getByText("Not yet resolved")).toBeVisible(); diff --git a/frontend/tests/test-utils.ts b/frontend/tests/test-utils.ts index a133fec3a..10c29b2d8 100644 --- a/frontend/tests/test-utils.ts +++ b/frontend/tests/test-utils.ts @@ -77,6 +77,142 @@ export const importTextOnEditorLanding = async (page: Page, text: string) => { ).not.toBeVisible(); }; +// Card-detail modal ecosystem parity port (2026-07-23, issue #272 wave 1). The classic editor +// opened the real, unforked `CardDetailedViewModal` (`data-testid="detailed-view"`) by clicking +// ANY card image in the grid. On the unified `/editor` page that same click behaviour +// (`showDetailedViewOnClick`, `Card.tsx`) is suppressed everywhere a `cardOnClick` handler is +// also wired - which is every `EditorCard` mount on this page except one: the Browse-mode catalog +// tiles `CatalogBrowseResults.tsx` renders have no `cardOnClick` at all, so clicking one still +// opens the real modal, unmodified. (Sheet slots select the rail instead; Select Version's own +// candidate tiles select an image instead - neither opens this modal any more.) This helper +// re-runs the caller's own already-mocked query through Browse mode and opens that result's +// detail view - the KNOWN fix this whole cluster needed (2026-07-23 triage): the unified page's +// left rail ALSO renders "Card details" text verbatim, lowercased, in its offcanvas dialog title +// and edge-handle button (both present in the DOM regardless of viewport/rail state) - a bare +// `getByText("Card Details")` (case-insensitive substring match by default) hit a 3-way +// strict-mode collision. Scoping to the modal's own `detailed-view` testid (NOT a heading role - +// react-bootstrap's `Modal.Title` renders a styled `
`, not a real heading element) resolves +// it once, here, for the whole cluster - consolidated out of near-duplicate helpers previously +// declared separately in ArtistSupportLink/VotePickers/ReportCard/AddCardToFavorites' own spec +// files (each still assuming the classic grid's plain "click the grid image" flow). +export const openDetailedView = async ( + page: Page, + query: string, + cardIdentifier: string +) => { + await page.getByTestId("display-search-mode-browse").click(); + await page.getByTestId("display-browse-search-input").fill(query); + const tile = page.getByTestId(`catalog-browse-tile-${cardIdentifier}`); + await expect(tile).toBeVisible(); + await tile.locator("img").click(); + await expect( + page.getByTestId("detailed-view").getByText("Card Details") + ).toBeVisible(); +}; + +export const closeDetailedView = async (page: Page) => { + await page.getByTestId("detailed-view").getByLabel("Close").click(); + await expect(page.getByTestId("detailed-view")).not.toBeVisible(); +}; + +// Proposal H parity port (2026-07-23, issue #272 wave 1): the classic grid's `front-slot`/ +// `back-slot` testids (and the "N / M" selected/total-image fraction `CardSlot.tsx` rendered +// inline on each one) have no equivalent on the unified page - `PagePreview.tsx` renders each +// sheet slot as a plain, un-testid'd `{cardName}`, with no inline candidate-count +// readout at all (that signal, where it exists, lives one layer deeper - the slot's own rail - +// and isn't reconstructable slot-by-slot without opening each one individually, which these +// import-cluster tests never needed to do before). These two helpers port the part that IS +// cleanly available: which named card is showing, for a given (1-based, row-major) sheet slot and +// face. `selectedImage`/`totalImages` numeric assertions are dropped, not silently - every +// fixture in this suite gives each result-set index its own distinct `name` (Card 1/2/3/...), so +// asserting the right NAME landed in the right slot already fully captures what those counts were +// standing in for (which specific candidate got auto-selected); see this port's own report for +// the one place that stops being true. +export const ensureDisplayFace = async (page: Page, face: "front" | "back") => { + const wantLabel = face === "front" ? "Showing: Fronts" : "Showing: Backs"; + const otherLabel = face === "front" ? "Showing: Backs" : "Showing: Fronts"; + if (await page.getByText(otherLabel).isVisible()) { + await page.getByText(otherLabel).click(); + } + await expect(page.getByText(wantLabel)).toBeVisible(); +}; + +export const expectDisplaySheetSlotState = async ( + page: Page, + slot: number, + face: "front" | "back", + cardName: string +) => { + await ensureDisplayFace(page, face); + const sheetSlot = page.getByTestId("page-preview-slot").nth(slot - 1); + await expect(sheetSlot.locator("img")).toHaveAttribute("alt", cardName); +}; + +interface DisplaySheetSlotAssertion { + slot: number; + name: string; +} + +export const expectDisplaySheetSlotStates = async ( + page: Page, + fronts: Array, + backs: Array +) => { + for (const { slot, name } of fronts) { + await expectDisplaySheetSlotState(page, slot, "front", name); + } + for (const { slot, name } of backs) { + await expectDisplaySheetSlotState(page, slot, "back", name); + } + // leave face state as fronts, matching expectCardGridSlotStates' own toggle-back convention + await ensureDisplayFace(page, "front"); +}; + +// Every sheet position renders a `page-preview-slot` div regardless of whether it's a real +// project member (PagePreview.tsx's own comment: "every cell gets a slot, only some get an +// "), so an unfilled/no-query "gap" slot (a real project member with nothing resolved for it +// yet) is indistinguishable at a glance from a genuinely-past-the-end-of-the-deck empty grid cell +// - neither renders an ``. Clicking through to the rail disambiguates: only a real project +// member selects it (`display-rail-header` shows "Slot N"); a past-the-end grid position ignores +// the click (DisplayPage.tsx's own onSlotClick guard) and the rail stays idle. +export const expectDisplaySheetSlotToExist = async ( + page: Page, + slot: number +) => { + await page + .getByTestId("page-preview-slot") + .nth(slot - 1) + .click(); + await expect(page.getByTestId("display-rail-header")).toContainText( + `Slot ${slot}` + ); +}; + +export const expectDisplaySheetSlotToNotExist = async ( + page: Page, + slot: number +) => { + await expect( + page + .getByTestId("page-preview-slot") + .nth(slot - 1) + .locator("img") + ).toHaveCount(0); +}; + +// The populated-project toolbar's compact search-bar row (ImportText's "inline" variant, no +// Submit button of its own - a plain browser form submit fires on Enter, see ImportText.tsx's own +// comment) - the unified page's equivalent of importText's "add more cards to a non-empty +// project" step above. +export const importTextInline = async (page: Page, text: string) => { + const field = page.getByRole("textbox", { name: "import-text-inline" }); + await field.fill(text); + await field.press("Enter"); + await expect( + page.locator('span:has-text("Loading your cards...")') + ).not.toBeVisible(); +}; + export const importText = async (page: Page, text: string) => { await openImportTextModal(page); await page.getByRole("textbox", { name: "import-text" }).fill(text); @@ -262,6 +398,107 @@ export const importXML = async ( ).not.toBeAttached(); }; +// Import cluster parity port (2026-07-23, issue #272 wave 1). DisplayPage's EMPTY-project landing +// (`display-empty-state`) mounts the bare `ImportCSV`/`ImportXML` components verbatim (DisplayPage +// module comment: "the same plain ImportText/ImportURL/ImportXML/ImportCSV components +// ProjectEditor.tsx's own AddCardsPanel mounts") directly inline inside a collapsed "Import a File +// or URL" Accordion - not behind the classic "Add Cards" dropdown-triggered modal +// openImportCSVModal/openImportXMLModal open (that dropdown only mounts once the project already +// has a member - see openDisplayToolbarAddCardsDropdown below for that non-empty-project path). +// The underlying `TextFileDropzone` `label`s ("import-csv"/"import-xml") are identical either way +// - only how you REACH the form differs. +export const importCSVOnEmptyLanding = async ( + page: Page, + fileContents: string +) => { + await page.getByRole("button", { name: "CSV", exact: false }).click(); + const fileInput = page + .getByLabel("import-csv") + .locator('input[type="file"]') + .first(); + const buffer = Buffer.from(fileContents); + await fileInput.setInputFiles({ + name: "test.csv", + mimeType: "text/csv", + buffer: buffer, + }); + await expect( + page.locator('span:has-text("Loading your cards...")') + ).not.toBeVisible(); +}; + +export const importXMLOnEmptyLanding = async ( + page: Page, + fileContents: string, + useXMLCardback: boolean = true +) => { + await page.getByRole("button", { name: "XML", exact: false }).click(); + if (!useXMLCardback) { + await page.getByText("Use XML Cardback").click(); + } + const fileInput = page + .getByLabel("import-xml") + .locator('input[type="file"]') + .first(); + const buffer = Buffer.from(fileContents); + await fileInput.setInputFiles({ + name: "test.xml", + mimeType: "text/xml;charset=utf-8", + buffer: buffer, + }); + await expect( + page.locator('span:has-text("Loading your cards...")') + ).not.toBeAttached(); +}; + +// The non-empty-project counterpart of the two helpers above: once a project has at least one +// member, DeckInputLanding (and its inline CSV/XML accordion) is no longer rendered at all - +// DisplayPage's populated toolbar mounts `` instead, the SAME "Add Cards" dropdown +// (Text/XML/CSV/URL, unforked) the classic grid's own right panel used (DisplayPage module +// comment: "the existing Import.tsx dropdown ... mounted verbatim"). Only the surrounding +// container differs (`display-toolbar` here vs. `right-panel` there) - openAddCardsDropdown/ +// getAddCardsMenu above stay untouched (their own callers, e.g. Toasts.spec.ts's still-skipped +// assertions, target the classic container specifically) rather than generalized to cover both. +export const openDisplayToolbarAddCardsDropdown = async (page: Page) => { + const textButton = page.getByRole("button", { name: " Text" }); + if (await textButton.isVisible()) { + return; + } + await expect(async () => { + await page + .getByTestId("display-toolbar") + .getByText("Add Cards", { exact: false }) + .click(); + await expect(textButton).toBeVisible(); + }).toPass({ timeout: 10_000 }); +}; + +export const importXMLFromToolbar = async ( + page: Page, + fileContents: string, + useXMLCardback: boolean = true +) => { + await openDisplayToolbarAddCardsDropdown(page); + await page.getByRole("button", { name: "XML", exact: false }).click(); + const modal = page.getByTestId("import-xml"); + + if (!useXMLCardback) { + await modal.getByText("Use XML Cardback").click(); + } + + const fileInput = modal.locator('input[type="file"]').first(); + const buffer = Buffer.from(fileContents); + await fileInput.setInputFiles({ + name: "test.xml", + mimeType: "text/xml;charset=utf-8", + buffer: buffer, + }); + + await expect( + page.locator('span:has-text("Loading your cards...")') + ).not.toBeAttached(); +}; + export const downloadXML = async (page: Page): Promise<[string, string]> => { const downloadPromise = page.waitForEvent("download"); await page.getByRole("button", { name: " Download" }).click(); @@ -286,6 +523,39 @@ export const downloadDecklist = async ( return [content, download.suggestedFilename()]; }; +// Export content-correctness parity port (2026-07-23, issue #272 wave 1). The classic grid's +// "Download" dropdown (downloadXML/downloadDecklist above) has no equivalent on the unified page - +// DisplayExportMenu.tsx composes the exact same unchanged Dropdown.Items behind a differently- +// named trigger instead (`display-export-menu-toggle`/`display-export-menu`, DisplayPage.tsx's own +// toolbar - see DisplayPage.spec.ts's own "Export ▾ toolbar menu" precedent test). The download +// functions/`export-xml-button`/`export-decklist-button` items themselves are unchanged either way +// - only how the menu is opened differs. +export const downloadXMLFromDisplayToolbar = async ( + page: Page +): Promise<[string, string]> => { + const downloadPromise = page.waitForEvent("download"); + await page.getByTestId("display-export-menu-toggle").click(); + await page.getByTestId("export-xml-button").click(); + const download = await downloadPromise; + const path = await download.path(); + if (!path) throw new Error("Download path is null"); + const content = await readFile(path, "utf-8"); + return [content, download.suggestedFilename()]; +}; + +export const downloadDecklistFromDisplayToolbar = async ( + page: Page +): Promise<[string, string]> => { + const downloadPromise = page.waitForEvent("download"); + await page.getByTestId("display-export-menu-toggle").click(); + await page.getByTestId("export-decklist-button").click(); + const download = await downloadPromise; + const path = await download.path(); + if (!path) throw new Error("Download path is null"); + const content = await readFile(path, "utf-8"); + return [content, download.suggestedFilename()]; +}; + export function normaliseString(text: string): string { return text.replaceAll(" ", "").replaceAll("\n", "").replaceAll("\r", ""); } diff --git a/frontend/tests/visual/CardDetailedViewModal.visual.spec.ts b/frontend/tests/visual/CardDetailedViewModal.visual.spec.ts index 6bf7cd4d8..a027d3416 100644 --- a/frontend/tests/visual/CardDetailedViewModal.visual.spec.ts +++ b/frontend/tests/visual/CardDetailedViewModal.visual.spec.ts @@ -13,27 +13,17 @@ import { import { test } from "../../playwright.setup"; import { - expectCardGridSlotState, importTextOnEditorLanding, loadPageWithDefaultBackend, + openDetailedView, } 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" - ); -}); +// Proposal H parity port (2026-07-23, issue #272 wave 1): ported onto the unified /editor page - +// CardDetailedViewModal (the shared, unforked component this aria snapshot targets) is reached +// via Browse mode - see openDetailedView's own module comment (test-utils.ts) for why that's the +// one surface on this page that still opens it. The snapshot itself DID need re-baselining - see +// the fix-round comment further down for why (real, unrelated content drift while this file sat +// skipped, not a route-swap DOM difference). test.describe("CardDetailedViewModal visual tests", () => { test("card detailed view modal structure", async ({ page, network }) => { @@ -51,13 +41,36 @@ test.describe("CardDetailedViewModal visual tests", () => { page, `my search query${SelectedImageSeparator}${cardDocument1.identifier}` ); - await expectCardGridSlotState(page, 1, "front", cardDocument1.name, 1, 1); + await openDetailedView(page, "my search query", cardDocument1.identifier); - await page.getByAltText(cardDocument1.name).click(); - await expect(page.getByText("Card Details")).toBeVisible(); await expect(page.getByText("English")).toBeVisible(); await expect(page.getByText("Not yet resolved")).toBeVisible(); + // Fix round (2026-07-23, this port): toMatchAriaSnapshot asserts the container's full + // accessibility tree, not a partial/contains match - AttributeVotingPanel (VotePickers.spec.ts's + // own precedent notes this is gated behind a chain of fetches slower than a single round trip) + // mounts asynchronously once printing consensus resolves unresolved, same as "Not yet + // resolved" above; waiting for its own heading here avoids a race between that panel finishing + // its mount and the snapshot assertion running. + await expect( + page.getByRole("heading", { name: "Who's the artist?" }) + ).toBeVisible({ timeout: 10000 }); + // Fix round (2026-07-23, this port) - real baseline drift, unrelated to the route swap + // itself: this modal has grown three real features since this snapshot was last verified + // green (Add to Favorites/AddCardToFavorites.spec.ts, Report this card/ReportCard.spec.ts, + // What's That Card?/PrintingTagsBlock+AttributeVotingPanel - VotePickers.spec.ts), none of + // which this file's own snapshot had ever captured. Regenerated from the real, current, fully- + // settled DOM (`ariaSnapshot()` printed directly, once "Who's the artist?" above confirmed + // AttributeVotingPanel had finished mounting) rather than hand-edited. The full-res left-column + // image's own loading spinner (`status: Loading...`, `MemoizedCardImage`'s `showSpinner`) is + // the one node deliberately left out below - genuinely present in this sandbox (no real network + // egress to the CDN host these mock fixtures point `smallThumbnailUrl`-less cards at, so the + // image request never resolves) but not a meaningful assertion for this test, and liable to + // flip absent wherever the image genuinely does load in time (e.g. a real CI runner with + // internet egress) - `toMatchAriaSnapshot` tolerates a top-level node being skipped like this + // (confirmed empirically: a snapshot omitting both it and the `img` line straight after it + // still matched), unlike genuinely reordering/omitting something nested inside an otherwise- + // asserted subtree (e.g. the table's own rows), which it does not. await expect(page.getByTestId("detailed-view")).toMatchAriaSnapshot(` - text: Card Details - button "Close" @@ -84,21 +97,49 @@ test.describe("CardDetailedViewModal visual tests", () => { - row "Tags Untagged": - rowheader "Tags" - cell "Untagged" - - row /Resolution \\d+ DPI/: + - row "Resolution 1200 DPI": - rowheader "Resolution" - - cell /\\d+ DPI/ - - row /Date Created 1st January, \\d+/: + - cell "1200 DPI" + - row "Date Created 1st January, 2000": - rowheader "Date Created" - - cell /1st January, \\d+/ - - row /Date Modified 1st January, \\d+/: + - cell "1st January, 2000" + - row "Date Modified 1st January, 2000": - rowheader "Date Modified" - - cell /1st January, \\d+/ - - row /File Size \\d+ MB/: + - cell "1st January, 2000" + - row "File Size 10 MB": - rowheader "File Size" - - cell /\\d+ MB/ + - cell "10 MB" + - row "Canonical Card Unknown": + - rowheader "Canonical Card" + - cell "Unknown" + - row "Canonical Aritst Unknown": + - rowheader "Canonical Aritst" + - cell "Unknown" - button " Download Image" + - button " Add to Favorites" - spinbutton: "1" - button " Add to Project" + - button " Report this card" + - separator + - heading "What's That Card?" [level=5] + - paragraph: Help us figure out which real-world printing this card is! + - text: Not yet resolved + - textbox "Search for a different card..." + - button "None of these match No match": + - img "None of these match" + - text: No match + - button "abc 1 ABC 1 Some Artist": + - img "abc 1" + - text: ABC 1 Some Artist + - button "xyz 42 XYZ 42 Another Artist": + - img "xyz 42" + - text: XYZ 42 Another Artist + - separator + - heading "Who's the artist?" [level=6] + - text: Loading current consensus... + - textbox "Search for an artist..." + - button "Unknown artist" + - heading "Do any of these tags apply?" [level=6] - button "Close" `); }); diff --git a/frontend/tests/visual/ImportText.visual.spec.ts b/frontend/tests/visual/ImportText.visual.spec.ts index 54490b04e..f27c734c3 100644 --- a/frontend/tests/visual/ImportText.visual.spec.ts +++ b/frontend/tests/visual/ImportText.visual.spec.ts @@ -1,5 +1,7 @@ import { expect } from "@playwright/test"; +import { SelectedImageSeparator } from "@/common/constants"; +import { cardDocument1 } from "@/common/test-constants"; import { cardDocumentsThreeResults, defaultHandlers, @@ -8,24 +10,20 @@ import { } from "@/mocks/handlers"; import { test } from "../../playwright.setup"; -import { loadPageWithDefaultBackend, openImportTextModal } from "../test-utils"; +import { + importTextOnEditorLanding, + loadPageWithDefaultBackend, + openDisplayToolbarAddCardsDropdown, +} 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" - ); -}); +// Proposal H parity port (2026-07-23, issue #272 wave 1): ported onto the unified /editor page. +// `ImportTextButton` (the classic dropdown-triggered Modal this test's aria snapshot targets, +// `data-testid="import-text"`) is unforked and only reachable once a project already has a +// member - DisplayPage's own empty-project landing mounts the bare `ImportText` form directly +// instead (no Modal chrome at all - see ImportText.spec.ts's own port). Seeding one card first via +// importTextOnEditorLanding gets to the populated toolbar, whose "Add Cards" dropdown +// (openDisplayToolbarAddCardsDropdown, test-utils.ts) opens the exact same, byte-for-byte +// unmodified modal this snapshot was always asserting against. test.describe("ImportText visual tests", () => { test("import text modal structure", async ({ page, network }) => { @@ -35,10 +33,15 @@ test.describe("ImportText visual tests", () => { searchResultsThreeResults, ...defaultHandlers ); - page.addInitScript({ content: "Math.random = () => 1;" }); + await page.addInitScript({ content: "Math.random = () => 1;" }); await loadPageWithDefaultBackend(page); + await importTextOnEditorLanding( + page, + `my search query${SelectedImageSeparator}${cardDocument1.identifier}` + ); - await openImportTextModal(page); + await openDisplayToolbarAddCardsDropdown(page); + await page.getByRole("button", { name: " Text" }).click(); await expect(page.getByTestId("import-text")).toMatchAriaSnapshot(` - text: Add Cards — Text