mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-14 20:21:39 +03:00
206 lines
8.8 KiB
TypeScript
206 lines
8.8 KiB
TypeScript
import { expect, test } from "@playwright/test"
|
|
import { fixture, mockStressTimeline } from "../utils/session-fixture"
|
|
import { sessionHref } from "../utils/app"
|
|
import { openWithDirection } from "../utils/direction"
|
|
|
|
for (const scheme of ["light", "dark"] as const) {
|
|
for (const direction of ["ltr", "rtl"] as const) {
|
|
test(`mobile plugins reuse the session drawer in ${scheme} ${direction}`, async ({ page }, testInfo) => {
|
|
await page.setViewportSize({ width: 390, height: 844 })
|
|
await mockStressTimeline(page)
|
|
await page.addInitScript((scheme) => {
|
|
localStorage.setItem("opencode-color-scheme", scheme)
|
|
localStorage.setItem("settings.v3", JSON.stringify({ general: { mobileTitlebarPosition: "bottom" } }))
|
|
}, scheme)
|
|
const warnings: string[] = []
|
|
page.on("console", (event) => {
|
|
if (event.text().includes("computations created outside")) warnings.push(event.text())
|
|
})
|
|
await openWithDirection(page, sessionHref(fixture.targetID), direction)
|
|
const tabs = page.getByRole("tablist", { name: "Session view", exact: true })
|
|
const more = tabs.getByRole("tab", { name: "More...", exact: true })
|
|
await expect(tabs.getByRole("tab", { selected: true })).toHaveText("Session")
|
|
await more.click()
|
|
await page
|
|
.getByRole("dialog", { name: "More options", exact: true })
|
|
.getByRole("button", { name: "Session details", exact: true })
|
|
.click()
|
|
const details = page.getByRole("dialog", { name: "Session details", exact: true })
|
|
await expect(details.getByRole("button", { name: "MCP", exact: true })).toBeVisible()
|
|
await details.evaluate((element) => element.setAttribute("data-drawer-probe", "single"))
|
|
|
|
for (const [name, empty] of [
|
|
["MCP", "No MCP servers configured"],
|
|
["Plugins", "No plugins configured"],
|
|
["Skills", "No skills configured"],
|
|
["LSP", "No LSP servers configured"],
|
|
]) {
|
|
const trigger = details.getByRole("button", { name, exact: true })
|
|
await trigger.click()
|
|
const plugin = page.getByRole("dialog", { name, exact: true })
|
|
await expect(plugin.getByText(empty, { exact: true })).toBeVisible()
|
|
await expect(plugin).toHaveAttribute("data-drawer-probe", "single")
|
|
await expect(page.getByRole("dialog")).toHaveCount(1)
|
|
await expect(page.locator('[data-slot="mobile-drawer-overlay"]')).toHaveCount(1)
|
|
await expect(plugin.getByRole("button", { name: "Close", exact: true })).toHaveCount(0)
|
|
await expect(plugin.locator(".session-service-flyout")).toHaveCount(0)
|
|
await expect(plugin).toBeInViewport({ ratio: 1 })
|
|
await plugin.getByRole("button", { name: "Navigate back", exact: true }).click()
|
|
await expect(details.getByRole("button", { name: "MCP", exact: true })).toBeVisible()
|
|
await expect(trigger).toBeFocused()
|
|
}
|
|
|
|
await details.getByRole("button", { name: "Skills", exact: true }).click()
|
|
await expect(
|
|
page.getByRole("dialog", { name: "Skills", exact: true }).getByText("No skills configured", { exact: true }),
|
|
).toBeVisible()
|
|
await page.screenshot({ path: testInfo.outputPath("plugins.png") })
|
|
await testInfo.attach("plugins", { path: testInfo.outputPath("plugins.png"), contentType: "image/png" })
|
|
await page.keyboard.press("Escape")
|
|
await expect(page.getByRole("dialog")).toHaveCount(0)
|
|
await expect(more).toBeFocused()
|
|
await more.click()
|
|
await expect(
|
|
page
|
|
.getByRole("dialog", { name: "More options", exact: true })
|
|
.getByRole("button", { name: "Files", exact: true }),
|
|
).toBeVisible()
|
|
expect(warnings).toEqual([])
|
|
})
|
|
}
|
|
}
|
|
|
|
test("mobile plugin drawers keep configured lists, retry, refresh, and MCP status working", async ({ page }) => {
|
|
await page.setViewportSize({ width: 390, height: 844 })
|
|
await mockStressTimeline(page)
|
|
const state = { fail: true, extra: false, enabled: true }
|
|
await page.route(
|
|
(url) => url.pathname === "/api/plugin",
|
|
(route) => {
|
|
if (route.request().method() === "OPTIONS") return route.fallback()
|
|
|
|
if (state.fail) return route.fulfill({ status: 500, json: { message: "Unavailable" } })
|
|
|
|
return route.fulfill({
|
|
json: {
|
|
location: { directory: fixture.directory },
|
|
data: [
|
|
{
|
|
id: "drawer-plugin",
|
|
source: { type: "package", target: "drawer-plugin" },
|
|
features: {},
|
|
state: { status: "active" },
|
|
},
|
|
...(state.extra
|
|
? [
|
|
{
|
|
id: "extra-plugin",
|
|
source: { type: "package", target: "extra-plugin" },
|
|
features: {},
|
|
state: { status: "active" },
|
|
},
|
|
]
|
|
: []),
|
|
],
|
|
},
|
|
})
|
|
},
|
|
)
|
|
await page.route(
|
|
(url) => url.pathname === "/api/skill",
|
|
(route) =>
|
|
route.fulfill({
|
|
json: {
|
|
location: { directory: fixture.directory },
|
|
data: [
|
|
{ id: "drawer-skill", name: "drawer-skill", path: "/skills/drawer/SKILL.md", content: "Drawer skill" },
|
|
],
|
|
},
|
|
}),
|
|
)
|
|
await page.route(
|
|
(url) => url.pathname === "/api/config",
|
|
(route) =>
|
|
route.fulfill({ json: [{ type: "document", info: { lsp: { "drawer-lsp": { command: ["drawer-lsp"] } } } }] }),
|
|
)
|
|
await page.route(
|
|
(url) => url.pathname.startsWith("/api/mcp") || url.pathname.startsWith("/api/experimental/mcp"),
|
|
(route) => {
|
|
if (route.request().method() === "OPTIONS") return route.fallback()
|
|
const path = new URL(route.request().url()).pathname
|
|
|
|
if (path.endsWith("/disconnect")) {
|
|
state.enabled = false
|
|
|
|
return route.fulfill({ status: 204 })
|
|
}
|
|
|
|
return route.fulfill({
|
|
json: {
|
|
location: { directory: fixture.directory },
|
|
data:
|
|
path === "/api/mcp/resource"
|
|
? { resources: [], templates: [] }
|
|
: Array.from({ length: 30 }, (_, index) => ({
|
|
name: `mcp-${String(index).padStart(2, "0")}`,
|
|
status: { status: index === 0 && !state.enabled ? "disabled" : "connected" },
|
|
})),
|
|
},
|
|
})
|
|
},
|
|
)
|
|
await openWithDirection(page, sessionHref(fixture.targetID), "ltr")
|
|
const tabs = page.getByRole("tablist", { name: "Session view", exact: true })
|
|
await expect(tabs.getByRole("tab", { selected: true })).toHaveText("Session")
|
|
await tabs.getByRole("tab", { name: "More...", exact: true }).click()
|
|
await page
|
|
.getByRole("dialog", { name: "More options", exact: true })
|
|
.getByRole("button", { name: "Session details", exact: true })
|
|
.click()
|
|
const details = page.getByRole("dialog", { name: "Session details", exact: true })
|
|
await details.getByRole("button", { name: "Plugins", exact: true }).click()
|
|
const plugins = page.getByRole("dialog", { name: "Plugins", exact: true })
|
|
await expect(plugins.getByRole("alert")).toContainText("Request failed")
|
|
state.fail = false
|
|
await plugins.getByRole("button", { name: "Retry", exact: true }).click()
|
|
await expect(plugins.getByText("drawer-plugin", { exact: true })).toBeVisible()
|
|
await plugins.getByRole("button", { name: "Navigate back", exact: true }).click()
|
|
state.extra = true
|
|
await details.getByRole("button", { name: "Plugins", exact: true }).click()
|
|
await expect(plugins.getByText("extra-plugin", { exact: true })).toBeVisible()
|
|
await plugins.getByRole("button", { name: "Navigate back", exact: true }).click()
|
|
|
|
for (const [name, item] of [
|
|
["Skills", "drawer-skill"],
|
|
["LSP", "drawer-lsp"],
|
|
]) {
|
|
await details.getByRole("button", { name, exact: true }).click()
|
|
const plugin = page.getByRole("dialog", { name, exact: true })
|
|
await expect(plugin.getByText(item, { exact: true })).toBeVisible()
|
|
await plugin.getByRole("button", { name: "Navigate back", exact: true }).click()
|
|
}
|
|
|
|
await details.getByRole("button", { name: "MCP", exact: true }).click()
|
|
const mcp = page.getByRole("dialog", { name: "MCP", exact: true })
|
|
await expect(mcp.locator(".session-mcp-row")).toHaveCount(30)
|
|
await expect(mcp.getByRole("switch")).toHaveCount(30)
|
|
const toggle = mcp.getByRole("switch", { name: "mcp-00", exact: true })
|
|
await expect(toggle).toBeChecked()
|
|
await toggle.press("Space")
|
|
await expect(toggle).not.toBeChecked()
|
|
await expect(toggle).toBeEnabled()
|
|
await expect
|
|
.poll(() =>
|
|
mcp
|
|
.locator('[data-slot="mobile-panel-content"]')
|
|
.evaluate((element) => element.scrollHeight > element.clientHeight),
|
|
)
|
|
.toBe(true)
|
|
await expect(mcp).toBeInViewport({ ratio: 1 })
|
|
|
|
const last = mcp.locator(".session-mcp-row").filter({ hasText: "mcp-29" })
|
|
await last.scrollIntoViewIfNeeded()
|
|
await expect(last).toBeInViewport()
|
|
await expect(mcp.getByRole("button", { name: "Manage MCP servers", exact: true })).toBeVisible()
|
|
})
|