import { describe, expect, it, vi } from "vite-plus/test"; const testState = vi.hoisted(() => { let completeProjectFileRead: (value: null) => void = () => undefined; let projectFileRead = Promise.resolve(null); let storedDraft: { readonly draftId: string; readonly environmentId: string; readonly promotedTo: null; readonly threadId: string; } | null = null; const router = { state: { location: { href: "/" }, matches: [{ params: {} }], }, navigate: vi.fn(async (request: { readonly params: { readonly draftId: string } }) => { router.state.location.href = `/draft/${request.params.draftId}`; }), }; const draftStore = { getComposerDraft: vi.fn(() => ({})), getDraftSessionByLogicalProjectKey: vi.fn(() => storedDraft), getDraftSession: vi.fn(() => null), getDraftThread: vi.fn(() => null), applyStickyState: vi.fn(), setDraftThreadContext: vi.fn(), setLogicalProjectDraftThreadId: vi.fn(), setModelSelection: vi.fn(), }; return { completeProjectFileRead: (value: null) => completeProjectFileRead(value), draftStore, get projectFileRead() { return projectFileRead; }, reset(nextStoredDraft: typeof storedDraft) { storedDraft = nextStoredDraft; router.state.location.href = "/"; router.navigate.mockClear(); draftStore.setLogicalProjectDraftThreadId.mockClear(); projectFileRead = new Promise((resolve) => { completeProjectFileRead = resolve; }); }, router, }; }); vi.mock("@effect/atom-react", () => ({ useAtomValue: (atom: unknown) => atom === "primary-settings" ? { newWorktreesStartFromOrigin: false } : new Map([ [ "environment-ssh", { settings: { defaultThreadEnvMode: "local", newWorktreesStartFromOrigin: false, defaultModelSelection: null, }, }, ], ]), })); vi.mock("@t3tools/client-runtime/environment", () => ({ scopedProjectKey: () => "remote-project", scopeProjectRef: (environmentId: string, projectId: string) => ({ environmentId, projectId }), scopeThreadRef: (environmentId: string, threadId: string) => ({ environmentId, threadId }), })); vi.mock("@t3tools/contracts", () => ({ DEFAULT_RUNTIME_MODE: "default", DEFAULT_SERVER_SETTINGS: {}, })); vi.mock("@t3tools/shared/threadEnvMode", () => ({ resolveDefaultThreadEnvMode: (input: { readonly projectFile: "local" | "worktree" | null; readonly globalDefault: "local" | "worktree"; }) => input.projectFile ?? input.globalDefault, })); vi.mock("@tanstack/react-router", () => ({ useParams: () => null, useRouter: () => testState.router, })); vi.mock("react", () => ({ useCallback: (callback: T) => callback, useMemo: (factory: () => T) => factory(), })); vi.mock("../components/Sidebar.logic", () => ({ orderItemsByPreferredIds: () => [] })); vi.mock("../composerDraftStore", () => { const useComposerDraftStore = Object.assign(() => null, { getState: () => testState.draftStore, }); return { composerDraftHasUserContent: () => false, markPromotedDraftThreadByRef: vi.fn(), useComposerDraftStore, }; }); vi.mock("../lib/chatThreadActions", () => ({ hasExplicitComposerModelSelection: () => false, resolveNewDraftStartFromOrigin: () => false, resolveNewThreadModelSelectionOverride: () => null, })); vi.mock("../lib/t3ProjectFileDefaults", () => ({ readT3ProjectFileDefaultThreadEnvMode: () => testState.projectFileRead, })); vi.mock("../lib/utils", () => ({ newDraftId: () => "draft-delayed", newThreadId: () => "thread-delayed", })); vi.mock("../logicalProject", () => ({ deriveLogicalProjectKeyFromSettings: () => "remote-project", getProjectOrderKey: () => "remote-project", selectProjectGroupingSettings: () => ({}), })); vi.mock("../state/entities", () => ({ readProjects: () => [ { id: "project-remote", environmentId: "environment-ssh", workspaceRoot: "/remote/project", defaultThreadEnvMode: null, defaultModelSelection: null, }, ], readThreadShell: () => null, useProjects: () => [], useThread: () => null, })); vi.mock("../state/server", () => ({ environmentServerConfigsAtom: {}, primaryServerSettingsAtom: "primary-settings", })); vi.mock("../threadRoutes", () => ({ resolveThreadRouteTarget: () => null })); vi.mock("../uiStateStore", () => ({ legacyProjectCwdPreferenceKey: () => "remote-project", useUiStateStore: () => [], })); vi.mock("./useSettings", () => ({ useClientSettings: () => ({}) })); import { useNewThreadHandler } from "./useHandleNewThread"; describe("useNewThreadHandler", () => { it.each([ ["new", null], [ "reusable", { draftId: "draft-existing", environmentId: "environment-ssh", promotedTo: null, threadId: "thread-existing", }, ], ])("abandons a delayed %s draft open when the user navigates elsewhere", async (_, draft) => { testState.reset(draft); const openThread = useNewThreadHandler(); const pendingOpen = openThread( { environmentId: "environment-ssh", projectId: "project-remote" } as never, { replace: true }, ); testState.router.state.location.href = "/usage"; testState.completeProjectFileRead(null); await pendingOpen; expect(testState.router.state.location.href).toBe("/usage"); expect(testState.router.navigate).not.toHaveBeenCalled(); expect(testState.draftStore.setLogicalProjectDraftThreadId).not.toHaveBeenCalled(); }); });