diff --git a/packages/app/e2e/app/home.spec.ts b/packages/app/e2e/app/home.spec.ts index f21dc40ec..a3cedf7cb 100644 --- a/packages/app/e2e/app/home.spec.ts +++ b/packages/app/e2e/app/home.spec.ts @@ -1,17 +1,17 @@ import { test, expect } from "../fixtures" -import { serverName } from "../utils" +import { serverNamePattern } from "../utils" test("home renders and shows core entrypoints", async ({ page }) => { await page.goto("/") await expect(page.getByRole("button", { name: "Open project" }).first()).toBeVisible() - await expect(page.getByRole("button", { name: serverName })).toBeVisible() + await expect(page.getByRole("button", { name: serverNamePattern })).toBeVisible() }) test("server picker dialog opens from home", async ({ page }) => { await page.goto("/") - const trigger = page.getByRole("button", { name: serverName }) + const trigger = page.getByRole("button", { name: serverNamePattern }) await expect(trigger).toBeVisible() await trigger.click() diff --git a/packages/app/e2e/app/server-default.spec.ts b/packages/app/e2e/app/server-default.spec.ts index adbc83473..2c63130f6 100644 --- a/packages/app/e2e/app/server-default.spec.ts +++ b/packages/app/e2e/app/server-default.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from "../fixtures" -import { serverName, serverUrl } from "../utils" -import { clickListItem, closeDialog, clickMenuItem } from "../actions" +import { serverNamePattern, serverUrls } from "../utils" +import { closeDialog, clickMenuItem } from "../actions" const DEFAULT_SERVER_URL_KEY = "opencode.settings.dat:defaultServerUrl" @@ -31,10 +31,9 @@ test("can set a default server on web", async ({ page, gotoSession }) => { const dialog = page.getByRole("dialog") await expect(dialog).toBeVisible() - const row = dialog.locator('[data-slot="list-item"]').filter({ hasText: serverName }).first() - await expect(row).toBeVisible() + await expect(dialog.getByText(serverNamePattern).first()).toBeVisible() - const menuTrigger = row.locator('[data-slot="dropdown-menu-trigger"]').first() + const menuTrigger = dialog.locator('[data-slot="dropdown-menu-trigger"]').first() await expect(menuTrigger).toBeVisible() await menuTrigger.click({ force: true }) @@ -42,14 +41,18 @@ test("can set a default server on web", async ({ page, gotoSession }) => { await expect(menu).toBeVisible() await clickMenuItem(menu, /set as default/i) - await expect.poll(() => page.evaluate((key) => localStorage.getItem(key), DEFAULT_SERVER_URL_KEY)).toBe(serverUrl) - await expect(row.getByText("Default", { exact: true })).toBeVisible() + await expect + .poll(async () => + serverUrls.includes((await page.evaluate((key) => localStorage.getItem(key), DEFAULT_SERVER_URL_KEY)) ?? ""), + ) + .toBe(true) + await expect(dialog.getByText("Default", { exact: true })).toBeVisible() await closeDialog(page, dialog) await ensurePopoverOpen() - const serverRow = popover.locator("button").filter({ hasText: serverName }).first() + const serverRow = popover.locator("button").filter({ hasText: serverNamePattern }).first() await expect(serverRow).toBeVisible() await expect(serverRow.getByText("Default", { exact: true })).toBeVisible() }) diff --git a/packages/app/e2e/commands/panels.spec.ts b/packages/app/e2e/commands/panels.spec.ts index 58c1f0a9a..7e5d7bd6e 100644 --- a/packages/app/e2e/commands/panels.spec.ts +++ b/packages/app/e2e/commands/panels.spec.ts @@ -10,6 +10,8 @@ const expanded = async (el: { getAttribute: (name: string) => Promise { await gotoSession() + const reviewPanel = page.locator("#review-panel") + const treeToggle = page.getByRole("button", { name: "Toggle file tree" }).first() await expect(treeToggle).toBeVisible() if (await expanded(treeToggle)) await treeToggle.click() @@ -19,13 +21,13 @@ test("review panel can be toggled via keybind", async ({ page, gotoSession }) => await expect(reviewToggle).toBeVisible() if (await expanded(reviewToggle)) await reviewToggle.click() await expect(reviewToggle).toHaveAttribute("aria-expanded", "false") - await expect(page.locator("#review-panel")).toHaveCount(0) + await expect(reviewPanel).toHaveAttribute("aria-hidden", "true") await page.keyboard.press(`${modKey}+Shift+R`) await expect(reviewToggle).toHaveAttribute("aria-expanded", "true") - await expect(page.locator("#review-panel")).toBeVisible() + await expect(reviewPanel).toHaveAttribute("aria-hidden", "false") await page.keyboard.press(`${modKey}+Shift+R`) await expect(reviewToggle).toHaveAttribute("aria-expanded", "false") - await expect(page.locator("#review-panel")).toHaveCount(0) + await expect(reviewPanel).toHaveAttribute("aria-hidden", "true") }) diff --git a/packages/app/e2e/files/file-tree.spec.ts b/packages/app/e2e/files/file-tree.spec.ts index 44efb7f00..a5872bdf8 100644 --- a/packages/app/e2e/files/file-tree.spec.ts +++ b/packages/app/e2e/files/file-tree.spec.ts @@ -43,6 +43,13 @@ test("file tree can expand folders and open a file", async ({ page, gotoSession await tab.click() await expect(tab).toHaveAttribute("aria-selected", "true") + await toggle.click() + await expect(toggle).toHaveAttribute("aria-expanded", "false") + + await toggle.click() + await expect(toggle).toHaveAttribute("aria-expanded", "true") + await expect(allTab).toHaveAttribute("aria-selected", "true") + const viewer = page.locator('[data-component="file"][data-mode="text"]').first() await expect(viewer).toBeVisible() await expect(viewer).toContainText("export default function FileTree") diff --git a/packages/app/e2e/session/session-undo-redo.spec.ts b/packages/app/e2e/session/session-undo-redo.spec.ts index c6ea2aea0..eb0840f7c 100644 --- a/packages/app/e2e/session/session-undo-redo.spec.ts +++ b/packages/app/e2e/session/session-undo-redo.spec.ts @@ -45,7 +45,7 @@ async function seedConversation(input: { .toBe(true) if (!userMessageID) throw new Error("Expected a user message id") - await expect(input.page.locator(`[data-message-id="${userMessageID}"]`).first()).toBeVisible({ timeout: 30_000 }) + await expect(input.page.locator(`[data-message-id="${userMessageID}"]`)).toHaveCount(1, { timeout: 30_000 }) return { prompt, userMessageID } } @@ -123,7 +123,7 @@ test("slash redo clears revert and restores latest state", async ({ page, withPr .toBeUndefined() await expect(seeded.prompt).not.toContainText(token) - await expect(page.locator(`[data-message-id="${seeded.userMessageID}"]`).first()).toBeVisible() + await expect(page.locator(`[data-message-id="${seeded.userMessageID}"]`)).toHaveCount(1) }) }) }) @@ -158,8 +158,8 @@ test("slash undo/redo traverses multi-step revert stack", async ({ page, withPro const firstMessage = page.locator(`[data-message-id="${first.userMessageID}"]`) const secondMessage = page.locator(`[data-message-id="${second.userMessageID}"]`) - await expect(firstMessage.first()).toBeVisible() - await expect(secondMessage.first()).toBeVisible() + await expect(firstMessage).toHaveCount(1) + await expect(secondMessage).toHaveCount(1) await second.prompt.click() await page.keyboard.press(`${modKey}+A`) @@ -176,7 +176,7 @@ test("slash undo/redo traverses multi-step revert stack", async ({ page, withPro }) .toBe(second.userMessageID) - await expect(firstMessage.first()).toBeVisible() + await expect(firstMessage).toHaveCount(1) await expect(secondMessage).toHaveCount(0) await second.prompt.click() @@ -210,7 +210,7 @@ test("slash undo/redo traverses multi-step revert stack", async ({ page, withPro }) .toBe(second.userMessageID) - await expect(firstMessage.first()).toBeVisible() + await expect(firstMessage).toHaveCount(1) await expect(secondMessage).toHaveCount(0) await second.prompt.click() @@ -226,8 +226,8 @@ test("slash undo/redo traverses multi-step revert stack", async ({ page, withPro }) .toBeUndefined() - await expect(firstMessage.first()).toBeVisible() - await expect(secondMessage.first()).toBeVisible() + await expect(firstMessage).toHaveCount(1) + await expect(secondMessage).toHaveCount(1) }) }) }) diff --git a/packages/app/e2e/utils.ts b/packages/app/e2e/utils.ts index c5bbba9d8..0dbc5f8b5 100644 --- a/packages/app/e2e/utils.ts +++ b/packages/app/e2e/utils.ts @@ -7,6 +7,22 @@ export const serverPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096" export const serverUrl = `http://${serverHost}:${serverPort}` export const serverName = `${serverHost}:${serverPort}` +const localHosts = ["127.0.0.1", "localhost"] + +const serverLabels = (() => { + const url = new URL(serverUrl) + if (!localHosts.includes(url.hostname)) return [serverName] + return localHosts.map((host) => `${host}:${url.port}`) +})() + +export const serverNames = [...new Set(serverLabels)] + +export const serverUrls = serverNames.map((name) => `http://${name}`) + +const escape = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + +export const serverNamePattern = new RegExp(`(?:${serverNames.map(escape).join("|")})`) + export const modKey = process.platform === "darwin" ? "Meta" : "Control" export const terminalToggleKey = "Control+Backquote" diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 302541d5e..4e469f73d 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1038,23 +1038,6 @@ export default function Page() { tabs().setActive(next) }) - createEffect( - on( - () => layout.fileTree.opened(), - (opened, prev) => { - if (prev === undefined) return - if (!isDesktop()) return - - if (opened) { - const active = tabs().active() - const tab = active === "review" || (!active && hasReview()) ? "changes" : "all" - layout.fileTree.setTab(tab) - } - }, - { defer: true }, - ), - ) - createEffect(() => { const id = params.id if (!id) return