repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/strata/__e2e__/continuity.spec.ts
Rawimport { expect, test } from "@playwright/test";
import type { Draft, DraftRecovery, Review, ReviewServer } from "../types.js";
const { startReviewServer }: typeof import("../server.ts") = await import(
new URL("../../../dist/strata/server.ts", import.meta.url).href
);
function review(id = "continuity"): Review {
return {
snapshot: {
id,
repoRoot: "/safe/continuity",
source: { kind: "working" },
base: "base",
head: "head",
hunks: [
{
id: "change",
path: "src/change.ts",
header: "@@ -1,2 +1,2 @@ change",
lines: [
{ kind: "add", text: "new", newLine: 1 },
{ kind: "context", text: "same", oldLine: 2, newLine: 2 },
],
},
],
skipped: [],
},
plan: {
summary: "Continuity",
cohorts: [
{
title: "Change",
layers: [
{
id: "layer",
title: "Change",
summary: "Explain the change",
hunks: [{ id: "change", summary: "Two lines" }],
},
],
},
],
},
draft: { reviewed: [], findings: [], notes: "" },
};
}
let server: ReviewServer | undefined;
test.afterEach(async () => {
await server?.close();
server = undefined;
});
async function state(page: import("@playwright/test").Page) {
return page.evaluate(async () =>
(await fetch("/api/state")).json(),
) as Promise<{ review: Review }>;
}
const options = {
isCurrent: async () => true,
ask: async () => "The changed line introduces the new behavior.",
submit: async () => {},
};
test("reload never overwrites another tab with an acknowledged but older cache", async ({
page,
}) => {
server = await startReviewServer({
...options,
review: review(),
refresh: async () => review(),
save: () => {},
});
await page.goto(server.url);
await page.getByRole("tab", { name: "Feedback", exact: true }).click();
await page.locator("#notes").fill("Older tab note");
await expect(page.locator("#status")).toHaveText("Draft saved");
const other = await page.context().newPage();
await other.goto(server.url);
await other.getByRole("tab", { name: "Feedback", exact: true }).click();
await other.locator("#notes").fill("Authoritative newer tab note");
await expect(other.locator("#status")).toHaveText("Draft saved");
let writes = 0;
page.on("request", (request) => {
if (request.url().endsWith("/api/draft")) writes++;
});
await page.reload();
await page.getByRole("tab", { name: "Feedback", exact: true }).click();
await page.locator("#local-recovery summary").click();
await expect(page.locator("#recovery-content")).toContainText(
"Older tab note",
);
await expect(page.locator("#notes")).toHaveValue(
"Authoritative newer tab note",
);
await page.waitForTimeout(650);
expect(writes).toBe(0);
expect((await state(page)).review.draft.notes).toBe(
"Authoritative newer tab note",
);
await other.close();
});
test("older save acknowledgement cannot erase still-debounced notes from recovery", async ({
page,
}) => {
server = await startReviewServer({
...options,
review: review(),
refresh: async () => review(),
save: () => {},
});
let releaseResponse!: () => void;
const responseHeld = new Promise<void>((resolve) => {
releaseResponse = resolve;
});
let responseReady = false;
await page.route("**/api/draft", async (route) => {
const response = await route.fetch();
responseReady = true;
await responseHeld;
await route.fulfill({ response });
});
await page.goto(server.url);
await page.getByRole("tab", { name: "Feedback", exact: true }).click();
await page.locator("#notes").fill("Acknowledged older note");
await expect.poll(() => responseReady).toBe(true);
await page.clock.install({ time: 0 });
await page.clock.pauseAt(1000);
await page.locator("#notes").fill("Still-debounced newer note");
releaseResponse();
await expect(page.locator("#status")).toHaveText("Draft saved");
const cached = await page.evaluate(() => {
const key = Object.keys(sessionStorage).find((key) =>
key.includes("draft"),
);
return JSON.parse(sessionStorage.getItem(key!)!) as Draft;
});
expect(cached.notes).toBe("Still-debounced newer note");
await page.reload();
await page.getByRole("tab", { name: "Feedback", exact: true }).click();
await page.locator("#local-recovery summary").click();
await expect(page.locator("#recovery-content")).toContainText(
"Still-debounced newer note",
);
expect((await state(page)).review.draft.notes).toBe(
"Acknowledged older note",
);
});
test("failed comment save keeps the range visible and Close preserves it", async ({
page,
}) => {
let recoveries: DraftRecovery[] = [];
server = await startReviewServer({
...options,
review: review(),
refresh: async () => review(),
save: () => {},
saveRecoveries: (saved) => {
recoveries = structuredClone(saved);
},
});
await page.route("**/api/draft", (route) =>
route.fulfill({
status: 500,
contentType: "application/json",
body: JSON.stringify({ error: "Comment save unavailable" }),
}),
);
await page.goto(server.url);
for (const line of [1, 2]) {
await page
.getByRole("button", {
name: `Comment on new line ${line} in src/change.ts`,
exact: true,
})
.click({ modifiers: ["Shift"] });
}
await page.locator(".keyboard-current").focus();
await page.keyboard.press("Space");
await page.keyboard.press("c");
await expect(page.locator("#comment-anchor")).toHaveText(
"src/change.ts · new:1–2",
);
await page.locator("#comment-text").fill("Keep the failed range comment");
await page.getByRole("button", { name: "Add comment", exact: true }).click();
await expect(page.locator("#status")).toContainText(
"Comment save unavailable",
);
await expect(page.locator("#comment-dialog")).not.toBeVisible();
await expect(page.locator("#findings .finding")).toHaveCount(1);
await expect(page.locator("#findings")).toContainText(
"Keep the failed range comment",
);
await page.locator("#notes").fill("Keep the pending note too");
page.once("dialog", (dialog) => dialog.accept());
await page.getByRole("button", { name: "Close", exact: true }).click();
await expect(page.locator("#status")).toContainText("Review closed");
expect(recoveries).toHaveLength(1);
expect(JSON.parse(recoveries[0]!.text)).toMatchObject({
notes: "Keep the pending note too",
findings: [
{
hunkId: "change",
side: "new",
line: 1,
endLine: 2,
text: "Keep the failed range comment",
},
],
});
});
test("failed autosave blocks destructive Refresh and Close persists recovery for another port", async ({
page,
}) => {
let refreshes = 0;
let savedRecoveries: DraftRecovery[] = [];
server = await startReviewServer({
...options,
review: review(),
refresh: async () => {
refreshes++;
return review("refreshed");
},
save: () => {},
saveRecoveries: (recoveries) => {
savedRecoveries = structuredClone(recoveries);
},
});
await page.route("**/api/draft", (route) =>
route.fulfill({
status: 500,
contentType: "application/json",
body: JSON.stringify({ error: "Autosave unavailable" }),
}),
);
await page.goto(server.url);
await page.getByRole("tab", { name: "Feedback", exact: true }).click();
await page.locator("#notes").fill("Keep the failed-save note");
await expect(page.locator("#status")).toContainText("Autosave unavailable");
page.once("dialog", (dialog) => dialog.accept());
await page.getByRole("button", { name: "Refresh", exact: true }).click();
await expect(page.locator("#status")).toContainText("Autosave unavailable");
await expect(page.locator("#notes")).toHaveValue("Keep the failed-save note");
expect(refreshes).toBe(0);
page.once("dialog", (dialog) => dialog.accept());
await page.getByRole("button", { name: "Close", exact: true }).click();
await expect(page.locator("#status")).toContainText("Review closed");
expect(savedRecoveries).toHaveLength(1);
expect(JSON.parse(savedRecoveries[0]!.text).notes).toBe(
"Keep the failed-save note",
);
await server.close();
await page.unroute("**/api/draft");
server = await startReviewServer({
...options,
review: { ...review(), recoveries: savedRecoveries },
refresh: async () => review(),
save: () => {},
saveRecoveries: () => {},
});
await page.goto(server.url);
await page.getByRole("tab", { name: "Feedback", exact: true }).click();
await page.locator("#local-recovery summary").click();
await expect(page.locator("#recovery-content")).toContainText(
"Keep the failed-save note",
);
page.once("dialog", (dialog) => dialog.accept());
await page
.getByRole("button", { name: "Merge local draft", exact: true })
.click();
await expect(page.locator("#status")).toHaveText("Local draft merged");
expect((await state(page)).review.draft.notes).toBe(
"Keep the failed-save note",
);
});
test("Close bypasses a stuck draft request and ignores its late rejection", async ({
page,
}) => {
let held: import("@playwright/test").Route | undefined;
let recoveries: DraftRecovery[] = [];
server = await startReviewServer({
...options,
review: review(),
refresh: async () => review(),
save: () => {},
saveRecoveries: (value) => {
recoveries = value;
},
});
await page.route("**/api/draft", (route) => {
held = route;
});
await page.goto(server.url);
await page.getByRole("tab", { name: "Feedback", exact: true }).click();
await page.locator("#notes").fill("Pending forever without this backup");
await expect.poll(() => Boolean(held)).toBe(true);
page.once("dialog", (dialog) => dialog.accept());
await page.getByRole("button", { name: "Close", exact: true }).click();
await expect(page.locator("#status")).toContainText("Review closed");
expect(JSON.parse(recoveries[0]!.text).notes).toBe(
"Pending forever without this backup",
);
await held!.abort();
await page.waitForTimeout(400);
await expect(page.locator("#status")).toHaveText(
"Review closed. You may close this tab.",
);
await expect(
page.getByRole("button", { name: "Close", exact: true }),
).toBeDisabled();
});
test("recovery merge cancels notes debounce rather than overwriting recovered notes", async ({
page,
}) => {
const recovered = { reviewed: [], findings: [], notes: "Recovered note" };
const saved: Draft[] = [];
server = await startReviewServer({
...options,
review: {
...review(),
recoveries: [
{
id: "recovery",
snapshotId: "continuity",
text: JSON.stringify(recovered),
},
],
},
refresh: async () => review(),
save: (draft) => {
saved.push(structuredClone(draft));
},
saveRecoveries: () => {},
});
await page.route("**/api/draft", async (route) => {
await new Promise((resolve) => setTimeout(resolve, 600));
await route.continue();
});
await page.goto(server.url);
await page.getByRole("tab", { name: "Feedback", exact: true }).click();
await page.locator("#local-recovery summary").click();
await page.locator("#notes").fill("Current local note");
page.once("dialog", (dialog) => dialog.accept());
await page
.getByRole("button", { name: "Merge local draft", exact: true })
.click();
await expect(page.locator("#status")).toHaveText("Local draft merged");
await page.waitForTimeout(650);
const draft = (await state(page)).review.draft;
expect(draft.notes).toContain("Current local note");
expect(draft.notes).toContain("Recovered note");
expect(saved).toHaveLength(1);
});
test("a changed snapshot never restores a comment through an accidentally reused hunk ID", async ({
page,
}) => {
const previous = review();
previous.draft.findings = [
{
id: "finding",
hunkId: "change",
side: "new",
line: 1,
endLine: 2,
severity: "major",
text: "Comment on the original block",
},
];
previous.draft.notes = "Keep the note separately";
const changed = review("changed");
changed.snapshot.hunks[0]!.lines[0]!.text =
"Different block at the old coordinates";
server = await startReviewServer({
...options,
review: previous,
refresh: async () => changed,
save: () => {},
saveRecoveries: () => {},
});
await page.goto(server.url);
page.once("dialog", (dialog) => dialog.accept());
await page.getByRole("button", { name: "Refresh", exact: true }).click();
await expect(page.locator("#source-label")).toContainText("changed");
await page.getByRole("tab", { name: "Feedback", exact: true }).click();
await page.locator("#local-recovery summary").click();
await expect(page.locator("#recovery-content")).toContainText(
"Comment on the original block",
);
page.once("dialog", (dialog) => dialog.accept());
await page
.getByRole("button", { name: "Merge local draft", exact: true })
.click();
await expect(page.locator("#status")).toContainText("remain in recovery");
const latest = await state(page);
expect(latest.review.draft.findings).toEqual([]);
expect(latest.review.draft.notes).toBe("Keep the note separately");
expect(latest.review.recoveries).toHaveLength(1);
});
test("unchanged Refresh retains Ask history", async ({ page }) => {
server = await startReviewServer({
...options,
review: review(),
refresh: async () => review(),
save: () => {},
});
await page.goto(server.url);
await page.getByRole("tab", { name: "Ask", exact: true }).click();
await page.locator("#question").fill("Why this change?");
await page.getByRole("button", { name: "Send", exact: true }).click();
await expect(page.locator("#status")).toHaveText("Answer received");
page.once("dialog", (dialog) => dialog.accept());
await page.getByRole("button", { name: "Refresh", exact: true }).click();
await expect(page.locator("#status")).toHaveText("Review refreshed");
await page.getByRole("tab", { name: "Ask", exact: true }).click();
await expect(page.locator("#ask-panel")).toContainText(
"The changed line introduces the new behavior.",
);
});