reigncode-app 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +30 -0
- package/Dockerfile +21 -0
- package/README.md +51 -0
- package/bunfig.toml +3 -0
- package/create-effect-simplification-spec.md +515 -0
- package/e2e/AGENTS.md +226 -0
- package/e2e/actions.ts +1018 -0
- package/e2e/app/home.spec.ts +24 -0
- package/e2e/app/navigation.spec.ts +10 -0
- package/e2e/app/palette.spec.ts +20 -0
- package/e2e/app/server-default.spec.ts +58 -0
- package/e2e/app/session.spec.ts +16 -0
- package/e2e/app/titlebar-history.spec.ts +120 -0
- package/e2e/commands/input-focus.spec.ts +15 -0
- package/e2e/commands/panels.spec.ts +33 -0
- package/e2e/commands/tab-close.spec.ts +32 -0
- package/e2e/files/file-open.spec.ts +31 -0
- package/e2e/files/file-tree.spec.ts +56 -0
- package/e2e/files/file-viewer.spec.ts +156 -0
- package/e2e/fixtures.ts +154 -0
- package/e2e/models/model-picker.spec.ts +48 -0
- package/e2e/models/models-visibility.spec.ts +61 -0
- package/e2e/projects/project-edit.spec.ts +43 -0
- package/e2e/projects/projects-close.spec.ts +54 -0
- package/e2e/projects/projects-switch.spec.ts +116 -0
- package/e2e/projects/workspace-new-session.spec.ts +94 -0
- package/e2e/projects/workspaces.spec.ts +375 -0
- package/e2e/prompt/context.spec.ts +95 -0
- package/e2e/prompt/prompt-async.spec.ts +76 -0
- package/e2e/prompt/prompt-drop-file-uri.spec.ts +22 -0
- package/e2e/prompt/prompt-drop-file.spec.ts +30 -0
- package/e2e/prompt/prompt-history.spec.ts +184 -0
- package/e2e/prompt/prompt-mention.spec.ts +26 -0
- package/e2e/prompt/prompt-multiline.spec.ts +24 -0
- package/e2e/prompt/prompt-shell.spec.ts +62 -0
- package/e2e/prompt/prompt-slash-open.spec.ts +22 -0
- package/e2e/prompt/prompt-slash-share.spec.ts +64 -0
- package/e2e/prompt/prompt-slash-terminal.spec.ts +18 -0
- package/e2e/prompt/prompt.spec.ts +55 -0
- package/e2e/selectors.ts +75 -0
- package/e2e/session/session-child-navigation.spec.ts +37 -0
- package/e2e/session/session-composer-dock.spec.ts +530 -0
- package/e2e/session/session-model-persistence.spec.ts +359 -0
- package/e2e/session/session-review.spec.ts +426 -0
- package/e2e/session/session-undo-redo.spec.ts +233 -0
- package/e2e/session/session.spec.ts +174 -0
- package/e2e/settings/settings-keybinds.spec.ts +389 -0
- package/e2e/settings/settings-models.spec.ts +122 -0
- package/e2e/settings/settings-providers.spec.ts +136 -0
- package/e2e/settings/settings.spec.ts +519 -0
- package/e2e/sidebar/sidebar-popover-actions.spec.ts +118 -0
- package/e2e/sidebar/sidebar-session-links.spec.ts +30 -0
- package/e2e/sidebar/sidebar.spec.ts +40 -0
- package/e2e/status/status-popover.spec.ts +94 -0
- package/e2e/terminal/terminal-init.spec.ts +28 -0
- package/e2e/terminal/terminal-reconnect.spec.ts +46 -0
- package/e2e/terminal/terminal-tabs.spec.ts +168 -0
- package/e2e/terminal/terminal.spec.ts +18 -0
- package/e2e/thinking-level.spec.ts +25 -0
- package/e2e/tsconfig.json +9 -0
- package/e2e/utils.ts +63 -0
- package/happydom.ts +75 -0
- package/index.html +23 -0
- package/package.json +77 -0
- package/playwright.config.ts +45 -0
- package/public/_headers +17 -0
- package/public/oc-theme-preload.js +35 -0
- package/script/e2e-local.ts +180 -0
- package/src/addons/serialize.test.ts +319 -0
- package/src/addons/serialize.ts +634 -0
- package/src/app.tsx +308 -0
- package/src/components/debug-bar.tsx +443 -0
- package/src/components/dialog-connect-provider.tsx +617 -0
- package/src/components/dialog-custom-provider-form.ts +158 -0
- package/src/components/dialog-custom-provider.test.ts +80 -0
- package/src/components/dialog-custom-provider.tsx +329 -0
- package/src/components/dialog-edit-project.tsx +255 -0
- package/src/components/dialog-fork.tsx +108 -0
- package/src/components/dialog-manage-models.tsx +101 -0
- package/src/components/dialog-release-notes.tsx +144 -0
- package/src/components/dialog-select-directory.tsx +392 -0
- package/src/components/dialog-select-file.tsx +466 -0
- package/src/components/dialog-select-mcp.tsx +107 -0
- package/src/components/dialog-select-model-unpaid.tsx +137 -0
- package/src/components/dialog-select-model.tsx +220 -0
- package/src/components/dialog-select-provider.tsx +86 -0
- package/src/components/dialog-select-server.tsx +649 -0
- package/src/components/dialog-settings.tsx +73 -0
- package/src/components/file-tree.test.ts +78 -0
- package/src/components/file-tree.tsx +507 -0
- package/src/components/link.tsx +26 -0
- package/src/components/model-tooltip.tsx +91 -0
- package/src/components/prompt-input/attachments.test.ts +44 -0
- package/src/components/prompt-input/attachments.ts +201 -0
- package/src/components/prompt-input/build-request-parts.test.ts +312 -0
- package/src/components/prompt-input/build-request-parts.ts +175 -0
- package/src/components/prompt-input/context-items.tsx +88 -0
- package/src/components/prompt-input/drag-overlay.tsx +25 -0
- package/src/components/prompt-input/editor-dom.test.ts +99 -0
- package/src/components/prompt-input/editor-dom.ts +148 -0
- package/src/components/prompt-input/files.ts +66 -0
- package/src/components/prompt-input/history.test.ts +153 -0
- package/src/components/prompt-input/history.ts +256 -0
- package/src/components/prompt-input/image-attachments.tsx +58 -0
- package/src/components/prompt-input/paste.ts +24 -0
- package/src/components/prompt-input/placeholder.test.ts +48 -0
- package/src/components/prompt-input/placeholder.ts +15 -0
- package/src/components/prompt-input/slash-popover.tsx +141 -0
- package/src/components/prompt-input/submit.test.ts +346 -0
- package/src/components/prompt-input/submit.ts +579 -0
- package/src/components/prompt-input.tsx +1595 -0
- package/src/components/server/server-row.tsx +130 -0
- package/src/components/session/index.ts +5 -0
- package/src/components/session/session-context-breakdown.test.ts +61 -0
- package/src/components/session/session-context-breakdown.ts +132 -0
- package/src/components/session/session-context-format.ts +20 -0
- package/src/components/session/session-context-metrics.test.ts +101 -0
- package/src/components/session/session-context-metrics.ts +82 -0
- package/src/components/session/session-context-tab.tsx +339 -0
- package/src/components/session/session-header.tsx +486 -0
- package/src/components/session/session-new-view.tsx +91 -0
- package/src/components/session/session-sortable-tab.tsx +70 -0
- package/src/components/session/session-sortable-terminal-tab.tsx +193 -0
- package/src/components/session-context-usage.tsx +122 -0
- package/src/components/settings-general.tsx +585 -0
- package/src/components/settings-keybinds.tsx +453 -0
- package/src/components/settings-list.tsx +5 -0
- package/src/components/settings-models.tsx +137 -0
- package/src/components/settings-providers.tsx +251 -0
- package/src/components/status-popover.tsx +419 -0
- package/src/components/terminal.tsx +653 -0
- package/src/components/titlebar-history.test.ts +63 -0
- package/src/components/titlebar-history.ts +57 -0
- package/src/components/titlebar.tsx +312 -0
- package/src/constants/file-picker.ts +89 -0
- package/src/context/command-keybind.test.ts +69 -0
- package/src/context/command.test.ts +25 -0
- package/src/context/command.tsx +437 -0
- package/src/context/comments.test.ts +186 -0
- package/src/context/comments.tsx +243 -0
- package/src/context/file/content-cache.ts +88 -0
- package/src/context/file/path.test.ts +360 -0
- package/src/context/file/path.ts +151 -0
- package/src/context/file/tree-store.ts +170 -0
- package/src/context/file/types.ts +41 -0
- package/src/context/file/view-cache.ts +146 -0
- package/src/context/file/watcher.test.ts +149 -0
- package/src/context/file/watcher.ts +53 -0
- package/src/context/file-content-eviction-accounting.test.ts +65 -0
- package/src/context/file.tsx +280 -0
- package/src/context/global-sdk.tsx +232 -0
- package/src/context/global-sync/bootstrap.ts +206 -0
- package/src/context/global-sync/child-store.test.ts +38 -0
- package/src/context/global-sync/child-store.ts +281 -0
- package/src/context/global-sync/event-reducer.test.ts +552 -0
- package/src/context/global-sync/event-reducer.ts +359 -0
- package/src/context/global-sync/eviction.ts +28 -0
- package/src/context/global-sync/queue.ts +83 -0
- package/src/context/global-sync/session-cache.test.ts +102 -0
- package/src/context/global-sync/session-cache.ts +62 -0
- package/src/context/global-sync/session-load.ts +25 -0
- package/src/context/global-sync/session-prefetch.test.ts +96 -0
- package/src/context/global-sync/session-prefetch.ts +100 -0
- package/src/context/global-sync/session-trim.test.ts +59 -0
- package/src/context/global-sync/session-trim.ts +56 -0
- package/src/context/global-sync/types.ts +133 -0
- package/src/context/global-sync/utils.ts +25 -0
- package/src/context/global-sync.test.ts +122 -0
- package/src/context/global-sync.tsx +408 -0
- package/src/context/highlights.tsx +233 -0
- package/src/context/language.tsx +248 -0
- package/src/context/layout-scroll.test.ts +64 -0
- package/src/context/layout-scroll.ts +126 -0
- package/src/context/layout.test.ts +69 -0
- package/src/context/layout.tsx +937 -0
- package/src/context/local.tsx +422 -0
- package/src/context/model-variant.test.ts +86 -0
- package/src/context/model-variant.ts +52 -0
- package/src/context/models.tsx +163 -0
- package/src/context/notification.tsx +373 -0
- package/src/context/permission-auto-respond.test.ts +102 -0
- package/src/context/permission-auto-respond.ts +51 -0
- package/src/context/permission.tsx +277 -0
- package/src/context/platform.tsx +99 -0
- package/src/context/prompt.tsx +297 -0
- package/src/context/sdk.tsx +49 -0
- package/src/context/server.tsx +295 -0
- package/src/context/settings.tsx +241 -0
- package/src/context/sync-optimistic.test.ts +123 -0
- package/src/context/sync.tsx +618 -0
- package/src/context/terminal-title.ts +51 -0
- package/src/context/terminal.test.ts +82 -0
- package/src/context/terminal.tsx +437 -0
- package/src/entry.tsx +144 -0
- package/src/env.d.ts +18 -0
- package/src/hooks/use-providers.ts +44 -0
- package/src/i18n/ar.ts +855 -0
- package/src/i18n/br.ts +867 -0
- package/src/i18n/bs.ts +943 -0
- package/src/i18n/da.ts +937 -0
- package/src/i18n/de.ts +879 -0
- package/src/i18n/en.ts +948 -0
- package/src/i18n/es.ts +950 -0
- package/src/i18n/fr.ts +878 -0
- package/src/i18n/ja.ts +861 -0
- package/src/i18n/ko.ts +860 -0
- package/src/i18n/no.ts +944 -0
- package/src/i18n/parity.test.ts +32 -0
- package/src/i18n/pl.ts +865 -0
- package/src/i18n/ru.ts +946 -0
- package/src/i18n/th.ts +933 -0
- package/src/i18n/tr.ts +952 -0
- package/src/i18n/zh.ts +930 -0
- package/src/i18n/zht.ts +925 -0
- package/src/index.css +29 -0
- package/src/index.ts +6 -0
- package/src/pages/directory-layout.tsx +88 -0
- package/src/pages/error.tsx +327 -0
- package/src/pages/home.tsx +131 -0
- package/src/pages/layout/deep-links.ts +50 -0
- package/src/pages/layout/helpers.test.ts +211 -0
- package/src/pages/layout/helpers.ts +98 -0
- package/src/pages/layout/inline-editor.tsx +126 -0
- package/src/pages/layout/sidebar-items.tsx +437 -0
- package/src/pages/layout/sidebar-project.tsx +384 -0
- package/src/pages/layout/sidebar-shell.tsx +125 -0
- package/src/pages/layout/sidebar-workspace.tsx +504 -0
- package/src/pages/layout.tsx +2509 -0
- package/src/pages/session/composer/index.ts +2 -0
- package/src/pages/session/composer/session-composer-region.tsx +255 -0
- package/src/pages/session/composer/session-composer-state.test.ts +128 -0
- package/src/pages/session/composer/session-composer-state.ts +249 -0
- package/src/pages/session/composer/session-followup-dock.tsx +109 -0
- package/src/pages/session/composer/session-permission-dock.tsx +74 -0
- package/src/pages/session/composer/session-question-dock.tsx +449 -0
- package/src/pages/session/composer/session-request-tree.ts +52 -0
- package/src/pages/session/composer/session-revert-dock.tsx +99 -0
- package/src/pages/session/composer/session-todo-dock.tsx +330 -0
- package/src/pages/session/file-tab-scroll.test.ts +40 -0
- package/src/pages/session/file-tab-scroll.ts +67 -0
- package/src/pages/session/file-tabs.tsx +456 -0
- package/src/pages/session/handoff.ts +36 -0
- package/src/pages/session/helpers.test.ts +181 -0
- package/src/pages/session/helpers.ts +198 -0
- package/src/pages/session/message-gesture.test.ts +62 -0
- package/src/pages/session/message-gesture.ts +21 -0
- package/src/pages/session/message-id-from-hash.ts +6 -0
- package/src/pages/session/message-timeline.tsx +1013 -0
- package/src/pages/session/review-tab.tsx +170 -0
- package/src/pages/session/session-layout.ts +20 -0
- package/src/pages/session/session-model-helpers.test.ts +51 -0
- package/src/pages/session/session-model-helpers.ts +16 -0
- package/src/pages/session/session-side-panel.tsx +453 -0
- package/src/pages/session/terminal-label.ts +16 -0
- package/src/pages/session/terminal-panel.test.ts +25 -0
- package/src/pages/session/terminal-panel.tsx +326 -0
- package/src/pages/session/use-session-commands.tsx +495 -0
- package/src/pages/session/use-session-hash-scroll.test.ts +16 -0
- package/src/pages/session/use-session-hash-scroll.ts +197 -0
- package/src/pages/session.tsx +1841 -0
- package/src/sst-env.d.ts +12 -0
- package/src/testing/model-selection.ts +80 -0
- package/src/testing/prompt.ts +56 -0
- package/src/testing/session-composer.ts +84 -0
- package/src/testing/terminal.ts +118 -0
- package/src/theme-preload.test.ts +46 -0
- package/src/utils/agent.ts +23 -0
- package/src/utils/aim.ts +138 -0
- package/src/utils/base64.ts +10 -0
- package/src/utils/comment-note.ts +88 -0
- package/src/utils/id.ts +99 -0
- package/src/utils/notification-click.test.ts +27 -0
- package/src/utils/notification-click.ts +13 -0
- package/src/utils/persist.test.ts +115 -0
- package/src/utils/persist.ts +476 -0
- package/src/utils/prompt.test.ts +44 -0
- package/src/utils/prompt.ts +203 -0
- package/src/utils/runtime-adapters.test.ts +62 -0
- package/src/utils/runtime-adapters.ts +39 -0
- package/src/utils/same.ts +6 -0
- package/src/utils/scoped-cache.test.ts +69 -0
- package/src/utils/scoped-cache.ts +104 -0
- package/src/utils/server-errors.test.ts +131 -0
- package/src/utils/server-errors.ts +80 -0
- package/src/utils/server-health.test.ts +123 -0
- package/src/utils/server-health.ts +91 -0
- package/src/utils/server.ts +22 -0
- package/src/utils/solid-dnd.tsx +49 -0
- package/src/utils/sound.ts +117 -0
- package/src/utils/terminal-writer.test.ts +64 -0
- package/src/utils/terminal-writer.ts +65 -0
- package/src/utils/time.ts +22 -0
- package/src/utils/uuid.test.ts +78 -0
- package/src/utils/uuid.ts +12 -0
- package/src/utils/worktree.test.ts +46 -0
- package/src/utils/worktree.ts +73 -0
- package/sst-env.d.ts +10 -0
- package/tsconfig.json +26 -0
- package/vite.config.ts +15 -0
- package/vite.js +26 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { test, expect } from "../fixtures"
|
|
2
|
+
import { closeDialog, openSettings } from "../actions"
|
|
3
|
+
|
|
4
|
+
test("custom provider form can be filled and validates input", async ({ page, gotoSession }) => {
|
|
5
|
+
await gotoSession()
|
|
6
|
+
|
|
7
|
+
const settings = await openSettings(page)
|
|
8
|
+
await settings.getByRole("tab", { name: "Providers" }).click()
|
|
9
|
+
|
|
10
|
+
const customProviderSection = settings.locator('[data-component="custom-provider-section"]')
|
|
11
|
+
await expect(customProviderSection).toBeVisible()
|
|
12
|
+
|
|
13
|
+
const connectButton = customProviderSection.getByRole("button", { name: "Connect" })
|
|
14
|
+
await connectButton.click()
|
|
15
|
+
|
|
16
|
+
const providerDialog = page.getByRole("dialog").filter({ has: page.getByText("Custom provider") })
|
|
17
|
+
await expect(providerDialog).toBeVisible()
|
|
18
|
+
|
|
19
|
+
await providerDialog.getByLabel("Provider ID").fill("test-provider")
|
|
20
|
+
await providerDialog.getByLabel("Display name").fill("Test Provider")
|
|
21
|
+
await providerDialog.getByLabel("Base URL").fill("http://localhost:9999/fake")
|
|
22
|
+
await providerDialog.getByLabel("API key").fill("fake-key")
|
|
23
|
+
|
|
24
|
+
await providerDialog.getByPlaceholder("model-id").first().fill("test-model")
|
|
25
|
+
await providerDialog.getByPlaceholder("Display Name").first().fill("Test Model")
|
|
26
|
+
|
|
27
|
+
await expect(providerDialog.getByRole("textbox", { name: "Provider ID" })).toHaveValue("test-provider")
|
|
28
|
+
await expect(providerDialog.getByRole("textbox", { name: "Display name" })).toHaveValue("Test Provider")
|
|
29
|
+
await expect(providerDialog.getByRole("textbox", { name: "Base URL" })).toHaveValue("http://localhost:9999/fake")
|
|
30
|
+
await expect(providerDialog.getByRole("textbox", { name: "API key" })).toHaveValue("fake-key")
|
|
31
|
+
await expect(providerDialog.getByPlaceholder("model-id").first()).toHaveValue("test-model")
|
|
32
|
+
await expect(providerDialog.getByPlaceholder("Display Name").first()).toHaveValue("Test Model")
|
|
33
|
+
|
|
34
|
+
await page.keyboard.press("Escape")
|
|
35
|
+
await expect(providerDialog).toHaveCount(0)
|
|
36
|
+
|
|
37
|
+
await closeDialog(page, settings)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test("custom provider form shows validation errors", async ({ page, gotoSession }) => {
|
|
41
|
+
await gotoSession()
|
|
42
|
+
|
|
43
|
+
const settings = await openSettings(page)
|
|
44
|
+
await settings.getByRole("tab", { name: "Providers" }).click()
|
|
45
|
+
|
|
46
|
+
const customProviderSection = settings.locator('[data-component="custom-provider-section"]')
|
|
47
|
+
await customProviderSection.getByRole("button", { name: "Connect" }).click()
|
|
48
|
+
|
|
49
|
+
const providerDialog = page.getByRole("dialog").filter({ has: page.getByText("Custom provider") })
|
|
50
|
+
await expect(providerDialog).toBeVisible()
|
|
51
|
+
|
|
52
|
+
await providerDialog.getByLabel("Provider ID").fill("invalid provider id")
|
|
53
|
+
await providerDialog.getByLabel("Base URL").fill("not-a-url")
|
|
54
|
+
|
|
55
|
+
await providerDialog.getByRole("button", { name: /submit|save/i }).click()
|
|
56
|
+
|
|
57
|
+
await expect(providerDialog.locator('[data-slot="input-error"]').filter({ hasText: /lowercase/i })).toBeVisible()
|
|
58
|
+
await expect(providerDialog.locator('[data-slot="input-error"]').filter({ hasText: /http/i })).toBeVisible()
|
|
59
|
+
|
|
60
|
+
await page.keyboard.press("Escape")
|
|
61
|
+
await expect(providerDialog).toHaveCount(0)
|
|
62
|
+
|
|
63
|
+
await closeDialog(page, settings)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test("custom provider form can add and remove models", async ({ page, gotoSession }) => {
|
|
67
|
+
await gotoSession()
|
|
68
|
+
|
|
69
|
+
const settings = await openSettings(page)
|
|
70
|
+
await settings.getByRole("tab", { name: "Providers" }).click()
|
|
71
|
+
|
|
72
|
+
const customProviderSection = settings.locator('[data-component="custom-provider-section"]')
|
|
73
|
+
await customProviderSection.getByRole("button", { name: "Connect" }).click()
|
|
74
|
+
|
|
75
|
+
const providerDialog = page.getByRole("dialog").filter({ has: page.getByText("Custom provider") })
|
|
76
|
+
await expect(providerDialog).toBeVisible()
|
|
77
|
+
|
|
78
|
+
await providerDialog.getByLabel("Provider ID").fill("multi-model-test")
|
|
79
|
+
await providerDialog.getByLabel("Display name").fill("Multi Model Test")
|
|
80
|
+
await providerDialog.getByLabel("Base URL").fill("http://localhost:9999/multi")
|
|
81
|
+
|
|
82
|
+
await providerDialog.getByPlaceholder("model-id").first().fill("model-1")
|
|
83
|
+
await providerDialog.getByPlaceholder("Display Name").first().fill("Model 1")
|
|
84
|
+
|
|
85
|
+
const idInputsBefore = await providerDialog.getByPlaceholder("model-id").count()
|
|
86
|
+
await providerDialog.getByRole("button", { name: "Add model" }).click()
|
|
87
|
+
const idInputsAfter = await providerDialog.getByPlaceholder("model-id").count()
|
|
88
|
+
expect(idInputsAfter).toBe(idInputsBefore + 1)
|
|
89
|
+
|
|
90
|
+
await providerDialog.getByPlaceholder("model-id").nth(1).fill("model-2")
|
|
91
|
+
await providerDialog.getByPlaceholder("Display Name").nth(1).fill("Model 2")
|
|
92
|
+
|
|
93
|
+
await expect(providerDialog.getByPlaceholder("model-id").nth(1)).toHaveValue("model-2")
|
|
94
|
+
await expect(providerDialog.getByPlaceholder("Display Name").nth(1)).toHaveValue("Model 2")
|
|
95
|
+
|
|
96
|
+
await page.keyboard.press("Escape")
|
|
97
|
+
await expect(providerDialog).toHaveCount(0)
|
|
98
|
+
|
|
99
|
+
await closeDialog(page, settings)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test("custom provider form can add and remove headers", async ({ page, gotoSession }) => {
|
|
103
|
+
await gotoSession()
|
|
104
|
+
|
|
105
|
+
const settings = await openSettings(page)
|
|
106
|
+
await settings.getByRole("tab", { name: "Providers" }).click()
|
|
107
|
+
|
|
108
|
+
const customProviderSection = settings.locator('[data-component="custom-provider-section"]')
|
|
109
|
+
await customProviderSection.getByRole("button", { name: "Connect" }).click()
|
|
110
|
+
|
|
111
|
+
const providerDialog = page.getByRole("dialog").filter({ has: page.getByText("Custom provider") })
|
|
112
|
+
await expect(providerDialog).toBeVisible()
|
|
113
|
+
|
|
114
|
+
await providerDialog.getByLabel("Provider ID").fill("header-test")
|
|
115
|
+
await providerDialog.getByLabel("Display name").fill("Header Test")
|
|
116
|
+
await providerDialog.getByLabel("Base URL").fill("http://localhost:9999/headers")
|
|
117
|
+
|
|
118
|
+
await providerDialog.getByPlaceholder("model-id").first().fill("model-x")
|
|
119
|
+
await providerDialog.getByPlaceholder("Display Name").first().fill("Model X")
|
|
120
|
+
|
|
121
|
+
const headerInputsBefore = await providerDialog.getByPlaceholder("Header-Name").count()
|
|
122
|
+
await providerDialog.getByRole("button", { name: "Add header" }).click()
|
|
123
|
+
const headerInputsAfter = await providerDialog.getByPlaceholder("Header-Name").count()
|
|
124
|
+
expect(headerInputsAfter).toBe(headerInputsBefore + 1)
|
|
125
|
+
|
|
126
|
+
await providerDialog.getByPlaceholder("Header-Name").first().fill("Authorization")
|
|
127
|
+
await providerDialog.getByPlaceholder("value").first().fill("Bearer token123")
|
|
128
|
+
|
|
129
|
+
await expect(providerDialog.getByPlaceholder("Header-Name").first()).toHaveValue("Authorization")
|
|
130
|
+
await expect(providerDialog.getByPlaceholder("value").first()).toHaveValue("Bearer token123")
|
|
131
|
+
|
|
132
|
+
await page.keyboard.press("Escape")
|
|
133
|
+
await expect(providerDialog).toHaveCount(0)
|
|
134
|
+
|
|
135
|
+
await closeDialog(page, settings)
|
|
136
|
+
})
|
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
import { test, expect, settingsKey } from "../fixtures"
|
|
2
|
+
import { closeDialog, openSettings } from "../actions"
|
|
3
|
+
import {
|
|
4
|
+
settingsColorSchemeSelector,
|
|
5
|
+
settingsFontSelector,
|
|
6
|
+
settingsLanguageSelectSelector,
|
|
7
|
+
settingsNotificationsAgentSelector,
|
|
8
|
+
settingsNotificationsErrorsSelector,
|
|
9
|
+
settingsNotificationsPermissionsSelector,
|
|
10
|
+
settingsReleaseNotesSelector,
|
|
11
|
+
settingsSoundsAgentSelector,
|
|
12
|
+
settingsSoundsErrorsSelector,
|
|
13
|
+
settingsSoundsPermissionsSelector,
|
|
14
|
+
settingsThemeSelector,
|
|
15
|
+
settingsUpdatesStartupSelector,
|
|
16
|
+
} from "../selectors"
|
|
17
|
+
|
|
18
|
+
test("smoke settings dialog opens, switches tabs, closes", async ({ page, gotoSession }) => {
|
|
19
|
+
await gotoSession()
|
|
20
|
+
|
|
21
|
+
const dialog = await openSettings(page)
|
|
22
|
+
|
|
23
|
+
await dialog.getByRole("tab", { name: "Shortcuts" }).click()
|
|
24
|
+
await expect(dialog.getByRole("button", { name: "Reset to defaults" })).toBeVisible()
|
|
25
|
+
await expect(dialog.getByPlaceholder("Search shortcuts")).toBeVisible()
|
|
26
|
+
|
|
27
|
+
await closeDialog(page, dialog)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test("changing language updates settings labels", async ({ page, gotoSession }) => {
|
|
31
|
+
await page.addInitScript(() => {
|
|
32
|
+
localStorage.setItem("opencode.global.dat:language", JSON.stringify({ locale: "en" }))
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
await gotoSession()
|
|
36
|
+
|
|
37
|
+
const dialog = await openSettings(page)
|
|
38
|
+
|
|
39
|
+
const heading = dialog.getByRole("heading", { level: 2 })
|
|
40
|
+
await expect(heading).toHaveText("General")
|
|
41
|
+
|
|
42
|
+
const select = dialog.locator(settingsLanguageSelectSelector)
|
|
43
|
+
await expect(select).toBeVisible()
|
|
44
|
+
await select.locator('[data-slot="select-select-trigger"]').click()
|
|
45
|
+
|
|
46
|
+
await page.locator('[data-slot="select-select-item"]').filter({ hasText: "Deutsch" }).click()
|
|
47
|
+
|
|
48
|
+
await expect(heading).toHaveText("Allgemein")
|
|
49
|
+
|
|
50
|
+
await select.locator('[data-slot="select-select-trigger"]').click()
|
|
51
|
+
await page.locator('[data-slot="select-select-item"]').filter({ hasText: "English" }).click()
|
|
52
|
+
await expect(heading).toHaveText("General")
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test("changing color scheme persists in localStorage", async ({ page, gotoSession }) => {
|
|
56
|
+
await gotoSession()
|
|
57
|
+
|
|
58
|
+
const dialog = await openSettings(page)
|
|
59
|
+
const select = dialog.locator(settingsColorSchemeSelector)
|
|
60
|
+
await expect(select).toBeVisible()
|
|
61
|
+
|
|
62
|
+
await select.locator('[data-slot="select-select-trigger"]').click()
|
|
63
|
+
await page.locator('[data-slot="select-select-item"]').filter({ hasText: "Dark" }).click()
|
|
64
|
+
|
|
65
|
+
const colorScheme = await page.evaluate(() => {
|
|
66
|
+
return document.documentElement.getAttribute("data-color-scheme")
|
|
67
|
+
})
|
|
68
|
+
expect(colorScheme).toBe("dark")
|
|
69
|
+
|
|
70
|
+
await select.locator('[data-slot="select-select-trigger"]').click()
|
|
71
|
+
await page.locator('[data-slot="select-select-item"]').filter({ hasText: "Light" }).click()
|
|
72
|
+
|
|
73
|
+
const lightColorScheme = await page.evaluate(() => {
|
|
74
|
+
return document.documentElement.getAttribute("data-color-scheme")
|
|
75
|
+
})
|
|
76
|
+
expect(lightColorScheme).toBe("light")
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test("changing theme persists in localStorage", async ({ page, gotoSession }) => {
|
|
80
|
+
await gotoSession()
|
|
81
|
+
|
|
82
|
+
const dialog = await openSettings(page)
|
|
83
|
+
const select = dialog.locator(settingsThemeSelector)
|
|
84
|
+
await expect(select).toBeVisible()
|
|
85
|
+
|
|
86
|
+
const currentThemeId = await page.evaluate(() => {
|
|
87
|
+
return document.documentElement.getAttribute("data-theme")
|
|
88
|
+
})
|
|
89
|
+
const currentTheme = (await select.locator('[data-slot="select-select-trigger-value"]').textContent())?.trim() ?? ""
|
|
90
|
+
|
|
91
|
+
await select.locator('[data-slot="select-select-trigger"]').click()
|
|
92
|
+
|
|
93
|
+
const items = page.locator('[data-slot="select-select-item"]')
|
|
94
|
+
const count = await items.count()
|
|
95
|
+
expect(count).toBeGreaterThan(1)
|
|
96
|
+
|
|
97
|
+
const nextTheme = (await items.locator('[data-slot="select-select-item-label"]').allTextContents())
|
|
98
|
+
.map((x) => x.trim())
|
|
99
|
+
.find((x) => x && x !== currentTheme)
|
|
100
|
+
expect(nextTheme).toBeTruthy()
|
|
101
|
+
|
|
102
|
+
await items.filter({ hasText: nextTheme! }).first().click()
|
|
103
|
+
|
|
104
|
+
await page.keyboard.press("Escape")
|
|
105
|
+
|
|
106
|
+
const storedThemeId = await page.evaluate(() => {
|
|
107
|
+
return localStorage.getItem("opencode-theme-id")
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
expect(storedThemeId).not.toBeNull()
|
|
111
|
+
expect(storedThemeId).not.toBe(currentThemeId)
|
|
112
|
+
|
|
113
|
+
const dataTheme = await page.evaluate(() => {
|
|
114
|
+
return document.documentElement.getAttribute("data-theme")
|
|
115
|
+
})
|
|
116
|
+
expect(dataTheme).toBe(storedThemeId)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test("legacy oc-1 theme migrates to oc-2", async ({ page, gotoSession }) => {
|
|
120
|
+
await page.addInitScript(() => {
|
|
121
|
+
localStorage.setItem("opencode-theme-id", "oc-1")
|
|
122
|
+
localStorage.setItem("opencode-theme-css-light", "--background-base:#fff;")
|
|
123
|
+
localStorage.setItem("opencode-theme-css-dark", "--background-base:#000;")
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
await gotoSession()
|
|
127
|
+
|
|
128
|
+
await expect(page.locator("html")).toHaveAttribute("data-theme", "oc-2")
|
|
129
|
+
|
|
130
|
+
await expect
|
|
131
|
+
.poll(async () => {
|
|
132
|
+
return await page.evaluate(() => {
|
|
133
|
+
return localStorage.getItem("opencode-theme-id")
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
.toBe("oc-2")
|
|
137
|
+
|
|
138
|
+
await expect
|
|
139
|
+
.poll(async () => {
|
|
140
|
+
return await page.evaluate(() => {
|
|
141
|
+
return localStorage.getItem("opencode-theme-css-light")
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
.toBeNull()
|
|
145
|
+
|
|
146
|
+
await expect
|
|
147
|
+
.poll(async () => {
|
|
148
|
+
return await page.evaluate(() => {
|
|
149
|
+
return localStorage.getItem("opencode-theme-css-dark")
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
.toBeNull()
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test("changing font persists in localStorage and updates CSS variable", async ({ page, gotoSession }) => {
|
|
156
|
+
await gotoSession()
|
|
157
|
+
|
|
158
|
+
const dialog = await openSettings(page)
|
|
159
|
+
const select = dialog.locator(settingsFontSelector)
|
|
160
|
+
await expect(select).toBeVisible()
|
|
161
|
+
|
|
162
|
+
const initialFontFamily = await page.evaluate(() => {
|
|
163
|
+
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono")
|
|
164
|
+
})
|
|
165
|
+
expect(initialFontFamily).toContain("IBM Plex Mono")
|
|
166
|
+
|
|
167
|
+
await select.locator('[data-slot="select-select-trigger"]').click()
|
|
168
|
+
|
|
169
|
+
const items = page.locator('[data-slot="select-select-item"]')
|
|
170
|
+
await items.nth(2).click()
|
|
171
|
+
|
|
172
|
+
await page.waitForTimeout(100)
|
|
173
|
+
|
|
174
|
+
const stored = await page.evaluate((key) => {
|
|
175
|
+
const raw = localStorage.getItem(key)
|
|
176
|
+
return raw ? JSON.parse(raw) : null
|
|
177
|
+
}, settingsKey)
|
|
178
|
+
|
|
179
|
+
expect(stored?.appearance?.font).not.toBe("ibm-plex-mono")
|
|
180
|
+
|
|
181
|
+
const newFontFamily = await page.evaluate(() => {
|
|
182
|
+
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono")
|
|
183
|
+
})
|
|
184
|
+
expect(newFontFamily).not.toBe(initialFontFamily)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test("color scheme and font rehydrate after reload", async ({ page, gotoSession }) => {
|
|
188
|
+
await gotoSession()
|
|
189
|
+
|
|
190
|
+
const dialog = await openSettings(page)
|
|
191
|
+
|
|
192
|
+
const colorSchemeSelect = dialog.locator(settingsColorSchemeSelector)
|
|
193
|
+
await expect(colorSchemeSelect).toBeVisible()
|
|
194
|
+
await colorSchemeSelect.locator('[data-slot="select-select-trigger"]').click()
|
|
195
|
+
await page.locator('[data-slot="select-select-item"]').filter({ hasText: "Dark" }).click()
|
|
196
|
+
await expect(page.locator("html")).toHaveAttribute("data-color-scheme", "dark")
|
|
197
|
+
|
|
198
|
+
const fontSelect = dialog.locator(settingsFontSelector)
|
|
199
|
+
await expect(fontSelect).toBeVisible()
|
|
200
|
+
|
|
201
|
+
const initialFontFamily = await page.evaluate(() => {
|
|
202
|
+
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim()
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const initialSettings = await page.evaluate((key) => {
|
|
206
|
+
const raw = localStorage.getItem(key)
|
|
207
|
+
return raw ? JSON.parse(raw) : null
|
|
208
|
+
}, settingsKey)
|
|
209
|
+
|
|
210
|
+
const currentFont =
|
|
211
|
+
(await fontSelect.locator('[data-slot="select-select-trigger-value"]').textContent())?.trim() ?? ""
|
|
212
|
+
await fontSelect.locator('[data-slot="select-select-trigger"]').click()
|
|
213
|
+
|
|
214
|
+
const fontItems = page.locator('[data-slot="select-select-item"]')
|
|
215
|
+
expect(await fontItems.count()).toBeGreaterThan(1)
|
|
216
|
+
|
|
217
|
+
if (currentFont) {
|
|
218
|
+
await fontItems.filter({ hasNotText: currentFont }).first().click()
|
|
219
|
+
}
|
|
220
|
+
if (!currentFont) {
|
|
221
|
+
await fontItems.nth(1).click()
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
await expect
|
|
225
|
+
.poll(async () => {
|
|
226
|
+
return await page.evaluate((key) => {
|
|
227
|
+
const raw = localStorage.getItem(key)
|
|
228
|
+
return raw ? JSON.parse(raw) : null
|
|
229
|
+
}, settingsKey)
|
|
230
|
+
})
|
|
231
|
+
.toMatchObject({
|
|
232
|
+
appearance: {
|
|
233
|
+
font: expect.any(String),
|
|
234
|
+
},
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
const updatedSettings = await page.evaluate((key) => {
|
|
238
|
+
const raw = localStorage.getItem(key)
|
|
239
|
+
return raw ? JSON.parse(raw) : null
|
|
240
|
+
}, settingsKey)
|
|
241
|
+
|
|
242
|
+
const updatedFontFamily = await page.evaluate(() => {
|
|
243
|
+
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim()
|
|
244
|
+
})
|
|
245
|
+
expect(updatedFontFamily).not.toBe(initialFontFamily)
|
|
246
|
+
expect(updatedSettings?.appearance?.font).not.toBe(initialSettings?.appearance?.font)
|
|
247
|
+
|
|
248
|
+
await closeDialog(page, dialog)
|
|
249
|
+
await page.reload()
|
|
250
|
+
|
|
251
|
+
await expect(page.locator("html")).toHaveAttribute("data-color-scheme", "dark")
|
|
252
|
+
|
|
253
|
+
await expect
|
|
254
|
+
.poll(async () => {
|
|
255
|
+
return await page.evaluate((key) => {
|
|
256
|
+
const raw = localStorage.getItem(key)
|
|
257
|
+
return raw ? JSON.parse(raw) : null
|
|
258
|
+
}, settingsKey)
|
|
259
|
+
})
|
|
260
|
+
.toMatchObject({
|
|
261
|
+
appearance: {
|
|
262
|
+
font: updatedSettings?.appearance?.font,
|
|
263
|
+
},
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
const rehydratedSettings = await page.evaluate((key) => {
|
|
267
|
+
const raw = localStorage.getItem(key)
|
|
268
|
+
return raw ? JSON.parse(raw) : null
|
|
269
|
+
}, settingsKey)
|
|
270
|
+
|
|
271
|
+
await expect
|
|
272
|
+
.poll(async () => {
|
|
273
|
+
return await page.evaluate(() => {
|
|
274
|
+
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim()
|
|
275
|
+
})
|
|
276
|
+
})
|
|
277
|
+
.not.toBe(initialFontFamily)
|
|
278
|
+
|
|
279
|
+
const rehydratedFontFamily = await page.evaluate(() => {
|
|
280
|
+
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim()
|
|
281
|
+
})
|
|
282
|
+
expect(rehydratedFontFamily).not.toBe(initialFontFamily)
|
|
283
|
+
expect(rehydratedSettings?.appearance?.font).toBe(updatedSettings?.appearance?.font)
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
test("toggling notification agent switch updates localStorage", async ({ page, gotoSession }) => {
|
|
287
|
+
await gotoSession()
|
|
288
|
+
|
|
289
|
+
const dialog = await openSettings(page)
|
|
290
|
+
const switchContainer = dialog.locator(settingsNotificationsAgentSelector)
|
|
291
|
+
await expect(switchContainer).toBeVisible()
|
|
292
|
+
|
|
293
|
+
const toggleInput = switchContainer.locator('[data-slot="switch-input"]')
|
|
294
|
+
const initialState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
|
|
295
|
+
expect(initialState).toBe(true)
|
|
296
|
+
|
|
297
|
+
await switchContainer.locator('[data-slot="switch-control"]').click()
|
|
298
|
+
await page.waitForTimeout(100)
|
|
299
|
+
|
|
300
|
+
const newState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
|
|
301
|
+
expect(newState).toBe(false)
|
|
302
|
+
|
|
303
|
+
const stored = await page.evaluate((key) => {
|
|
304
|
+
const raw = localStorage.getItem(key)
|
|
305
|
+
return raw ? JSON.parse(raw) : null
|
|
306
|
+
}, settingsKey)
|
|
307
|
+
|
|
308
|
+
expect(stored?.notifications?.agent).toBe(false)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
test("toggling notification permissions switch updates localStorage", async ({ page, gotoSession }) => {
|
|
312
|
+
await gotoSession()
|
|
313
|
+
|
|
314
|
+
const dialog = await openSettings(page)
|
|
315
|
+
const switchContainer = dialog.locator(settingsNotificationsPermissionsSelector)
|
|
316
|
+
await expect(switchContainer).toBeVisible()
|
|
317
|
+
|
|
318
|
+
const toggleInput = switchContainer.locator('[data-slot="switch-input"]')
|
|
319
|
+
const initialState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
|
|
320
|
+
expect(initialState).toBe(true)
|
|
321
|
+
|
|
322
|
+
await switchContainer.locator('[data-slot="switch-control"]').click()
|
|
323
|
+
await page.waitForTimeout(100)
|
|
324
|
+
|
|
325
|
+
const newState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
|
|
326
|
+
expect(newState).toBe(false)
|
|
327
|
+
|
|
328
|
+
const stored = await page.evaluate((key) => {
|
|
329
|
+
const raw = localStorage.getItem(key)
|
|
330
|
+
return raw ? JSON.parse(raw) : null
|
|
331
|
+
}, settingsKey)
|
|
332
|
+
|
|
333
|
+
expect(stored?.notifications?.permissions).toBe(false)
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
test("toggling notification errors switch updates localStorage", async ({ page, gotoSession }) => {
|
|
337
|
+
await gotoSession()
|
|
338
|
+
|
|
339
|
+
const dialog = await openSettings(page)
|
|
340
|
+
const switchContainer = dialog.locator(settingsNotificationsErrorsSelector)
|
|
341
|
+
await expect(switchContainer).toBeVisible()
|
|
342
|
+
|
|
343
|
+
const toggleInput = switchContainer.locator('[data-slot="switch-input"]')
|
|
344
|
+
const initialState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
|
|
345
|
+
expect(initialState).toBe(false)
|
|
346
|
+
|
|
347
|
+
await switchContainer.locator('[data-slot="switch-control"]').click()
|
|
348
|
+
await page.waitForTimeout(100)
|
|
349
|
+
|
|
350
|
+
const newState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
|
|
351
|
+
expect(newState).toBe(true)
|
|
352
|
+
|
|
353
|
+
const stored = await page.evaluate((key) => {
|
|
354
|
+
const raw = localStorage.getItem(key)
|
|
355
|
+
return raw ? JSON.parse(raw) : null
|
|
356
|
+
}, settingsKey)
|
|
357
|
+
|
|
358
|
+
expect(stored?.notifications?.errors).toBe(true)
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
test("changing sound agent selection persists in localStorage", async ({ page, gotoSession }) => {
|
|
362
|
+
await gotoSession()
|
|
363
|
+
|
|
364
|
+
const dialog = await openSettings(page)
|
|
365
|
+
const select = dialog.locator(settingsSoundsAgentSelector)
|
|
366
|
+
await expect(select).toBeVisible()
|
|
367
|
+
|
|
368
|
+
await select.locator('[data-slot="select-select-trigger"]').click()
|
|
369
|
+
|
|
370
|
+
const items = page.locator('[data-slot="select-select-item"]')
|
|
371
|
+
await items.nth(2).click()
|
|
372
|
+
|
|
373
|
+
const stored = await page.evaluate((key) => {
|
|
374
|
+
const raw = localStorage.getItem(key)
|
|
375
|
+
return raw ? JSON.parse(raw) : null
|
|
376
|
+
}, settingsKey)
|
|
377
|
+
|
|
378
|
+
expect(stored?.sounds?.agent).not.toBe("staplebops-01")
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
test("selecting none disables agent sound", async ({ page, gotoSession }) => {
|
|
382
|
+
await gotoSession()
|
|
383
|
+
|
|
384
|
+
const dialog = await openSettings(page)
|
|
385
|
+
const select = dialog.locator(settingsSoundsAgentSelector)
|
|
386
|
+
const trigger = select.locator('[data-slot="select-select-trigger"]')
|
|
387
|
+
await expect(select).toBeVisible()
|
|
388
|
+
await expect(trigger).toBeEnabled()
|
|
389
|
+
|
|
390
|
+
await trigger.click()
|
|
391
|
+
const items = page.locator('[data-slot="select-select-item"]')
|
|
392
|
+
await expect(items.first()).toBeVisible()
|
|
393
|
+
await items.first().click()
|
|
394
|
+
|
|
395
|
+
const stored = await page.evaluate((key) => {
|
|
396
|
+
const raw = localStorage.getItem(key)
|
|
397
|
+
return raw ? JSON.parse(raw) : null
|
|
398
|
+
}, settingsKey)
|
|
399
|
+
|
|
400
|
+
expect(stored?.sounds?.agentEnabled).toBe(false)
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
test("changing permissions and errors sounds updates localStorage", async ({ page, gotoSession }) => {
|
|
404
|
+
await gotoSession()
|
|
405
|
+
|
|
406
|
+
const dialog = await openSettings(page)
|
|
407
|
+
const permissionsSelect = dialog.locator(settingsSoundsPermissionsSelector)
|
|
408
|
+
const errorsSelect = dialog.locator(settingsSoundsErrorsSelector)
|
|
409
|
+
await expect(permissionsSelect).toBeVisible()
|
|
410
|
+
await expect(errorsSelect).toBeVisible()
|
|
411
|
+
|
|
412
|
+
const initial = await page.evaluate((key) => {
|
|
413
|
+
const raw = localStorage.getItem(key)
|
|
414
|
+
return raw ? JSON.parse(raw) : null
|
|
415
|
+
}, settingsKey)
|
|
416
|
+
|
|
417
|
+
const permissionsCurrent =
|
|
418
|
+
(await permissionsSelect.locator('[data-slot="select-select-trigger-value"]').textContent())?.trim() ?? ""
|
|
419
|
+
await permissionsSelect.locator('[data-slot="select-select-trigger"]').click()
|
|
420
|
+
const permissionItems = page.locator('[data-slot="select-select-item"]')
|
|
421
|
+
expect(await permissionItems.count()).toBeGreaterThan(1)
|
|
422
|
+
if (permissionsCurrent) {
|
|
423
|
+
await permissionItems.filter({ hasNotText: permissionsCurrent }).first().click()
|
|
424
|
+
}
|
|
425
|
+
if (!permissionsCurrent) {
|
|
426
|
+
await permissionItems.nth(1).click()
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const errorsCurrent =
|
|
430
|
+
(await errorsSelect.locator('[data-slot="select-select-trigger-value"]').textContent())?.trim() ?? ""
|
|
431
|
+
await errorsSelect.locator('[data-slot="select-select-trigger"]').click()
|
|
432
|
+
const errorItems = page.locator('[data-slot="select-select-item"]')
|
|
433
|
+
expect(await errorItems.count()).toBeGreaterThan(1)
|
|
434
|
+
if (errorsCurrent) {
|
|
435
|
+
await errorItems.filter({ hasNotText: errorsCurrent }).first().click()
|
|
436
|
+
}
|
|
437
|
+
if (!errorsCurrent) {
|
|
438
|
+
await errorItems.nth(1).click()
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
await expect
|
|
442
|
+
.poll(async () => {
|
|
443
|
+
return await page.evaluate((key) => {
|
|
444
|
+
const raw = localStorage.getItem(key)
|
|
445
|
+
return raw ? JSON.parse(raw) : null
|
|
446
|
+
}, settingsKey)
|
|
447
|
+
})
|
|
448
|
+
.toMatchObject({
|
|
449
|
+
sounds: {
|
|
450
|
+
permissions: expect.any(String),
|
|
451
|
+
errors: expect.any(String),
|
|
452
|
+
},
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
const stored = await page.evaluate((key) => {
|
|
456
|
+
const raw = localStorage.getItem(key)
|
|
457
|
+
return raw ? JSON.parse(raw) : null
|
|
458
|
+
}, settingsKey)
|
|
459
|
+
|
|
460
|
+
expect(stored?.sounds?.permissions).not.toBe(initial?.sounds?.permissions)
|
|
461
|
+
expect(stored?.sounds?.errors).not.toBe(initial?.sounds?.errors)
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
test("toggling updates startup switch updates localStorage", async ({ page, gotoSession }) => {
|
|
465
|
+
await gotoSession()
|
|
466
|
+
|
|
467
|
+
const dialog = await openSettings(page)
|
|
468
|
+
const switchContainer = dialog.locator(settingsUpdatesStartupSelector)
|
|
469
|
+
await expect(switchContainer).toBeVisible()
|
|
470
|
+
|
|
471
|
+
const toggleInput = switchContainer.locator('[data-slot="switch-input"]')
|
|
472
|
+
|
|
473
|
+
const isDisabled = await toggleInput.evaluate((el: HTMLInputElement) => el.disabled)
|
|
474
|
+
if (isDisabled) {
|
|
475
|
+
test.skip()
|
|
476
|
+
return
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const initialState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
|
|
480
|
+
expect(initialState).toBe(true)
|
|
481
|
+
|
|
482
|
+
await switchContainer.locator('[data-slot="switch-control"]').click()
|
|
483
|
+
await page.waitForTimeout(100)
|
|
484
|
+
|
|
485
|
+
const newState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
|
|
486
|
+
expect(newState).toBe(false)
|
|
487
|
+
|
|
488
|
+
const stored = await page.evaluate((key) => {
|
|
489
|
+
const raw = localStorage.getItem(key)
|
|
490
|
+
return raw ? JSON.parse(raw) : null
|
|
491
|
+
}, settingsKey)
|
|
492
|
+
|
|
493
|
+
expect(stored?.updates?.startup).toBe(false)
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
test("toggling release notes switch updates localStorage", async ({ page, gotoSession }) => {
|
|
497
|
+
await gotoSession()
|
|
498
|
+
|
|
499
|
+
const dialog = await openSettings(page)
|
|
500
|
+
const switchContainer = dialog.locator(settingsReleaseNotesSelector)
|
|
501
|
+
await expect(switchContainer).toBeVisible()
|
|
502
|
+
|
|
503
|
+
const toggleInput = switchContainer.locator('[data-slot="switch-input"]')
|
|
504
|
+
const initialState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
|
|
505
|
+
expect(initialState).toBe(true)
|
|
506
|
+
|
|
507
|
+
await switchContainer.locator('[data-slot="switch-control"]').click()
|
|
508
|
+
await page.waitForTimeout(100)
|
|
509
|
+
|
|
510
|
+
const newState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
|
|
511
|
+
expect(newState).toBe(false)
|
|
512
|
+
|
|
513
|
+
const stored = await page.evaluate((key) => {
|
|
514
|
+
const raw = localStorage.getItem(key)
|
|
515
|
+
return raw ? JSON.parse(raw) : null
|
|
516
|
+
}, settingsKey)
|
|
517
|
+
|
|
518
|
+
expect(stored?.general?.releaseNotes).toBe(false)
|
|
519
|
+
})
|