diff --git a/packages/app/e2e/home.spec.ts b/packages/app/e2e/app/home.spec.ts similarity index 88% rename from packages/app/e2e/home.spec.ts rename to packages/app/e2e/app/home.spec.ts index c6fb0e3b074f..f21dc40ec21a 100644 --- a/packages/app/e2e/home.spec.ts +++ b/packages/app/e2e/app/home.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { serverName } from "./utils" +import { test, expect } from "../fixtures" +import { serverName } from "../utils" test("home renders and shows core entrypoints", async ({ page }) => { await page.goto("/") diff --git a/packages/app/e2e/navigation.spec.ts b/packages/app/e2e/app/navigation.spec.ts similarity index 72% rename from packages/app/e2e/navigation.spec.ts rename to packages/app/e2e/app/navigation.spec.ts index 76923af6ede1..0812ea0187dc 100644 --- a/packages/app/e2e/navigation.spec.ts +++ b/packages/app/e2e/app/navigation.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { dirPath, promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { dirPath, promptSelector } from "../utils" test("project route redirects to /session", async ({ page, directory, slug }) => { await page.goto(dirPath(directory)) diff --git a/packages/app/e2e/palette.spec.ts b/packages/app/e2e/app/palette.spec.ts similarity index 82% rename from packages/app/e2e/palette.spec.ts rename to packages/app/e2e/app/palette.spec.ts index 617c55ac1670..264b463bb44a 100644 --- a/packages/app/e2e/palette.spec.ts +++ b/packages/app/e2e/app/palette.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { modKey } from "./utils" +import { test, expect } from "../fixtures" +import { modKey } from "../utils" test("search palette opens and closes", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/server-default.spec.ts b/packages/app/e2e/app/server-default.spec.ts similarity index 95% rename from packages/app/e2e/server-default.spec.ts rename to packages/app/e2e/app/server-default.spec.ts index b6b16f0bccf7..6f44ded1a28c 100644 --- a/packages/app/e2e/server-default.spec.ts +++ b/packages/app/e2e/app/server-default.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { serverName, serverUrl } from "./utils" +import { test, expect } from "../fixtures" +import { serverName, serverUrl } from "../utils" const DEFAULT_SERVER_URL_KEY = "opencode.settings.dat:defaultServerUrl" diff --git a/packages/app/e2e/session.spec.ts b/packages/app/e2e/app/session.spec.ts similarity index 88% rename from packages/app/e2e/session.spec.ts rename to packages/app/e2e/app/session.spec.ts index 19e25a421319..8d605f0c3a78 100644 --- a/packages/app/e2e/session.spec.ts +++ b/packages/app/e2e/app/session.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { promptSelector } from "../utils" test("can open an existing session and type into the prompt", async ({ page, sdk, gotoSession }) => { const title = `e2e smoke ${Date.now()}` diff --git a/packages/app/e2e/titlebar-history.spec.ts b/packages/app/e2e/app/titlebar-history.spec.ts similarity index 95% rename from packages/app/e2e/titlebar-history.spec.ts rename to packages/app/e2e/app/titlebar-history.spec.ts index d4aa605e6dd3..649e5e0dc1cb 100644 --- a/packages/app/e2e/titlebar-history.spec.ts +++ b/packages/app/e2e/app/titlebar-history.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { modKey, promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { modKey, promptSelector } from "../utils" test("titlebar back/forward navigates between sessions", async ({ page, slug, sdk, gotoSession }) => { await page.setViewportSize({ width: 1400, height: 800 }) diff --git a/packages/app/e2e/file-open.spec.ts b/packages/app/e2e/files/file-open.spec.ts similarity index 89% rename from packages/app/e2e/file-open.spec.ts rename to packages/app/e2e/files/file-open.spec.ts index fb7104b6b05d..e384f0b0da2e 100644 --- a/packages/app/e2e/file-open.spec.ts +++ b/packages/app/e2e/files/file-open.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { modKey } from "./utils" +import { test, expect } from "../fixtures" +import { modKey } from "../utils" test("can open a file tab from the search palette", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/file-tree.spec.ts b/packages/app/e2e/files/file-tree.spec.ts similarity index 96% rename from packages/app/e2e/file-tree.spec.ts rename to packages/app/e2e/files/file-tree.spec.ts index 0b04eb2468fc..844da1b329b9 100644 --- a/packages/app/e2e/file-tree.spec.ts +++ b/packages/app/e2e/files/file-tree.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from "./fixtures" +import { test, expect } from "../fixtures" test.skip("file tree can expand folders and open a file", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/file-viewer.spec.ts b/packages/app/e2e/files/file-viewer.spec.ts similarity index 92% rename from packages/app/e2e/file-viewer.spec.ts rename to packages/app/e2e/files/file-viewer.spec.ts index 1e0f8a6f23ab..bed6d1d3690f 100644 --- a/packages/app/e2e/file-viewer.spec.ts +++ b/packages/app/e2e/files/file-viewer.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { modKey } from "./utils" +import { test, expect } from "../fixtures" +import { modKey } from "../utils" test("smoke file viewer renders real file content", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/model-picker.spec.ts b/packages/app/e2e/models/model-picker.spec.ts similarity index 94% rename from packages/app/e2e/model-picker.spec.ts rename to packages/app/e2e/models/model-picker.spec.ts index 9e64b3dfb0a1..a0c70aabef5b 100644 --- a/packages/app/e2e/model-picker.spec.ts +++ b/packages/app/e2e/models/model-picker.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { promptSelector } from "../utils" test("smoke model selection updates prompt footer", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/models-visibility.spec.ts b/packages/app/e2e/models/models-visibility.spec.ts similarity index 96% rename from packages/app/e2e/models-visibility.spec.ts rename to packages/app/e2e/models/models-visibility.spec.ts index 680ba96a3183..0db7580c2095 100644 --- a/packages/app/e2e/models-visibility.spec.ts +++ b/packages/app/e2e/models/models-visibility.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { modKey, promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { modKey, promptSelector } from "../utils" test("hiding a model removes it from the model picker", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/context.spec.ts b/packages/app/e2e/prompt/context.spec.ts similarity index 93% rename from packages/app/e2e/context.spec.ts rename to packages/app/e2e/prompt/context.spec.ts index beabd2eb7dd0..f0f3f073aee7 100644 --- a/packages/app/e2e/context.spec.ts +++ b/packages/app/e2e/prompt/context.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { promptSelector } from "../utils" test("context panel can be opened from the prompt", async ({ page, sdk, gotoSession }) => { const title = `e2e smoke context ${Date.now()}` diff --git a/packages/app/e2e/prompt-mention.spec.ts b/packages/app/e2e/prompt/prompt-mention.spec.ts similarity index 90% rename from packages/app/e2e/prompt-mention.spec.ts rename to packages/app/e2e/prompt/prompt-mention.spec.ts index 113b8465f71a..85acb4c28f46 100644 --- a/packages/app/e2e/prompt-mention.spec.ts +++ b/packages/app/e2e/prompt/prompt-mention.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { promptSelector } from "../utils" test("smoke @mention inserts file pill token", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/prompt-slash-open.spec.ts b/packages/app/e2e/prompt/prompt-slash-open.spec.ts similarity index 87% rename from packages/app/e2e/prompt-slash-open.spec.ts rename to packages/app/e2e/prompt/prompt-slash-open.spec.ts index 3c29d405c18c..3e769e330529 100644 --- a/packages/app/e2e/prompt-slash-open.spec.ts +++ b/packages/app/e2e/prompt/prompt-slash-open.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { promptSelector } from "../utils" test("smoke /open opens file picker dialog", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/prompt.spec.ts b/packages/app/e2e/prompt/prompt.spec.ts similarity index 95% rename from packages/app/e2e/prompt.spec.ts rename to packages/app/e2e/prompt/prompt.spec.ts index 3e5892ce8d59..b58e5e296c0a 100644 --- a/packages/app/e2e/prompt.spec.ts +++ b/packages/app/e2e/prompt/prompt.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { promptSelector } from "../utils" function sessionIDFromUrl(url: string) { const match = /\/session\/([^/?#]+)/.exec(url) diff --git a/packages/app/e2e/settings/settings-language.spec.ts b/packages/app/e2e/settings/settings-language.spec.ts new file mode 100644 index 000000000000..b2ef70bf889c --- /dev/null +++ b/packages/app/e2e/settings/settings-language.spec.ts @@ -0,0 +1,39 @@ +import { test, expect } from "../fixtures" +import { modKey, settingsLanguageSelectSelector } from "../utils" + +test("smoke changing language updates settings labels", async ({ page, gotoSession }) => { + await page.addInitScript(() => { + localStorage.setItem("opencode.global.dat:language", JSON.stringify({ locale: "en" })) + }) + + await gotoSession() + + const dialog = page.getByRole("dialog") + + await page.keyboard.press(`${modKey}+Comma`).catch(() => undefined) + + const opened = await dialog + .waitFor({ state: "visible", timeout: 3000 }) + .then(() => true) + .catch(() => false) + + if (!opened) { + await page.getByRole("button", { name: "Settings" }).first().click() + await expect(dialog).toBeVisible() + } + + const heading = dialog.getByRole("heading", { level: 2 }) + await expect(heading).toHaveText("General") + + const select = dialog.locator(settingsLanguageSelectSelector) + await expect(select).toBeVisible() + await select.locator('[data-slot="select-select-trigger"]').click() + + await page.locator('[data-slot="select-select-item"]').filter({ hasText: "Deutsch" }).click() + + await expect(heading).toHaveText("Allgemein") + + await select.locator('[data-slot="select-select-trigger"]').click() + await page.locator('[data-slot="select-select-item"]').filter({ hasText: "English" }).click() + await expect(heading).toHaveText("General") +}) diff --git a/packages/app/e2e/settings-providers.spec.ts b/packages/app/e2e/settings/settings-providers.spec.ts similarity index 95% rename from packages/app/e2e/settings-providers.spec.ts rename to packages/app/e2e/settings/settings-providers.spec.ts index 326a9fad1d25..5b9325c2ab2f 100644 --- a/packages/app/e2e/settings-providers.spec.ts +++ b/packages/app/e2e/settings/settings-providers.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { modKey, promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { modKey, promptSelector } from "../utils" test("smoke providers settings opens provider selector", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/settings.spec.ts b/packages/app/e2e/settings/settings.spec.ts similarity index 94% rename from packages/app/e2e/settings.spec.ts rename to packages/app/e2e/settings/settings.spec.ts index 09dc942cc92b..293a4ba9aa0b 100644 --- a/packages/app/e2e/settings.spec.ts +++ b/packages/app/e2e/settings/settings.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { modKey } from "./utils" +import { test, expect } from "../fixtures" +import { modKey } from "../utils" test("smoke settings dialog opens, switches tabs, closes", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/sidebar-session-links.spec.ts b/packages/app/e2e/sidebar/sidebar-session-links.spec.ts similarity index 64% rename from packages/app/e2e/sidebar-session-links.spec.ts rename to packages/app/e2e/sidebar/sidebar-session-links.spec.ts index fab64736e26f..8c3f695475fd 100644 --- a/packages/app/e2e/sidebar-session-links.spec.ts +++ b/packages/app/e2e/sidebar/sidebar-session-links.spec.ts @@ -1,33 +1,7 @@ -import { test, expect } from "./fixtures" -import { modKey, promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { modKey, promptSelector } from "../utils" -type Locator = { - first: () => Locator - getAttribute: (name: string) => Promise - scrollIntoViewIfNeeded: () => Promise - click: () => Promise -} - -type Page = { - locator: (selector: string) => Locator - keyboard: { - press: (key: string) => Promise - } -} - -type Fixtures = { - page: Page - slug: string - sdk: { - session: { - create: (input: { title: string }) => Promise<{ data?: { id?: string } }> - delete: (input: { sessionID: string }) => Promise - } - } - gotoSession: (sessionID?: string) => Promise -} - -test("sidebar session links navigate to the selected session", async ({ page, slug, sdk, gotoSession }: Fixtures) => { +test("sidebar session links navigate to the selected session", async ({ page, slug, sdk, gotoSession }) => { const stamp = Date.now() const one = await sdk.session.create({ title: `e2e sidebar nav 1 ${stamp}` }).then((r) => r.data) diff --git a/packages/app/e2e/sidebar.spec.ts b/packages/app/e2e/sidebar/sidebar.spec.ts similarity index 88% rename from packages/app/e2e/sidebar.spec.ts rename to packages/app/e2e/sidebar/sidebar.spec.ts index 925590f51065..ba58b1008f30 100644 --- a/packages/app/e2e/sidebar.spec.ts +++ b/packages/app/e2e/sidebar/sidebar.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { modKey } from "./utils" +import { test, expect } from "../fixtures" +import { modKey } from "../utils" test("sidebar can be collapsed and expanded", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/terminal-init.spec.ts b/packages/app/e2e/terminal/terminal-init.spec.ts similarity index 93% rename from packages/app/e2e/terminal-init.spec.ts rename to packages/app/e2e/terminal/terminal-init.spec.ts index cfde2d019307..6faa73a751f0 100644 --- a/packages/app/e2e/terminal-init.spec.ts +++ b/packages/app/e2e/terminal/terminal-init.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { promptSelector, terminalSelector, terminalToggleKey } from "./utils" +import { test, expect } from "../fixtures" +import { promptSelector, terminalSelector, terminalToggleKey } from "../utils" test("smoke terminal mounts and can create a second tab", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/terminal.spec.ts b/packages/app/e2e/terminal/terminal.spec.ts similarity index 79% rename from packages/app/e2e/terminal.spec.ts rename to packages/app/e2e/terminal/terminal.spec.ts index fc558b632593..aaf5c2d75d06 100644 --- a/packages/app/e2e/terminal.spec.ts +++ b/packages/app/e2e/terminal/terminal.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { terminalSelector, terminalToggleKey } from "./utils" +import { test, expect } from "../fixtures" +import { terminalSelector, terminalToggleKey } from "../utils" test("terminal panel can be toggled", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/utils.ts b/packages/app/e2e/utils.ts index 3de488bd9c22..3dec12592258 100644 --- a/packages/app/e2e/utils.ts +++ b/packages/app/e2e/utils.ts @@ -14,6 +14,8 @@ export const promptSelector = '[data-component="prompt-input"]' export const terminalSelector = '[data-component="terminal"]' export const modelVariantCycleSelector = '[data-action="model-variant-cycle"]' +export const settingsLanguageSelectSelector = '[data-action="settings-language"]' + export function createSdk(directory?: string) { return createOpencodeClient({ baseUrl: serverUrl, directory, throwOnError: true }) } diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 3b08652bbb9f..b26f6ba22969 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -148,6 +148,7 @@ export const SettingsGeneral: Component = () => { description={language.t("settings.general.row.language.description")} >