summaryrefslogtreecommitdiffhomepage
path: root/src/features/chat/ui.test.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-07 02:06:55 +0900
committerAdam Malczewski <[email protected]>2026-06-07 02:06:55 +0900
commit529c6a2bb56447fe93796111df3d4cc5a05fdd93 (patch)
tree8db14b4b072b8a73ac85963f625b5bb3f77883ac /src/features/chat/ui.test.ts
parent90c438c4562793eb09358f9d1a050d2267f4fca5 (diff)
downloaddispatch-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.ts40
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");
+ });
+});