import { expect, test } from "@playwright/test";
import { fileURLToPath } from "node:url";
import { readdir, readFile } from "node:fs/promises";
import { terminalOriginBinding } from "./question-terminal-origin.fixture";
import type { Page } from "@playwright/test";
let script: string;
let stylesheet: string;
test.beforeAll(async () => {
if (typeof Bun === "undefined") throw new Error("Run Playwright through bun --bun");
let entry = fileURLToPath(
new URL("../../packages/editor/src/widgets/questionnaire.tsx", import.meta.url),
);
let result = await Bun.build({
entrypoints: [entry],
target: "browser",
format: "iife",
plugins: [{
name: "actual-question-terminal-origin-binding",
setup(build) {
build.onLoad({ filter: /\/widgets\/questionnaire\.tsx$/ }, async args => ({
loader: "tsx",
contents: await Bun.file(args.path).text() + "\n" + terminalOriginBinding,
}));
},
}],
});
expect(result.success, JSON.stringify(result.logs)).toBe(true);
let outputs = result.outputs.filter(output => output.path.endsWith(".js"));
expect(outputs).toHaveLength(1);
script = await outputs[0]!.text();
let assets = fileURLToPath(new URL("../../apps/web/dist/assets/", import.meta.url));
let files = await readdir(assets);
let names = files.filter(name => /^index-.*\.css$/.test(name));
let editor = files.filter(name => /^plan-editor-.*\.css$/.test(name));
expect(names).toHaveLength(1);
expect(editor).toHaveLength(1);
stylesheet = (await Promise.all([names[0]!, editor[0]!].map(name =>
readFile(assets + name, "utf8")
))).join("\n");
expect(stylesheet).toContain(".question-choice-row");
expect(stylesheet).toContain(".question-input");
});
async function load(
page: Page,
mode: "suggestion" | "legacy-custom" | "discarded" | "cancelled" | "gallery",
) {
let errors: string[] = [];
page.on("pageerror", error => errors.push(error.message));
let url = "https://question-terminal-origin.invalid/";
await page.route(
"**/*",
route =>
route.request().url() === url && route.request().isNavigationRequest()
? route.fulfill({
contentType: "text/html",
body:
'
',
})
: route.abort(),
);
await page.goto(url);
expect(await page.evaluate(() => ({ mode: document.compatMode, secure: window.isSecureContext })))
.toEqual({ mode: "CSS1Compat", secure: true });
await page.addStyleTag({ content: stylesheet });
await page.addScriptTag({ content: script });
await page.evaluate(mode => window.terminalOriginFixture.mount(mode), mode);
return errors;
}
test("a real human edit removes from chat until the suggestion lifecycle is cleared", async ({ page }) => {
let errors = await load(page, "suggestion");
let badge = page.getByText("from chat", { exact: true });
await expect(badge).toBeVisible();
await expect(page.getByRole("radio", { name: "GitHub Apps from chat", exact: true }))
.toBeChecked();
await expect(page.getByRole("radio", { name: /GitHub Apps/ })).toBeChecked();
expect((await page.evaluate(() => window.terminalOriginFixture.snapshot())).choice).toBeNull();
await page.getByRole("radio", { name: "Auth0", exact: true }).check();
await expect(badge).toHaveCount(0);
expect((await page.evaluate(() => window.terminalOriginFixture.snapshot())).choice).toBe("a");
await page.evaluate(() => window.terminalOriginFixture.empty());
await expect(page.getByRole("radio", { name: "Auth0", exact: true })).not.toBeChecked();
await expect(badge).toHaveCount(0);
await page.evaluate(() => window.terminalOriginFixture.suggestion(false));
await expect(page.getByRole("radio", { name: "GitHub Apps", exact: true })).not.toBeChecked();
await page.evaluate(() => window.terminalOriginFixture.suggestion(true));
await expect(badge).toBeVisible();
await expect(page.getByRole("radio", { name: /GitHub Apps/ })).toBeChecked();
expect((await page.evaluate(() => window.terminalOriginFixture.snapshot())).choice).toBeNull();
let save = page.getByRole("button", { name: "Save", exact: true });
await expect(save).toBeEnabled();
await page.getByRole("button", { name: "Add an option", exact: true }).click();
let field = page.getByRole("textbox", { name: "New option", exact: true });
await expect(field).toBeFocused();
await field.fill("Another approach");
await expect(badge).toHaveCount(0);
await expect(save).toBeDisabled();
await field.press("Enter");
await expect(page.getByRole("status").filter({ hasText: "Adding option" }))
.toBeVisible();
expect(await page.evaluate(() => window.terminalOriginFixture.additions())).toEqual([
{ question: "q", label: "Another approach" },
]);
await page.evaluate(() => window.terminalOriginFixture.publishOption());
let option = page.getByRole("radio", { name: "Another approach", exact: true });
await expect(option).toBeVisible();
await expect(option).not.toBeChecked();
await expect(field).toBeFocused();
await expect(field).toHaveJSProperty("readOnly", true);
await expect(page.getByRole("status").filter({ hasText: "Adding option" })).toBeVisible();
await expect(badge).toHaveCount(0);
await expect(save).toBeDisabled();
expect(await page.evaluate(() => window.terminalOriginFixture.snapshot())).toMatchObject({
mode: "choices",
choice: null,
custom: "",
});
await page.evaluate(() => window.terminalOriginFixture.acknowledgeOption());
await expect(page.getByText("Adding option", { exact: true })).toHaveCount(0);
await expect(field).toHaveCount(0);
await expect(page.getByRole("button", { name: "Add an option", exact: true })).toBeVisible();
await expect(badge).toHaveCount(0);
await expect(save).toBeDisabled();
await option.check();
await expect(badge).toHaveCount(0);
await expect(save).toBeEnabled();
expect(await page.evaluate(() => window.terminalOriginFixture.snapshot())).toMatchObject({
mode: "choices",
choice: "native-added",
custom: "",
});
expect(errors).toEqual([]);
});
test("the actual metadata-driven card shows Discarded rather than a stale answer or Cancelled", async ({ page }) => {
let errors = await load(page, "discarded");
await expect(
page.getByText("Discarded by @bea — What auth system should we use?", { exact: true }),
).toBeVisible();
await expect(page.getByText(/What auth system should we use\?/)).toBeVisible();
await expect(page.getByText(/Cancelled|Answered by|GitHub Apps/)).toHaveCount(0);
await expect(page.getByRole("radio")).toHaveCount(0);
await expect(page.getByRole("button", { name: "Reopen", exact: true })).toHaveCount(0);
await expect(page.getByRole("button", { name: "Discard", exact: true })).toHaveCount(0);
expect(errors).toEqual([]);
});
test("genuine cancellation remains separate in the styled real component gallery", async ({ page }) => {
let errors = await load(page, "gallery");
await expect(page.getByText("Cancelled by @ana", { exact: true })).toBeVisible();
await expect(
page.getByText("Discarded by @bea — What auth system should we use?", { exact: true }),
).toBeVisible();
let badge = page.getByText("from chat", { exact: true });
await expect(badge).toBeVisible();
expect(
await badge.evaluate(element => {
let label = element.previousElementSibling;
if (!label) throw new Error("Suggestion badge has no option label");
let badgeStyle = getComputedStyle(element);
let labelStyle = getComputedStyle(label);
return {
sameTextRole: badgeStyle.fontSize === labelStyle.fontSize,
muted: badgeStyle.color !== labelStyle.color,
};
}),
).toEqual({ sameTextRole: true, muted: true });
await page.screenshot({ path: "/tmp/jev-question-terminal-origin.png", fullPage: true });
expect(errors).toEqual([]);
});
test("a persisted legacy custom answer stays readable until a human chooses an option", async ({ page }) => {
let errors = await load(page, "legacy-custom");
let legacy = page.getByRole("radio", { name: "Another approach", exact: true });
let save = page.getByRole("button", { name: "Save", exact: true });
await expect(legacy).toBeChecked();
await expect(page.getByText("from chat", { exact: true })).toHaveCount(0);
await expect(page.getByRole("radio", { name: "Write a custom answer", exact: true }))
.toHaveCount(0);
await expect(page.getByRole("textbox")).toHaveCount(0);
await expect(save).toBeEnabled();
expect(await page.evaluate(() => window.terminalOriginFixture.snapshot())).toMatchObject({
mode: "custom",
choice: null,
custom: "Another approach",
});
await page.getByRole("radio", { name: "Auth0", exact: true }).check();
await expect(legacy).toHaveCount(0);
await expect(page.getByRole("radio", { name: "Auth0", exact: true })).toBeChecked();
await expect(save).toBeEnabled();
expect(await page.evaluate(() => window.terminalOriginFixture.snapshot())).toMatchObject({
mode: "choices",
choice: "a",
});
expect(errors).toEqual([]);
});