opencode/packages/app/component-tests/composer.spec.ts

86 lines
3.9 KiB
TypeScript

import { expect, story } from "../../storybook/playwright/story"
for (const draft of ["empty-draft", "multiline-draft", "mixed-attachments"]) {
story(`select all stays inside the composer with ${draft}`, async ({ mount, page }) => {
const component = await mount(`opencode-composer-flow--${draft}`)
const input = component.getByRole("textbox", { name: "Prompt", exact: true })
const text = await input.textContent()
for (let count = 0; count < 2; count++) {
await input.press("ControlOrMeta+a")
expect(
await input.evaluate((editor) => {
const selection = window.getSelection()
return {
text: selection?.toString(),
inside: editor.contains(selection?.anchorNode ?? null) && editor.contains(selection?.focusNode ?? null),
}
}),
).toEqual({ text, inside: true })
}
await page.keyboard.type("Replacement draft")
await expect(input).toHaveText("Replacement draft")
await expect(component.getByRole("status")).toHaveText("Ready")
if (draft === "mixed-attachments") {
await expect(component.getByAltText("layout.png")).toBeVisible()
await expect(component.getByText("Keep the normal flow flat", { exact: true })).toBeVisible()
}
})
}
story("renders a draft once and supports editing, caret restoration, and failure recovery", async ({ mount, page }) => {
await page.addInitScript(() => {
const replace = Element.prototype.replaceChildren
Element.prototype.replaceChildren = function (this: Element, ...nodes) {
// The ref can run before data-component is assigned, so count on every target.
this.setAttribute("data-test-replacements", String(Number(this.getAttribute("data-test-replacements")) + 1))
return replace.apply(this, nodes)
}
})
const component = await mount("opencode-composer-flow--failed-submission-restoration")
const input = component.getByRole("textbox", { name: "Prompt", exact: true })
await expect(input).toHaveText("Preserve this draft on failure")
await expect(input).toHaveAttribute("data-test-replacements", "1")
await input.press("Home")
await input.press("Shift+ArrowRight")
await input.pressSequentially("XY")
await expect(input).toHaveText("XYreserve this draft on failure")
await expect(input).toHaveAttribute("data-test-replacements", "1")
// Closing the model picker restores the controller's saved caret through its editor ref.
await component.locator('[data-action="composer-model"]').click()
await page.getByRole("menu").getByRole("textbox").press("Escape")
await expect(input).toBeFocused()
await input.pressSequentially("!")
await expect(input).toHaveText("XY!reserve this draft on failure")
await component.getByRole("button", { name: "Send", exact: true }).click()
await expect(component.getByRole("status")).toHaveText("Submission failed; draft restored")
await expect(input).toHaveText("Preserve this draft on failure")
})
// Moved from packages/app/e2e/regression/prompt-thinking-level.spec.ts
story("shows the thinking level control while relevant", async ({ mount, page }) => {
const component = await mount("opencode-composer-flow--model-and-variant")
const composer = component.locator('[data-component="composer"]')
const input = composer.locator('[data-component="composer-editor"]')
const control = composer.getByRole("button", { name: "Choose model variant" })
await page.mouse.move(0, 0)
await page.evaluate(() => (document.activeElement as HTMLElement | null)?.blur())
await expect(control).toBeVisible()
await control.click()
const high = page.getByRole("menuitemradio", { name: "high" })
await expect(high).toBeVisible()
await page.mouse.move(0, 0)
await expect(control).toBeVisible()
await expect(high).toBeVisible()
await high.click()
await input.focus()
await expect(control).toBeVisible()
await input.blur()
await expect(control).toBeVisible()
})