|
| 1 | +import { test, expect, type ElectronApplication, type Page } from "@playwright/test"; |
| 2 | +import fs from "node:fs"; |
| 3 | +import path from "node:path"; |
| 4 | +import { currentRow, items, launch, seedHome, tid } from "./support"; |
| 5 | + |
| 6 | +/** |
| 7 | + * A task's plan and record (docs/task-context.md): the plan the CLI reported, in its statuses, and an |
| 8 | + * explicit Save to Notes that writes the turn's record into the project's notes and updates it when |
| 9 | + * saved again. Plan items are sent to the window as main would; the saved turn ran on the mock engine. |
| 10 | + */ |
| 11 | +let app: ElectronApplication; |
| 12 | +let page: Page; |
| 13 | +let home: string; |
| 14 | +let repo: string; |
| 15 | +let threadId: string; |
| 16 | + |
| 17 | +const at = () => new Date().toISOString(); |
| 18 | + |
| 19 | +test.beforeAll(async () => { |
| 20 | + ({ home, repo } = seedHome()); |
| 21 | + ({ app, page } = await launch(home)); |
| 22 | + const script = path.join(home, "task-script.json"); |
| 23 | + fs.writeFileSync(script, JSON.stringify([{ content: "Fixed the import." }])); |
| 24 | + await page.evaluate((mock_script) => window.modex!.invoke("settings:update", { mock_script }), script); |
| 25 | + await page.keyboard.press("Meta+n"); |
| 26 | + await expect(tid(page, "composer-input")).toBeFocused(); |
| 27 | + await tid(page, "composer-input").fill("Fix the failing import"); |
| 28 | + await page.keyboard.press("Meta+Enter"); |
| 29 | + await expect(tid(page, "thread-view")).toBeVisible({ timeout: 15_000 }); |
| 30 | + await expect(currentRow(page)).toHaveAttribute("data-status", "idle"); |
| 31 | + threadId = (await currentRow(page).getAttribute("data-thread-id"))!; |
| 32 | +}); |
| 33 | + |
| 34 | +test.afterAll(async () => { |
| 35 | + await app?.close(); |
| 36 | + fs.rmSync(home, { recursive: true, force: true }); |
| 37 | + fs.rmSync(repo, { recursive: true, force: true }); |
| 38 | +}); |
| 39 | + |
| 40 | +test("a plan shows each step as the CLI reported it, never as verified", async () => { |
| 41 | + const origin = { thread: threadId, turn: "u-plan", attempt: "P1", backend: "codex", native: { runtime: "R", item: "plan" } }; |
| 42 | + await app.evaluate(({ BrowserWindow }, events) => { |
| 43 | + for (const event of events) BrowserWindow.getAllWindows()[0]!.webContents.send("thread:event", event); |
| 44 | + }, [ |
| 45 | + { type: "item", threadId, item: { id: "u-plan", kind: "user", text: "plan turn", at: at() } }, |
| 46 | + { type: "item", threadId, item: { id: "p", kind: "plan", from: "codex-plan", revision: 2, explanation: "Small fix", outcome: "succeeded", origin, at: at(), |
| 47 | + steps: [{ text: "Read the config", status: "completed" }, { text: "Fix the import", status: "in-progress" }, { text: "Run the tests", status: "pending" }, { text: "Ship", status: "unknown" }] } }, |
| 48 | + ]); |
| 49 | + const plan = items(page, "plan"); |
| 50 | + await expect(tid(plan, "plan-meta")).toHaveText("From Codex's plan · revision 2"); |
| 51 | + await expect(tid(plan, "plan-step")).toHaveText(["Reported done Read the config", "In progress Fix the import", "Not started Run the tests", "Status unknown Ship"]); |
| 52 | + await expect(plan).toContainText("Small fix"); |
| 53 | + await expect(plan).not.toContainText(/verified/i); |
| 54 | +}); |
| 55 | + |
| 56 | +test("Save to Notes writes the turn's record into the project's notes, and saving again updates the same note", async () => { |
| 57 | + const save = tid(page, "save-task-note").first(); |
| 58 | + await save.click(); |
| 59 | + await expect(tid(page, "task-note-saved").first()).toContainText("Saved to Notes · Task:"); |
| 60 | + const pages = async () => (await page.evaluate(() => window.modex!.invoke("space:list", undefined))) as { id: string; title: string; markdown: string }[]; |
| 61 | + const saved = (await pages()).filter((p) => p.title.startsWith("Task:")); |
| 62 | + expect(saved).toHaveLength(1); |
| 63 | + expect(saved[0]!.markdown).toContain("> Fix the failing import"); |
| 64 | + expect(saved[0]!.markdown).toContain("Fixed the import."); |
| 65 | + expect(saved[0]!.markdown).toContain(`From modex thread ${threadId}.`); |
| 66 | + expect(saved[0]!.markdown).not.toContain(repo); |
| 67 | + await expect(save).toHaveText("Update note"); |
| 68 | + await save.click(); |
| 69 | + await expect(tid(page, "task-note-saved").first()).toContainText("Updated in Notes · Task:"); |
| 70 | + expect((await pages()).filter((p) => p.title.startsWith("Task:"))).toHaveLength(1); |
| 71 | +}); |
0 commit comments