1import { DEFAULT_CLIENT_SETTINGS, EnvironmentId, ProjectId } from "@t3tools/contracts";
2import type { AnyRouter } from "@tanstack/react-router";
3import { describe, expect, it, vi } from "vite-plus/test";
4
5import * as commandPalette from "../commandPaletteBus";
6import * as settings from "../hooks/useSettings";
7import * as storage from "../hooks/useLocalStorage";
8import * as threadActions from "../lib/chatThreadActions";
9import * as entities from "../state/entities";
10import * as projectsState from "../state/projects";
11import { Atom } from "effect/reactivity";
12import type { Project } from "../types";
13import { installChatRouteCommands } from "./chatRouteCommands";
14import * as dispatcher from "./dispatcher";
15
16function project(id: string): Project {
17 return {
18 id: ProjectId.make(id),
19 environmentId: EnvironmentId.make("local"),
20 title: id,
21 workspaceRoot: `/projects/${id}`,
22 repositoryIdentity: null,
23 defaultModelSelection: null,
24 createdAt: "2026-01-01T00:00:00.000Z",
25 updatedAt: "2026-01-01T00:00:00.000Z",
26 scripts: [],
27 };
28}
29
30describe("new thread commands", () => {
31 it.each([
32 { mode: "normal", count: 2, command: "chat.new", picker: true },
33 { mode: "boring", count: 2, command: "chat.new", picker: true },
34 { mode: "boring", count: 1, command: "chat.new", picker: false },
35 { mode: "boring", count: 2, command: "chat.newLocal", picker: false },
36 ] as const)("$mode sidebar, $count projects, $command: picker=$picker", (scenario) => {
37 using _settings = vi.spyOn(settings, "getClientSettings").mockReturnValue({
38 ...DEFAULT_CLIENT_SETTINGS,
39 boringSidebarEnabled: scenario.mode === "boring",
40 });
41 using _projects = vi
42 .spyOn(entities, "readProjects")
43 .mockReturnValue([project("active"), project("idle")].slice(0, scenario.count));
44 using _ready = vi
45 .spyOn(projectsState, "defaultProjectFallbackReadyAtom")
46 .mockReturnValue(Atom.make(true));
47 using storedProject = vi.spyOn(storage, "getLocalStorageItem").mockReturnValue("local:active");
48 using openPicker = vi.spyOn(commandPalette, "openCommandPalette").mockImplementation(() => {});
49 using startLocal = vi.spyOn(threadActions, "startNewThreadFromContext").mockResolvedValue(true);
50 using register = vi.spyOn(dispatcher, "registerCommands").mockReturnValue(() => {});
51 const router = { state: { matches: [] } } as unknown as AnyRouter;
52 using stack = new DisposableStack();
53 stack.defer(installChatRouteCommands(router));
54 const commands = register.mock.calls[0]![0];
55 const event = {
56 preventDefault: vi.fn(),
57 stopPropagation: vi.fn(),
58 } as unknown as KeyboardEvent;
59
60 storedProject.mockReturnValue("local:idle");
61 commands[scenario.command]!(event);
62
63 if (scenario.picker) {
64 expect(openPicker).toHaveBeenCalledWith({ open: "new-thread-in" });
65 expect(startLocal).not.toHaveBeenCalled();
66 } else {
67 expect(openPicker).not.toHaveBeenCalled();
68 expect(startLocal).toHaveBeenCalledOnce();
69 expect(startLocal.mock.calls[0]![0].defaultProjectRef).toEqual({
70 environmentId: "local",
71 projectId: scenario.count === 1 ? "active" : "idle",
72 });
73 }
74 });
75});