diff options
| author | Adam Malczewski <[email protected]> | 2026-06-07 02:06:55 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-07 02:06:55 +0900 |
| commit | 529c6a2bb56447fe93796111df3d4cc5a05fdd93 (patch) | |
| tree | 8db14b4b072b8a73ac85963f625b5bb3f77883ac /src/features/chat/ui.test.ts | |
| parent | 90c438c4562793eb09358f9d1a050d2267f4fca5 (diff) | |
| download | dispatch-web-529c6a2bb56447fe93796111df3d4cc5a05fdd93.tar.gz dispatch-web-529c6a2bb56447fe93796111df3d4cc5a05fdd93.zip | |
Slice 3 wave A: tabs model, model selector, cache delete, localStorage
- features/tabs: pure tab-workspace reducer (create/select/close/setModel/
setTitle/deriveTitle, draft=null active) + injected-persistence runes store
- features/chat: mutable per-tab model (setModel) + delta routing guard
(ignore foreign conversationId) + ModelSelector.svelte + DaisyUI chat bubbles
/ composer (keeps streaming <details> keying fix)
- features/conversation-cache: surface delete(conversationId) on the wrapper
for tab-close local-forget
- adapters/local-storage: generic injected JSON localStore<T> (quota/corrupt-safe)
Verified: svelte-check 0/0, vitest 273, biome clean, build ok.
Diffstat (limited to 'src/features/chat/ui.test.ts')
| -rw-r--r-- | src/features/chat/ui.test.ts | 40 |
1 files changed, 38 insertions, 2 deletions
diff --git a/src/features/chat/ui.test.ts b/src/features/chat/ui.test.ts index aebb97c..ac8f640 100644 --- a/src/features/chat/ui.test.ts +++ b/src/features/chat/ui.test.ts @@ -4,6 +4,7 @@ import { describe, expect, it, vi } from "vitest"; import type { RenderedChunk } from "../../core/chunks"; import ChatView from "./ui/ChatView.svelte"; import Composer from "./ui/Composer.svelte"; +import ModelSelector from "./ui/ModelSelector.svelte"; describe("ChatView", () => { it("renders a message's text chunk", () => { @@ -144,8 +145,8 @@ describe("ChatView", () => { render(ChatView, { props: { chunks } }); - const article = screen.getByText("Streaming...").closest("article"); - expect(article).toHaveClass("message--provisional"); + const bubble = screen.getByText("Streaming...").closest(".chat-bubble"); + expect(bubble).toHaveClass("opacity-50"); }); it("renders empty transcript", () => { @@ -260,3 +261,38 @@ describe("Composer", () => { expect(onSend).not.toHaveBeenCalled(); }); }); + +describe("ModelSelector", () => { + it("renders the options and current selection", () => { + const models = ["openai/gpt-4", "anthropic/claude-3", "google/gemini"]; + render(ModelSelector, { + props: { models, selected: "anthropic/claude-3", onSelect: vi.fn() }, + }); + + const select = screen.getByRole("combobox", { name: "Model selector" }); + expect(select).toBeInTheDocument(); + expect(select).toHaveValue("anthropic/claude-3"); + + const options = screen.getAllByRole("option"); + expect(options).toHaveLength(3); + expect(options[0]).toHaveValue("openai/gpt-4"); + expect(options[1]).toHaveValue("anthropic/claude-3"); + expect(options[2]).toHaveValue("google/gemini"); + }); + + it("calls onSelect on change", async () => { + const onSelect = vi.fn(); + const user = userEvent.setup(); + const models = ["openai/gpt-4", "anthropic/claude-3"]; + + render(ModelSelector, { + props: { models, selected: "openai/gpt-4", onSelect }, + }); + + const select = screen.getByRole("combobox", { name: "Model selector" }); + await user.selectOptions(select, "anthropic/claude-3"); + + expect(onSelect).toHaveBeenCalledTimes(1); + expect(onSelect).toHaveBeenCalledWith("anthropic/claude-3"); + }); +}); |
