summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorFilip <[email protected]>2026-03-07 16:47:45 +0100
committerGitHub <[email protected]>2026-03-07 09:47:45 -0600
commit66fcab7b0801130c212aa67159415d75d935f555 (patch)
treeee7edb3346c6b3ecc0147f227b4b1a0aabbb0713
parent641e1781a25f848a9375a9196e5039c8e65e5551 (diff)
downloadopencode-66fcab7b0801130c212aa67159415d75d935f555.tar.gz
opencode-66fcab7b0801130c212aa67159415d75d935f555.zip
fix(app): preserve file tree tab on reopen + fix e2e test regressions (#16482)
-rw-r--r--packages/app/e2e/app/home.spec.ts6
-rw-r--r--packages/app/e2e/app/server-default.spec.ts19
-rw-r--r--packages/app/e2e/commands/panels.spec.ts8
-rw-r--r--packages/app/e2e/files/file-tree.spec.ts7
-rw-r--r--packages/app/e2e/session/session-undo-redo.spec.ts16
-rw-r--r--packages/app/e2e/utils.ts16
-rw-r--r--packages/app/src/pages/session.tsx17
7 files changed, 50 insertions, 39 deletions
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<string | n
test("review panel can be toggled via keybind", async ({ page, gotoSession }) => {
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