Skip to content

Commit 5ce4d63

Browse files
committed
Preserve artifact deletion regression coverage
1 parent 9a1d594 commit 5ce4d63

1 file changed

Lines changed: 80 additions & 6 deletions

File tree

‎e2e/scenarios/artifacts.test.ts‎

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -606,8 +606,10 @@ scenario(
606606
const suffix = uniqueSuffix();
607607
const originalTitle = `Draft Dashboard ${suffix}`;
608608
const renamedTitle = `Quarterly Dashboard ${suffix}`;
609+
const listDeleteTitle = `List Card ${suffix}`;
609610

610611
let artifactId: ArtifactId | undefined;
612+
let listArtifactId: ArtifactId | undefined;
611613

612614
yield* Effect.gen(function* () {
613615
const rendered = yield* session.call("create-artifact", {
@@ -657,39 +659,111 @@ scenario(
657659
originalTitle,
658660
);
659661

662+
// A second artifact for the gallery card's own delete path: the detail
663+
// page steps below consume the renamed one, and the card's hover →
664+
// Delete affordance is a separate surface the console must keep working.
665+
const listed = yield* session.call("create-artifact", {
666+
code: artifactSource(`list-delete-${suffix}`),
667+
title: listDeleteTitle,
668+
description: "A dashboard the user will delete from the gallery",
669+
});
670+
expect(listed.ok, `create-artifact succeeded: ${listed.text}`).toBe(true);
671+
listArtifactId = structuredOf(listed).artifactId as ArtifactId;
672+
expect(listArtifactId, "the second artifact was persisted").toBeTruthy();
673+
660674
yield* browser.session(identity, async ({ page, step }) => {
661-
await step("Delete the artifact from the list", async () => {
675+
await step("Delete the artifact from its gallery card", async () => {
662676
await visit(page, "/artifacts");
677+
// Card actions reveal on hover; the card is the link's enclosing tile.
663678
const card = page.locator('[data-slot="artifact-card"]').filter({
664-
hasText: renamedTitle,
679+
hasText: listDeleteTitle,
665680
});
666681
await card.waitFor({ timeout: 20_000 });
667682
await card.hover();
668683
await card.getByRole("button", { name: "Delete" }).click();
669684

670685
const confirm = page.getByRole("alertdialog");
671-
await confirm.getByRole("heading", { name: `Delete ${renamedTitle}?` }).waitFor();
686+
await confirm.getByRole("heading", { name: `Delete ${listDeleteTitle}?` }).waitFor();
672687
await confirm.getByRole("button", { name: "Delete Artifact" }).click();
673688
await confirm.waitFor({ state: "hidden", timeout: 20_000 });
689+
await page
690+
.getByRole("link", { name: `Open artifact ${listDeleteTitle}` })
691+
.waitFor({ state: "detached", timeout: 20_000 });
674692
});
675693

676-
await step("The artifact is gone from the list", async () => {
677-
await page
694+
let releaseListRefresh = () => {};
695+
let markListRefreshStarted = () => {};
696+
const listRefreshGate = new Promise<void>((resolve) => {
697+
releaseListRefresh = resolve;
698+
});
699+
const listRefreshStarted = new Promise<void>((resolve) => {
700+
markListRefreshStarted = resolve;
701+
});
702+
703+
await step("Open the artifact and delete it from its detail page", async () => {
704+
await visit(page, "/artifacts");
705+
await page.getByRole("link", { name: `Open artifact ${renamedTitle}` }).click();
706+
await page.getByRole("heading", { name: renamedTitle }).waitFor({ timeout: 20_000 });
707+
708+
// Hold the post-delete list refresh open. The redirected gallery must
709+
// carry the optimistic removal across the route handoff rather than
710+
// relying on a fast canonical response to hide a stale-cache flash.
711+
await page.route("**/artifacts", async (route) => {
712+
if (route.request().method() !== "GET") {
713+
await route.continue();
714+
return;
715+
}
716+
markListRefreshStarted();
717+
await listRefreshGate;
718+
await route.continue();
719+
});
720+
721+
await page.getByRole("button", { name: "Delete" }).click();
722+
const confirm = page.getByRole("alertdialog");
723+
await confirm.getByRole("heading", { name: `Delete ${renamedTitle}?` }).waitFor();
724+
await confirm.getByRole("button", { name: "Delete Artifact" }).click();
725+
});
726+
727+
await step("The redirected gallery already omits the deleted artifact", async () => {
728+
await page.waitForURL((url) => /\/artifacts\/?$/.test(url.pathname), {
729+
timeout: 20_000,
730+
});
731+
await page.getByRole("heading", { name: "Saved artifacts" }).waitFor({ timeout: 20_000 });
732+
await listRefreshStarted;
733+
734+
const deletedCardCount = await page
678735
.getByRole("link", { name: `Open artifact ${renamedTitle}` })
679-
.waitFor({ state: "detached", timeout: 20_000 });
736+
.count();
737+
releaseListRefresh();
738+
await page.unrouteAll({ behavior: "wait" });
739+
740+
expect(
741+
deletedCardCount,
742+
"the optimistic delete survives navigation while the list refresh is pending",
743+
).toBe(0);
680744
});
681745
});
682746

683747
const afterDelete = yield* session.call("list-artifacts", {});
684748
expect(afterDelete.text, "the agent no longer offers the deleted artifact").not.toContain(
685749
renamedTitle,
686750
);
751+
expect(afterDelete.text, "nor the artifact deleted from its gallery card").not.toContain(
752+
listDeleteTitle,
753+
);
687754

688755
const missing = yield* session.call("show-artifact", { id: artifactId });
689756
expect(missing.ok, "fetching a deleted artifact is an error, not an empty render").toBe(
690757
false,
691758
);
692759
}).pipe(
760+
Effect.ensuring(
761+
Effect.suspend(() =>
762+
listArtifactId === undefined
763+
? Effect.void
764+
: client.artifacts.remove({ params: { artifactId: listArtifactId } }),
765+
).pipe(Effect.ignore),
766+
),
693767
Effect.ensuring(
694768
Effect.suspend(() =>
695769
artifactId === undefined

0 commit comments

Comments
 (0)