test(app): more e2e tests (#13162)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { test, expect } from "../fixtures"
|
||||
import { openSidebar, withSession } from "../actions"
|
||||
import { defocus, openSidebar, withSession } from "../actions"
|
||||
import { promptSelector } from "../selectors"
|
||||
import { modKey } from "../utils"
|
||||
|
||||
test("titlebar back/forward navigates between sessions", async ({ page, slug, sdk, gotoSession }) => {
|
||||
await page.setViewportSize({ width: 1400, height: 800 })
|
||||
@@ -40,3 +41,84 @@ test("titlebar back/forward navigates between sessions", async ({ page, slug, sd
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test("titlebar forward is cleared after branching history from sidebar", async ({ page, slug, sdk, gotoSession }) => {
|
||||
await page.setViewportSize({ width: 1400, height: 800 })
|
||||
|
||||
const stamp = Date.now()
|
||||
|
||||
await withSession(sdk, `e2e titlebar history a ${stamp}`, async (a) => {
|
||||
await withSession(sdk, `e2e titlebar history b ${stamp}`, async (b) => {
|
||||
await withSession(sdk, `e2e titlebar history c ${stamp}`, async (c) => {
|
||||
await gotoSession(a.id)
|
||||
|
||||
await openSidebar(page)
|
||||
|
||||
const second = page.locator(`[data-session-id="${b.id}"] a`).first()
|
||||
await expect(second).toBeVisible()
|
||||
await second.scrollIntoViewIfNeeded()
|
||||
await second.click()
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${b.id}(?:\\?|#|$)`))
|
||||
await expect(page.locator(promptSelector)).toBeVisible()
|
||||
|
||||
const back = page.getByRole("button", { name: "Back" })
|
||||
const forward = page.getByRole("button", { name: "Forward" })
|
||||
|
||||
await expect(back).toBeVisible()
|
||||
await expect(back).toBeEnabled()
|
||||
await back.click()
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${a.id}(?:\\?|#|$)`))
|
||||
await expect(page.locator(promptSelector)).toBeVisible()
|
||||
|
||||
await openSidebar(page)
|
||||
|
||||
const third = page.locator(`[data-session-id="${c.id}"] a`).first()
|
||||
await expect(third).toBeVisible()
|
||||
await third.scrollIntoViewIfNeeded()
|
||||
await third.click()
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${c.id}(?:\\?|#|$)`))
|
||||
await expect(page.locator(promptSelector)).toBeVisible()
|
||||
|
||||
await expect(forward).toBeVisible()
|
||||
await expect(forward).toBeDisabled()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test("keyboard shortcuts navigate titlebar history", async ({ page, slug, sdk, gotoSession }) => {
|
||||
await page.setViewportSize({ width: 1400, height: 800 })
|
||||
|
||||
const stamp = Date.now()
|
||||
|
||||
await withSession(sdk, `e2e titlebar shortcuts 1 ${stamp}`, async (one) => {
|
||||
await withSession(sdk, `e2e titlebar shortcuts 2 ${stamp}`, async (two) => {
|
||||
await gotoSession(one.id)
|
||||
|
||||
await openSidebar(page)
|
||||
|
||||
const link = page.locator(`[data-session-id="${two.id}"] a`).first()
|
||||
await expect(link).toBeVisible()
|
||||
await link.scrollIntoViewIfNeeded()
|
||||
await link.click()
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`))
|
||||
await expect(page.locator(promptSelector)).toBeVisible()
|
||||
|
||||
await defocus(page)
|
||||
await page.keyboard.press(`${modKey}+[`)
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${one.id}(?:\\?|#|$)`))
|
||||
await expect(page.locator(promptSelector)).toBeVisible()
|
||||
|
||||
await defocus(page)
|
||||
await page.keyboard.press(`${modKey}+]`)
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`))
|
||||
await expect(page.locator(promptSelector)).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,37 +1,49 @@
|
||||
import { test, expect } from "../fixtures"
|
||||
|
||||
test.skip("file tree can expand folders and open a file", async ({ page, gotoSession }) => {
|
||||
test("file tree can expand folders and open a file", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
const toggle = page.getByRole("button", { name: "Toggle file tree" })
|
||||
const treeTabs = page.locator('[data-component="tabs"][data-variant="pill"][data-scope="filetree"]')
|
||||
const panel = page.locator("#file-tree-panel")
|
||||
const treeTabs = panel.locator('[data-component="tabs"][data-variant="pill"][data-scope="filetree"]')
|
||||
|
||||
await expect(toggle).toBeVisible()
|
||||
if ((await toggle.getAttribute("aria-expanded")) !== "true") await toggle.click()
|
||||
await expect(toggle).toHaveAttribute("aria-expanded", "true")
|
||||
await expect(panel).toBeVisible()
|
||||
await expect(treeTabs).toBeVisible()
|
||||
|
||||
await treeTabs.locator('[data-slot="tabs-trigger"]').nth(1).click()
|
||||
const allTab = treeTabs.getByRole("tab", { name: /^all files$/i })
|
||||
await expect(allTab).toBeVisible()
|
||||
await allTab.click()
|
||||
await expect(allTab).toHaveAttribute("aria-selected", "true")
|
||||
|
||||
const node = (name: string) => treeTabs.getByRole("button", { name, exact: true })
|
||||
const tree = treeTabs.locator('[data-slot="tabs-content"]:not([hidden])')
|
||||
await expect(tree).toBeVisible()
|
||||
|
||||
await expect(node("packages")).toBeVisible()
|
||||
await node("packages").click()
|
||||
const expand = async (name: string) => {
|
||||
const folder = tree.getByRole("button", { name, exact: true }).first()
|
||||
await expect(folder).toBeVisible()
|
||||
await expect(folder).toHaveAttribute("aria-expanded", /true|false/)
|
||||
if ((await folder.getAttribute("aria-expanded")) === "false") await folder.click()
|
||||
await expect(folder).toHaveAttribute("aria-expanded", "true")
|
||||
}
|
||||
|
||||
await expect(node("app")).toBeVisible()
|
||||
await node("app").click()
|
||||
await expand("packages")
|
||||
await expand("app")
|
||||
await expand("src")
|
||||
await expand("components")
|
||||
|
||||
await expect(node("src")).toBeVisible()
|
||||
await node("src").click()
|
||||
|
||||
await expect(node("components")).toBeVisible()
|
||||
await node("components").click()
|
||||
|
||||
await expect(node("file-tree.tsx")).toBeVisible()
|
||||
await node("file-tree.tsx").click()
|
||||
const file = tree.getByRole("button", { name: "file-tree.tsx", exact: true }).first()
|
||||
await expect(file).toBeVisible()
|
||||
await file.click()
|
||||
|
||||
const tab = page.getByRole("tab", { name: "file-tree.tsx" })
|
||||
await expect(tab).toBeVisible()
|
||||
await tab.click()
|
||||
await expect(tab).toHaveAttribute("aria-selected", "true")
|
||||
|
||||
const code = page.locator('[data-component="code"]').first()
|
||||
await expect(code.getByText("export default function FileTree")).toBeVisible()
|
||||
await expect(code).toBeVisible()
|
||||
await expect(code).toContainText("export default function FileTree")
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { test, expect } from "../fixtures"
|
||||
import { createTestProject, cleanupTestProject, openSidebar, clickMenuItem } from "../actions"
|
||||
import { projectCloseHoverSelector, projectCloseMenuSelector, projectSwitchSelector } from "../selectors"
|
||||
import { createTestProject, cleanupTestProject, openSidebar, clickMenuItem, openProjectMenu } from "../actions"
|
||||
import { projectCloseHoverSelector, projectSwitchSelector } from "../selectors"
|
||||
import { dirSlug } from "../utils"
|
||||
|
||||
test("can close a project via hover card close button", async ({ page, withProject }) => {
|
||||
@@ -31,16 +31,15 @@ test("can close a project via hover card close button", async ({ page, withProje
|
||||
}
|
||||
})
|
||||
|
||||
test("can close a project via project header more options menu", async ({ page, withProject }) => {
|
||||
test("closing active project navigates to another open project", async ({ page, withProject }) => {
|
||||
await page.setViewportSize({ width: 1400, height: 800 })
|
||||
|
||||
const other = await createTestProject()
|
||||
const otherName = other.split("/").pop() ?? other
|
||||
const otherSlug = dirSlug(other)
|
||||
|
||||
try {
|
||||
await withProject(
|
||||
async () => {
|
||||
async ({ slug }) => {
|
||||
await openSidebar(page)
|
||||
|
||||
const otherButton = page.locator(projectSwitchSelector(otherSlug)).first()
|
||||
@@ -49,21 +48,20 @@ test("can close a project via project header more options menu", async ({ page,
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/${otherSlug}/session`))
|
||||
|
||||
const header = page
|
||||
.locator(".group\\/project")
|
||||
.filter({ has: page.locator(`[data-action="project-menu"][data-project="${otherSlug}"]`) })
|
||||
.first()
|
||||
await expect(header).toContainText(otherName)
|
||||
|
||||
const trigger = header.locator(`[data-action="project-menu"][data-project="${otherSlug}"]`).first()
|
||||
await expect(trigger).toHaveCount(1)
|
||||
await trigger.focus()
|
||||
await page.keyboard.press("Enter")
|
||||
|
||||
const menu = page.locator('[data-component="dropdown-menu-content"]').first()
|
||||
await expect(menu).toBeVisible({ timeout: 10_000 })
|
||||
const menu = await openProjectMenu(page, otherSlug)
|
||||
|
||||
await clickMenuItem(menu, /^Close$/i, { force: true })
|
||||
|
||||
await expect
|
||||
.poll(() => {
|
||||
const pathname = new URL(page.url()).pathname
|
||||
if (new RegExp(`^/${slug}/session(?:/[^/]+)?/?$`).test(pathname)) return "project"
|
||||
if (pathname === "/") return "home"
|
||||
return ""
|
||||
})
|
||||
.toMatch(/^(project|home)$/)
|
||||
|
||||
await expect(page).not.toHaveURL(new RegExp(`/${otherSlug}/session(?:[/?#]|$)`))
|
||||
await expect(otherButton).toHaveCount(0)
|
||||
},
|
||||
{ extra: [other] },
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { base64Decode } from "@opencode-ai/util/encode"
|
||||
import fs from "node:fs/promises"
|
||||
import os from "node:os"
|
||||
import path from "node:path"
|
||||
import type { Page } from "@playwright/test"
|
||||
|
||||
@@ -10,11 +11,18 @@ import {
|
||||
cleanupTestProject,
|
||||
clickMenuItem,
|
||||
confirmDialog,
|
||||
openProjectMenu,
|
||||
openSidebar,
|
||||
openWorkspaceMenu,
|
||||
setWorkspacesEnabled,
|
||||
} from "../actions"
|
||||
import { inlineInputSelector, workspaceItemSelector } from "../selectors"
|
||||
import {
|
||||
inlineInputSelector,
|
||||
projectSwitchSelector,
|
||||
projectWorkspacesToggleSelector,
|
||||
workspaceItemSelector,
|
||||
} from "../selectors"
|
||||
import { dirSlug } from "../utils"
|
||||
|
||||
function slugFromUrl(url: string) {
|
||||
return /\/([^/]+)\/session(?:\/|$)/.exec(url)?.[1] ?? ""
|
||||
@@ -126,6 +134,40 @@ test("can create a workspace", async ({ page, withProject }) => {
|
||||
})
|
||||
})
|
||||
|
||||
test("non-git projects keep workspace mode disabled", async ({ page, withProject }) => {
|
||||
await page.setViewportSize({ width: 1400, height: 800 })
|
||||
|
||||
const nonGit = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-e2e-project-nongit-"))
|
||||
const nonGitSlug = dirSlug(nonGit)
|
||||
|
||||
await fs.writeFile(path.join(nonGit, "README.md"), "# e2e nongit\n")
|
||||
|
||||
try {
|
||||
await withProject(
|
||||
async () => {
|
||||
await openSidebar(page)
|
||||
|
||||
const nonGitButton = page.locator(projectSwitchSelector(nonGitSlug)).first()
|
||||
await expect(nonGitButton).toBeVisible()
|
||||
await nonGitButton.click()
|
||||
await expect(page).toHaveURL(new RegExp(`/${nonGitSlug}/session`))
|
||||
|
||||
const menu = await openProjectMenu(page, nonGitSlug)
|
||||
const toggle = menu.locator(projectWorkspacesToggleSelector(nonGitSlug)).first()
|
||||
|
||||
await expect(toggle).toBeVisible()
|
||||
await expect(toggle).toBeDisabled()
|
||||
|
||||
await expect(menu.getByRole("menuitem", { name: "New workspace" })).toHaveCount(0)
|
||||
await expect(page.getByRole("button", { name: "New workspace" })).toHaveCount(0)
|
||||
},
|
||||
{ extra: [nonGit] },
|
||||
)
|
||||
} finally {
|
||||
await cleanupTestProject(nonGit)
|
||||
}
|
||||
})
|
||||
|
||||
test("can rename a workspace", async ({ page, withProject }) => {
|
||||
await page.setViewportSize({ width: 1400, height: 800 })
|
||||
|
||||
|
||||
@@ -23,10 +23,15 @@ async function seedConversation(input: {
|
||||
const messages = await input.sdk.session
|
||||
.messages({ sessionID: input.sessionID, limit: 50 })
|
||||
.then((r) => r.data ?? [])
|
||||
const users = messages.filter((m) => m.info.role === "user")
|
||||
const users = messages.filter(
|
||||
(m) =>
|
||||
m.info.role === "user" &&
|
||||
m.parts.filter((p) => p.type === "text").some((p) => p.text.includes(input.token)),
|
||||
)
|
||||
if (users.length === 0) return false
|
||||
|
||||
const user = users.reduce((acc, item) => (item.info.id > acc.info.id ? item : acc))
|
||||
const user = users[users.length - 1]
|
||||
if (!user) return false
|
||||
userMessageID = user.info.id
|
||||
|
||||
const assistantText = messages
|
||||
@@ -124,3 +129,107 @@ test("slash redo clears revert and restores latest state", async ({ page, withPr
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test("slash undo/redo traverses multi-step revert stack", async ({ page, withProject }) => {
|
||||
test.setTimeout(120_000)
|
||||
|
||||
const firstToken = `undo_redo_first_${Date.now()}`
|
||||
const secondToken = `undo_redo_second_${Date.now()}`
|
||||
|
||||
await withProject(async (project) => {
|
||||
const sdk = createSdk(project.directory)
|
||||
|
||||
await withSession(sdk, `e2e undo redo stack ${Date.now()}`, async (session) => {
|
||||
await project.gotoSession(session.id)
|
||||
|
||||
const first = await seedConversation({
|
||||
page,
|
||||
sdk,
|
||||
sessionID: session.id,
|
||||
token: firstToken,
|
||||
})
|
||||
const second = await seedConversation({
|
||||
page,
|
||||
sdk,
|
||||
sessionID: session.id,
|
||||
token: secondToken,
|
||||
})
|
||||
|
||||
expect(first.userMessageID).not.toBe(second.userMessageID)
|
||||
|
||||
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 second.prompt.click()
|
||||
await page.keyboard.press(`${modKey}+A`)
|
||||
await page.keyboard.press("Backspace")
|
||||
await page.keyboard.type("/undo")
|
||||
|
||||
const undo = page.locator('[data-slash-id="session.undo"]').first()
|
||||
await expect(undo).toBeVisible()
|
||||
await page.keyboard.press("Enter")
|
||||
|
||||
await expect
|
||||
.poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
|
||||
timeout: 30_000,
|
||||
})
|
||||
.toBe(second.userMessageID)
|
||||
|
||||
await expect(firstMessage.first()).toBeVisible()
|
||||
await expect(secondMessage).toHaveCount(0)
|
||||
|
||||
await second.prompt.click()
|
||||
await page.keyboard.press(`${modKey}+A`)
|
||||
await page.keyboard.press("Backspace")
|
||||
await page.keyboard.type("/undo")
|
||||
await expect(undo).toBeVisible()
|
||||
await page.keyboard.press("Enter")
|
||||
|
||||
await expect
|
||||
.poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
|
||||
timeout: 30_000,
|
||||
})
|
||||
.toBe(first.userMessageID)
|
||||
|
||||
await expect(firstMessage).toHaveCount(0)
|
||||
await expect(secondMessage).toHaveCount(0)
|
||||
|
||||
await second.prompt.click()
|
||||
await page.keyboard.press(`${modKey}+A`)
|
||||
await page.keyboard.press("Backspace")
|
||||
await page.keyboard.type("/redo")
|
||||
|
||||
const redo = page.locator('[data-slash-id="session.redo"]').first()
|
||||
await expect(redo).toBeVisible()
|
||||
await page.keyboard.press("Enter")
|
||||
|
||||
await expect
|
||||
.poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
|
||||
timeout: 30_000,
|
||||
})
|
||||
.toBe(second.userMessageID)
|
||||
|
||||
await expect(firstMessage.first()).toBeVisible()
|
||||
await expect(secondMessage).toHaveCount(0)
|
||||
|
||||
await second.prompt.click()
|
||||
await page.keyboard.press(`${modKey}+A`)
|
||||
await page.keyboard.press("Backspace")
|
||||
await page.keyboard.type("/redo")
|
||||
await expect(redo).toBeVisible()
|
||||
await page.keyboard.press("Enter")
|
||||
|
||||
await expect
|
||||
.poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
|
||||
timeout: 30_000,
|
||||
})
|
||||
.toBeUndefined()
|
||||
|
||||
await expect(firstMessage.first()).toBeVisible()
|
||||
await expect(secondMessage.first()).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@ test("changing sidebar toggle keybind works", async ({ page, gotoSession }) => {
|
||||
const dialog = await openSettings(page)
|
||||
await dialog.getByRole("tab", { name: "Shortcuts" }).click()
|
||||
|
||||
const keybindButton = dialog.locator(keybindButtonSelector("sidebar.toggle"))
|
||||
const keybindButton = dialog.locator(keybindButtonSelector("sidebar.toggle")).first()
|
||||
await expect(keybindButton).toBeVisible()
|
||||
|
||||
const initialKeybind = await keybindButton.textContent()
|
||||
@@ -51,6 +51,40 @@ test("changing sidebar toggle keybind works", async ({ page, gotoSession }) => {
|
||||
expect(finalClosed).toBe(initiallyClosed)
|
||||
})
|
||||
|
||||
test("sidebar toggle keybind guards against shortcut conflicts", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
const dialog = await openSettings(page)
|
||||
await dialog.getByRole("tab", { name: "Shortcuts" }).click()
|
||||
|
||||
const keybindButton = dialog.locator(keybindButtonSelector("sidebar.toggle"))
|
||||
await expect(keybindButton).toBeVisible()
|
||||
|
||||
const initialKeybind = await keybindButton.textContent()
|
||||
expect(initialKeybind).toContain("B")
|
||||
|
||||
await keybindButton.click()
|
||||
await expect(keybindButton).toHaveText(/press/i)
|
||||
|
||||
await page.keyboard.press(`${modKey}+Shift+KeyP`)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const toast = page.locator('[data-component="toast"]').last()
|
||||
await expect(toast).toBeVisible()
|
||||
await expect(toast).toContainText(/already/i)
|
||||
|
||||
await keybindButton.click()
|
||||
await expect(keybindButton).toContainText("B")
|
||||
|
||||
const stored = await page.evaluate(() => {
|
||||
const raw = localStorage.getItem("settings.v3")
|
||||
return raw ? JSON.parse(raw) : null
|
||||
})
|
||||
expect(stored?.keybinds?.["sidebar.toggle"]).toBeUndefined()
|
||||
|
||||
await closeDialog(page, dialog)
|
||||
})
|
||||
|
||||
test("resetting all keybinds to defaults works", async ({ page, gotoSession }) => {
|
||||
await page.addInitScript(() => {
|
||||
localStorage.setItem("settings.v3", JSON.stringify({ keybinds: { "sidebar.toggle": "mod+shift+x" } }))
|
||||
@@ -277,6 +311,44 @@ test("changing terminal toggle keybind works", async ({ page, gotoSession }) =>
|
||||
await expect(terminal).not.toBeVisible()
|
||||
})
|
||||
|
||||
test("terminal toggle keybind persists after reload", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
const dialog = await openSettings(page)
|
||||
await dialog.getByRole("tab", { name: "Shortcuts" }).click()
|
||||
|
||||
const keybindButton = dialog.locator(keybindButtonSelector("terminal.toggle"))
|
||||
await expect(keybindButton).toBeVisible()
|
||||
|
||||
await keybindButton.click()
|
||||
await expect(keybindButton).toHaveText(/press/i)
|
||||
|
||||
await page.keyboard.press(`${modKey}+Shift+KeyY`)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await expect(keybindButton).toContainText("Y")
|
||||
await closeDialog(page, dialog)
|
||||
|
||||
await page.reload()
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return await page.evaluate(() => {
|
||||
const raw = localStorage.getItem("settings.v3")
|
||||
if (!raw) return
|
||||
const parsed = JSON.parse(raw)
|
||||
return parsed?.keybinds?.["terminal.toggle"]
|
||||
})
|
||||
})
|
||||
.toBe("mod+shift+y")
|
||||
|
||||
const reloaded = await openSettings(page)
|
||||
await reloaded.getByRole("tab", { name: "Shortcuts" }).click()
|
||||
const reloadedKeybind = reloaded.locator(keybindButtonSelector("terminal.toggle")).first()
|
||||
await expect(reloadedKeybind).toContainText("Y")
|
||||
await closeDialog(page, reloaded)
|
||||
})
|
||||
|
||||
test("changing command palette keybind works", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
settingsNotificationsPermissionsSelector,
|
||||
settingsReleaseNotesSelector,
|
||||
settingsSoundsAgentSelector,
|
||||
settingsSoundsErrorsSelector,
|
||||
settingsSoundsPermissionsSelector,
|
||||
settingsThemeSelector,
|
||||
settingsUpdatesStartupSelector,
|
||||
} from "../selectors"
|
||||
@@ -139,6 +141,105 @@ test("changing font persists in localStorage and updates CSS variable", async ({
|
||||
expect(newFontFamily).not.toBe(initialFontFamily)
|
||||
})
|
||||
|
||||
test("color scheme and font rehydrate after reload", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
const dialog = await openSettings(page)
|
||||
|
||||
const colorSchemeSelect = dialog.locator(settingsColorSchemeSelector)
|
||||
await expect(colorSchemeSelect).toBeVisible()
|
||||
await colorSchemeSelect.locator('[data-slot="select-select-trigger"]').click()
|
||||
await page.locator('[data-slot="select-select-item"]').filter({ hasText: "Dark" }).click()
|
||||
await expect(page.locator("html")).toHaveAttribute("data-color-scheme", "dark")
|
||||
|
||||
const fontSelect = dialog.locator(settingsFontSelector)
|
||||
await expect(fontSelect).toBeVisible()
|
||||
|
||||
const initialFontFamily = await page.evaluate(() => {
|
||||
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim()
|
||||
})
|
||||
|
||||
const initialSettings = await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
}, settingsKey)
|
||||
|
||||
const currentFont =
|
||||
(await fontSelect.locator('[data-slot="select-select-trigger-value"]').textContent())?.trim() ?? ""
|
||||
await fontSelect.locator('[data-slot="select-select-trigger"]').click()
|
||||
|
||||
const fontItems = page.locator('[data-slot="select-select-item"]')
|
||||
expect(await fontItems.count()).toBeGreaterThan(1)
|
||||
|
||||
if (currentFont) {
|
||||
await fontItems.filter({ hasNotText: currentFont }).first().click()
|
||||
}
|
||||
if (!currentFont) {
|
||||
await fontItems.nth(1).click()
|
||||
}
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
}, settingsKey)
|
||||
})
|
||||
.toMatchObject({
|
||||
appearance: {
|
||||
font: expect.any(String),
|
||||
},
|
||||
})
|
||||
|
||||
const updatedSettings = await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
}, settingsKey)
|
||||
|
||||
const updatedFontFamily = await page.evaluate(() => {
|
||||
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim()
|
||||
})
|
||||
expect(updatedFontFamily).not.toBe(initialFontFamily)
|
||||
expect(updatedSettings?.appearance?.font).not.toBe(initialSettings?.appearance?.font)
|
||||
|
||||
await closeDialog(page, dialog)
|
||||
await page.reload()
|
||||
|
||||
await expect(page.locator("html")).toHaveAttribute("data-color-scheme", "dark")
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
}, settingsKey)
|
||||
})
|
||||
.toMatchObject({
|
||||
appearance: {
|
||||
font: updatedSettings?.appearance?.font,
|
||||
},
|
||||
})
|
||||
|
||||
const rehydratedSettings = await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
}, settingsKey)
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return await page.evaluate(() => {
|
||||
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim()
|
||||
})
|
||||
})
|
||||
.not.toBe(initialFontFamily)
|
||||
|
||||
const rehydratedFontFamily = await page.evaluate(() => {
|
||||
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim()
|
||||
})
|
||||
expect(rehydratedFontFamily).not.toBe(initialFontFamily)
|
||||
expect(rehydratedSettings?.appearance?.font).toBe(updatedSettings?.appearance?.font)
|
||||
})
|
||||
|
||||
test("toggling notification agent switch updates localStorage", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
@@ -234,6 +335,67 @@ test("changing sound agent selection persists in localStorage", async ({ page, g
|
||||
expect(stored?.sounds?.agent).not.toBe("staplebops-01")
|
||||
})
|
||||
|
||||
test("changing permissions and errors sounds updates localStorage", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
const dialog = await openSettings(page)
|
||||
const permissionsSelect = dialog.locator(settingsSoundsPermissionsSelector)
|
||||
const errorsSelect = dialog.locator(settingsSoundsErrorsSelector)
|
||||
await expect(permissionsSelect).toBeVisible()
|
||||
await expect(errorsSelect).toBeVisible()
|
||||
|
||||
const initial = await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
}, settingsKey)
|
||||
|
||||
const permissionsCurrent =
|
||||
(await permissionsSelect.locator('[data-slot="select-select-trigger-value"]').textContent())?.trim() ?? ""
|
||||
await permissionsSelect.locator('[data-slot="select-select-trigger"]').click()
|
||||
const permissionItems = page.locator('[data-slot="select-select-item"]')
|
||||
expect(await permissionItems.count()).toBeGreaterThan(1)
|
||||
if (permissionsCurrent) {
|
||||
await permissionItems.filter({ hasNotText: permissionsCurrent }).first().click()
|
||||
}
|
||||
if (!permissionsCurrent) {
|
||||
await permissionItems.nth(1).click()
|
||||
}
|
||||
|
||||
const errorsCurrent =
|
||||
(await errorsSelect.locator('[data-slot="select-select-trigger-value"]').textContent())?.trim() ?? ""
|
||||
await errorsSelect.locator('[data-slot="select-select-trigger"]').click()
|
||||
const errorItems = page.locator('[data-slot="select-select-item"]')
|
||||
expect(await errorItems.count()).toBeGreaterThan(1)
|
||||
if (errorsCurrent) {
|
||||
await errorItems.filter({ hasNotText: errorsCurrent }).first().click()
|
||||
}
|
||||
if (!errorsCurrent) {
|
||||
await errorItems.nth(1).click()
|
||||
}
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
}, settingsKey)
|
||||
})
|
||||
.toMatchObject({
|
||||
sounds: {
|
||||
permissions: expect.any(String),
|
||||
errors: expect.any(String),
|
||||
},
|
||||
})
|
||||
|
||||
const stored = await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
}, settingsKey)
|
||||
|
||||
expect(stored?.sounds?.permissions).not.toBe(initial?.sounds?.permissions)
|
||||
expect(stored?.sounds?.errors).not.toBe(initial?.sounds?.errors)
|
||||
})
|
||||
|
||||
test("toggling updates startup switch updates localStorage", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { test, expect } from "../fixtures"
|
||||
import { openSidebar, toggleSidebar } from "../actions"
|
||||
import { openSidebar, toggleSidebar, withSession } from "../actions"
|
||||
|
||||
test("sidebar can be collapsed and expanded", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
@@ -12,3 +12,26 @@ test("sidebar can be collapsed and expanded", async ({ page, gotoSession }) => {
|
||||
await toggleSidebar(page)
|
||||
await expect(page.locator("main")).not.toHaveClass(/xl:border-l/)
|
||||
})
|
||||
|
||||
test("sidebar collapsed state persists across navigation and reload", async ({ page, sdk, gotoSession }) => {
|
||||
await withSession(sdk, "sidebar persist session 1", async (session1) => {
|
||||
await withSession(sdk, "sidebar persist session 2", async (session2) => {
|
||||
await gotoSession(session1.id)
|
||||
|
||||
await openSidebar(page)
|
||||
await toggleSidebar(page)
|
||||
await expect(page.locator("main")).toHaveClass(/xl:border-l/)
|
||||
|
||||
await gotoSession(session2.id)
|
||||
await expect(page.locator("main")).toHaveClass(/xl:border-l/)
|
||||
|
||||
await page.reload()
|
||||
await expect(page.locator("main")).toHaveClass(/xl:border-l/)
|
||||
|
||||
const opened = await page.evaluate(
|
||||
() => JSON.parse(localStorage.getItem("opencode.global.dat:layout") ?? "{}").sidebar?.opened,
|
||||
)
|
||||
await expect(opened).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user