import { expect, test, type Locator, type Page } from "@playwright/test"; const content = (page: Page, id: string) => page.locator(`#${id}`).locator(".cm-content"); const lineWith = (editor: Locator, text: string) => editor.locator(".cm-line", { hasText: text }); test.beforeEach(async ({ page }) => { await page.goto("/docs.html"); await expect(content(page, "ada")).toContainText("Field notes"); await expect(content(page, "grace")).toContainText("Field notes"); }); test("both editors show the seed once", async ({ page }) => { const text = await page.evaluate(() => (globalThis as any).agent.text as string); expect(text.match(/# Field notes/g)).toHaveLength(1); }); test("typing in one editor appears in the other", async ({ page }) => { await lineWith(content(page, "ada"), "Type in both editors").click(); await page.keyboard.press("End"); await page.keyboard.type(" from Ada"); await expect(content(page, "grace")).toContainText("Type in both editors at once from Ada"); }); test("an agent write against the version read keeps typing made since", async ({ page }) => { await page.click("#agent-read"); await lineWith(content(page, "grace"), "Type in both editors").click(); await page.keyboard.press("End"); await page.keyboard.type(" (typed after the read)"); await page.click("#agent-write"); for (const id of ["ada", "grace"]) { await expect(content(page, id)).toContainText("Agent summary"); await expect(content(page, id)).toContainText("(typed after the read)"); await expect(content(page, id)).toContainText("Fix the typo"); } }); test("a naive agent write drops typing made since the read", async ({ page }) => { await page.click("#agent-read"); await lineWith(content(page, "grace"), "Type in both editors").click(); await page.keyboard.press("End"); await page.keyboard.type(" (typed after the read)"); await page.check("#agent-naive"); await page.click("#agent-write"); await expect(content(page, "ada")).toContainText("Agent summary"); await expect(content(page, "ada")).not.toContainText("(typed after the read)"); }); test("syntax marks are hidden off the cursor line and shown on it", async ({ page }) => { const ada = content(page, "ada"); const line = lineWith(ada, "edits the same"); await expect(line).not.toContainText("**"); await expect(line.locator(".cm-nib-strong")).toHaveText("Loro"); await line.click(); await expect(line).toContainText("**Loro**"); }); test("a remote cursor shows the other person's name", async ({ page }) => { await lineWith(content(page, "grace"), "Type in both editors").click(); await expect(page.locator("#ada").locator(".loro-cursor.nib-user-1")).toHaveCount(1); }); test("frontmatter is shown as text, not as a rule or heading", async ({ page }) => { await lineWith(content(page, "grace"), "Type in both editors").click(); const ada = content(page, "ada"); await page.locator("#ada .cm-line", { hasText: "Type in both" }).click(); await lineWith(content(page, "grace"), "Things to try").click(); await expect(ada.locator(".cm-nib-frontmatter")).toHaveCount(3); await expect(ada.locator(".cm-nib-frontmatter .cm-nib-hr, .cm-nib-frontmatter.cm-nib-h2")).toHaveCount(0); await expect(ada.locator(".cm-line").first()).toHaveText("---"); }); test("toggling a checkbox edits the source for everyone", async ({ page }) => { const task = (id: string) => lineWith(content(page, id), "Let the agent read"); await task("ada").locator("input.cm-nib-checkbox").click(); await expect(task("ada").locator("input.cm-nib-checkbox")).toBeChecked(); await expect(task("grace").locator("input.cm-nib-checkbox")).toBeChecked(); expect(await page.evaluate(() => (globalThis as any).agent.text as string)).toContain("- [x] Let the agent read"); }); test("mod-click opens a link, a plain click places the cursor", async ({ page }) => { await page.evaluate(() => { const opened: unknown[][] = ((globalThis as any).opened = []); window.open = (...args: unknown[]) => (opened.push(args), null); }); // The image above the link changes the layout when it loads. await expect.poll(() => content(page, "ada").locator("img.cm-nib-img").evaluate((el: HTMLImageElement) => el.complete)).toBe(true); const link = lineWith(content(page, "ada"), "for why Loro").locator(".cm-nib-link"); await expect(link).toHaveText("D16"); await link.click({ modifiers: ["ControlOrMeta"] }); expect(await page.evaluate(() => (globalThis as any).opened)).toEqual([ ["https://tangled.org/did:plc:qgte7qw4zeutobb5h7joxoyt/core", "_blank", "noopener,noreferrer"], ]); await link.click(); await expect(lineWith(content(page, "ada"), "for why Loro")).toContainText("[D16](https://"); }); test("images render off the cursor line", async ({ page }) => { const img = content(page, "ada").locator("img.cm-nib-img"); await expect(img).toHaveAttribute("alt", "A gradient swatch"); await expect.poll(() => img.evaluate((el: HTMLImageElement) => el.naturalWidth)).toBe(480); }); test("task items show a checkbox instead of a bullet", async ({ page }) => { const task = lineWith(content(page, "ada"), "Let the agent read"); await expect(task.locator("input.cm-nib-checkbox")).toHaveCount(1); await expect(task.locator(".cm-nib-bullet")).toHaveCount(0); await expect(lineWith(content(page, "ada"), "Move the cursor").locator(".cm-nib-bullet")).toHaveCount(1); });