summaryrefslogtreecommitdiffhomepage
path: root/src/features/chat/ui.test.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-08-12 22:01:59 +0900
committerAdam Malczewski <[email protected]>2026-08-12 22:01:59 +0900
commit5f10a40b26dbfdc10b2c02cdf23ec3a7edfa9443 (patch)
tree2f1d5ee2674d1dcc9909e89f06a82a7b10b4191a /src/features/chat/ui.test.ts
parent774519e052fd869952cc5e05aca159e4bf978fa1 (diff)
downloaddispatch-web-5f10a40b26dbfdc10b2c02cdf23ec3a7edfa9443.tar.gz
dispatch-web-5f10a40b26dbfdc10b2c02cdf23ec3a7edfa9443.zip
feat(models): numbered OpenCode credential selection, labels, migration
Adopt the numbered OPENCODE_API_KEY_N scheme in the model picker and concurrency surfaces. - model-select: numeric sort of opencode-<n> keys (#2 before #10); friendly OpenCode #N labels (raw opencode-N/<model> value still selected/persisted); migrateLegacyModelName rewrites opencode/<model> -> opencode-1/<model>. - ModelSelector: renders friendly key labels; surfaces a disappeared saved credential as UNAVAILABLE (disabled option + notice) without silently switching to another model. - app store: DEFAULT_MODEL -> opencode-1/...; catalogs no longer auto-fallback the active model when the selected credential is absent; boot restore migrates persisted legacy opencode/<model> tab models idempotently. - concurrency: providerLabel renders opencode-<n> as OpenCode #<n> in the limit rows and Add dropdown (raw provider id preserved on the wire). - Tests: parsing/sorting/labels/migration, unavailable-state selector behavior, no-silent-fallback, and concurrency provider label; fixtures updated to the numbered scheme. Verification: bun install, bun run typecheck (0 errors), bun run test (1179 passed), bun run check (clean, exit 0), bun run build (vite OK).
Diffstat (limited to 'src/features/chat/ui.test.ts')
-rw-r--r--src/features/chat/ui.test.ts37
1 files changed, 37 insertions, 0 deletions
diff --git a/src/features/chat/ui.test.ts b/src/features/chat/ui.test.ts
index f4006f7..aa43e04 100644
--- a/src/features/chat/ui.test.ts
+++ b/src/features/chat/ui.test.ts
@@ -1089,6 +1089,43 @@ describe("ModelSelector", () => {
});
expect(screen.getByText(/auto-described/i)).toBeInTheDocument();
});
+
+ it("labels numbered OpenCode keys as OpenCode #N and sorts them numerically", () => {
+ const models = ["opencode-10/m1", "opencode-2/m2", "opencode-1/m3"];
+ const onSelect = vi.fn();
+ render(ModelSelector, {
+ props: { models, selected: "opencode-2/m2", onSelect },
+ });
+
+ const keySelect = screen.getByRole("combobox", { name: "Key selector" });
+ const options = within(keySelect).getAllByRole("option");
+ // Numeric order: #1 < #2 < #10, and values stay RAW (opencode-N).
+ expect(options.map((o) => (o as HTMLOptionElement).value)).toEqual([
+ "opencode-1",
+ "opencode-2",
+ "opencode-10",
+ ]);
+ expect(options[0]?.textContent).toContain("OpenCode #1");
+ expect(options[1]?.textContent).toContain("OpenCode #2");
+ expect(options[2]?.textContent).toContain("OpenCode #10");
+ });
+
+ it("shows a disappeared saved credential as unavailable without silently falling back", () => {
+ // Saved selection references opencode-5, but the catalog no longer has it.
+ const models = ["opencode-1/deepseek-v4-flash"];
+ const onSelect = vi.fn();
+ render(ModelSelector, {
+ props: { models, selected: "opencode-5/deepseek-v4-flash", onSelect },
+ });
+
+ // The unavailable key is surfaced as an explicit (disabled) option + notice.
+ const keySelect = screen.getByRole("combobox", { name: "Key selector" });
+ const unavailable = within(keySelect).getByText(/OpenCode #5 \(unavailable\)/i);
+ expect(unavailable).toBeInTheDocument();
+ expect(screen.getByText(/Selected model .* unavailable/)).toBeInTheDocument();
+ // No silent switch is attempted.
+ expect(onSelect).not.toHaveBeenCalled();
+ });
});
describe("ReasoningEffortSelector", () => {