mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Prune low-value regression tests
This commit is contained in:
parent
b52fc163dd
commit
0bca5b3477
8 changed files with 23 additions and 488 deletions
|
|
@ -117,6 +117,20 @@ describe("app-server catalog adapters", () => {
|
|||
errors: ['{"message":"err"}'],
|
||||
});
|
||||
});
|
||||
|
||||
it("does not fall back to unrelated hook rows", async () => {
|
||||
const client = {
|
||||
listHooks: vi.fn().mockResolvedValue({
|
||||
data: [{ cwd: "/other", hooks: [{ key: "other" }], warnings: ["skip"], errors: [{ message: "skip" }] }],
|
||||
}),
|
||||
} as unknown as AppServerClient;
|
||||
|
||||
await expect(listHookCatalog(client, "/vault")).resolves.toEqual({
|
||||
hooks: [],
|
||||
warnings: [],
|
||||
errors: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function modelFixture(): Model {
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
import { readdir, readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const repoRoot = process.cwd();
|
||||
const sourceRoot = path.join(repoRoot, "src");
|
||||
const testsRoot = path.join(repoRoot, "tests");
|
||||
const generatedImportPattern = /(?:from\s+|import\s*\(\s*|require\s*\(\s*)["'][^"']*generated\/app-server\//;
|
||||
const sourceExtensions = new Set([".ts", ".tsx"]);
|
||||
|
||||
describe("generated app-server import boundary", () => {
|
||||
it("detects static, import type, and re-export generated app-server references", () => {
|
||||
expect(generatedImportPattern.test('import type { Thread } from "../../src/generated/app-server/v2/Thread";')).toBe(true);
|
||||
expect(generatedImportPattern.test('type Thread = import("../../src/generated/app-server/v2/Thread").Thread;')).toBe(true);
|
||||
expect(generatedImportPattern.test('const thread = require("../../src/generated/app-server/v2/Thread");')).toBe(true);
|
||||
expect(generatedImportPattern.test('export type { Thread } from "../../src/generated/app-server/v2/Thread";')).toBe(true);
|
||||
expect(generatedImportPattern.test('export * from "../../src/generated/app-server/v2/Thread";')).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps generated app-server types behind explicit app-server adapters", async () => {
|
||||
const files = await sourceFiles(sourceRoot);
|
||||
const offenders: string[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
const relativePath = slashPath(path.relative(repoRoot, file));
|
||||
if (relativePath.startsWith("src/generated/")) continue;
|
||||
if (allowsGeneratedAppServerImport(relativePath)) continue;
|
||||
if (generatedImportPattern.test(await readFile(file, "utf8"))) offenders.push(relativePath);
|
||||
}
|
||||
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps generated app-server types out of feature tests", async () => {
|
||||
const files = await sourceFiles(testsRoot);
|
||||
const offenders: string[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
const relativePath = slashPath(path.relative(repoRoot, file));
|
||||
if (relativePath.startsWith("tests/app-server/")) continue;
|
||||
if (generatedImportPattern.test(await readFile(file, "utf8"))) offenders.push(relativePath);
|
||||
}
|
||||
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
async function sourceFiles(directory: string): Promise<string[]> {
|
||||
const entries = await readdir(directory, { withFileTypes: true });
|
||||
const files = await Promise.all(
|
||||
entries.map(async (entry) => {
|
||||
const fullPath = path.join(directory, entry.name);
|
||||
if (entry.isDirectory()) return sourceFiles(fullPath);
|
||||
if (entry.isFile() && sourceExtensions.has(path.extname(entry.name))) return [fullPath];
|
||||
return [];
|
||||
}),
|
||||
);
|
||||
return files.flat();
|
||||
}
|
||||
|
||||
function slashPath(value: string): string {
|
||||
return value.split(path.sep).join("/");
|
||||
}
|
||||
|
||||
function allowsGeneratedAppServerImport(relativePath: string): boolean {
|
||||
return relativePath.startsWith("src/app-server/connection/");
|
||||
}
|
||||
|
|
@ -1,182 +0,0 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { ServerRequest } from "../../src/app-server/connection/rpc-messages";
|
||||
import {
|
||||
appServerApprovalRequest,
|
||||
appServerApprovalResponse,
|
||||
appServerMcpElicitationRequest,
|
||||
appServerMcpElicitationResponse,
|
||||
appServerUserInputRequest,
|
||||
appServerUserInputResponse,
|
||||
} from "../../src/app-server/protocol/server-requests";
|
||||
|
||||
function expectPresent<T>(value: T | null | undefined): T {
|
||||
if (value === null || value === undefined) throw new Error("Expected value to be present");
|
||||
return value;
|
||||
}
|
||||
|
||||
describe("app-server server request protocol adapters", () => {
|
||||
it("classifies approval requests and builds approval responses", () => {
|
||||
const request: ServerRequest = {
|
||||
id: 2,
|
||||
method: "item/permissions/requestApproval",
|
||||
params: {
|
||||
cwd: "/tmp/project",
|
||||
threadId: "thread",
|
||||
turnId: "turn",
|
||||
itemId: "item",
|
||||
environmentId: null,
|
||||
startedAtMs: 1,
|
||||
reason: "Need network",
|
||||
permissions: { network: { enabled: true }, fileSystem: null },
|
||||
},
|
||||
};
|
||||
|
||||
const approval = expectPresent(appServerApprovalRequest(request));
|
||||
|
||||
expect(approval.kind).toBe("permission");
|
||||
expect(appServerApprovalResponse(approval, "decline")).toEqual({ permissions: {}, scope: "turn" });
|
||||
expect(appServerApprovalResponse(approval, "accept")).toEqual({
|
||||
permissions: { network: { enabled: true } },
|
||||
scope: "turn",
|
||||
});
|
||||
});
|
||||
|
||||
it("classifies user input requests and builds answer responses", () => {
|
||||
const request: ServerRequest = {
|
||||
id: 7,
|
||||
method: "item/tool/requestUserInput",
|
||||
params: {
|
||||
threadId: "thread",
|
||||
turnId: "turn",
|
||||
itemId: "item",
|
||||
questions: [
|
||||
{
|
||||
id: "direction",
|
||||
header: "Direction",
|
||||
question: "Which way?",
|
||||
isOther: true,
|
||||
isSecret: false,
|
||||
options: [{ label: "Recommended", description: "Use the default path" }],
|
||||
},
|
||||
],
|
||||
autoResolutionMs: null,
|
||||
},
|
||||
};
|
||||
|
||||
const input = expectPresent(appServerUserInputRequest(request));
|
||||
|
||||
expect(input.params.questions[0]?.id).toBe("direction");
|
||||
expect(appServerUserInputResponse(input.params.questions, { direction: "Left" })).toEqual({
|
||||
answers: { direction: { answers: ["Left"] } },
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes MCP form schema fields and response content", () => {
|
||||
const input = expectPresent(appServerMcpElicitationRequest(formRequest()));
|
||||
|
||||
expect(input).toMatchObject({
|
||||
requestId: 42,
|
||||
params: {
|
||||
mode: "form",
|
||||
threadId: "thread",
|
||||
turnId: null,
|
||||
serverName: "github",
|
||||
},
|
||||
});
|
||||
if (input.params.mode !== "form") throw new Error("Expected form mode");
|
||||
expect(input.params.fields).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: "title", type: "string", title: "Title", required: true, defaultValue: "Issue" }),
|
||||
expect.objectContaining({ id: "notify", type: "boolean", defaultValue: true }),
|
||||
expect.objectContaining({ id: "count", type: "integer", defaultValue: 2 }),
|
||||
expect.objectContaining({
|
||||
id: "labels",
|
||||
type: "multi-select",
|
||||
minItems: 1,
|
||||
maxItems: 2,
|
||||
options: [
|
||||
{ value: "bug", label: "Bug" },
|
||||
{ value: "docs", label: "Docs" },
|
||||
],
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(appServerMcpElicitationResponse("accept", { title: "Fix bug", labels: ["bug"] })).toEqual({
|
||||
action: "accept",
|
||||
content: { title: "Fix bug", labels: ["bug"] },
|
||||
_meta: null,
|
||||
});
|
||||
expect(appServerMcpElicitationResponse("decline", { title: "Fix bug" })).toEqual({
|
||||
action: "decline",
|
||||
content: null,
|
||||
_meta: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes MCP url mode separately from form fields", () => {
|
||||
expect(appServerMcpElicitationRequest(urlRequest())).toMatchObject({
|
||||
requestId: 43,
|
||||
params: {
|
||||
mode: "url",
|
||||
url: "https://example.com/confirm",
|
||||
elicitationId: "elicit-1",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function formRequest(): ServerRequest {
|
||||
return {
|
||||
id: 42,
|
||||
method: "mcpServer/elicitation/request",
|
||||
params: {
|
||||
threadId: "thread",
|
||||
turnId: null,
|
||||
serverName: "github",
|
||||
mode: "form",
|
||||
_meta: null,
|
||||
message: "Provide issue details",
|
||||
requestedSchema: {
|
||||
type: "object",
|
||||
required: ["title"],
|
||||
properties: {
|
||||
title: { type: "string", title: "Title", description: "Issue title", default: "Issue" },
|
||||
labels: {
|
||||
type: "array",
|
||||
title: "Labels",
|
||||
minItems: 1,
|
||||
maxItems: 2,
|
||||
items: {
|
||||
anyOf: [
|
||||
{ const: "bug", title: "Bug" },
|
||||
{ const: "docs", title: "Docs" },
|
||||
],
|
||||
},
|
||||
default: ["bug"],
|
||||
},
|
||||
notify: { type: "boolean", title: "Notify", default: true },
|
||||
count: { type: "integer", title: "Count", default: 2 },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ServerRequest;
|
||||
}
|
||||
|
||||
function urlRequest(): ServerRequest {
|
||||
return {
|
||||
id: 43,
|
||||
method: "mcpServer/elicitation/request",
|
||||
params: {
|
||||
threadId: "thread",
|
||||
turnId: "turn",
|
||||
serverName: "github",
|
||||
mode: "url",
|
||||
_meta: null,
|
||||
message: "Confirm in browser",
|
||||
url: "https://example.com/confirm",
|
||||
elicitationId: "elicit-1",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -480,10 +480,6 @@ describe("slash commands", () => {
|
|||
expect(ctx.addSystemMessage).toHaveBeenCalledWith("Multiple matching threads: Draft (thread-a), Draft notes (thread-b)");
|
||||
});
|
||||
|
||||
it("documents archive", () => {
|
||||
expect(slashCommandHelpValue("/archive <thread>")).toBe("Archive the selected Codex thread.");
|
||||
});
|
||||
|
||||
it("shows slash command help as a structured system result", async () => {
|
||||
const ctx = context();
|
||||
|
||||
|
|
@ -582,10 +578,6 @@ describe("slash commands", () => {
|
|||
expect(ctx.addSystemMessage).toHaveBeenCalledWith("/doctor does not take arguments. Usage: /doctor");
|
||||
});
|
||||
|
||||
it("documents that /plan can take a message", () => {
|
||||
expect(slashCommandHelpValue("/plan [message]")).toBe("Toggle Plan mode, optionally with a message.");
|
||||
});
|
||||
|
||||
it("expands goal subcommands in help", () => {
|
||||
expect(slashCommandHelpValue("/goal [set <objective>|edit|pause|resume|clear]")).toBeUndefined();
|
||||
expect(slashCommandHelpValue("/goal")).toBe("Show or manage the current thread goal.");
|
||||
|
|
@ -601,14 +593,6 @@ describe("slash commands", () => {
|
|||
expect(slashCommandHelpValue("/rollback")).toBe("Roll back the latest turn and restore its prompt.");
|
||||
});
|
||||
|
||||
it("documents rename", () => {
|
||||
expect(slashCommandHelpValue("/rename <thread> <name>")).toBe("Rename the selected Codex thread.");
|
||||
});
|
||||
|
||||
it("documents refer history size", () => {
|
||||
expect(slashCommandHelpValue("/refer <thread> <message>")).toBe("Send a message with recent turns from another non-archived thread.");
|
||||
});
|
||||
|
||||
it("documents status and doctor as separate commands", () => {
|
||||
expect(slashCommandHelpValue("/status")).toBe("Show current thread, context, and usage limits.");
|
||||
expect(slashCommandHelpValue("/doctor")).toBe("Show Codex CLI and Codex App Server diagnostics.");
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import type { ThreadGoal } from "../../../../../src/domain/threads/goal";
|
||||
import type { Thread } from "../../../../../src/domain/threads/model";
|
||||
import {
|
||||
activeTurnId,
|
||||
chatTurnBusy,
|
||||
pendingTurnStart,
|
||||
transitionChatTurnLifecycleState,
|
||||
} from "../../../../../src/features/chat/application/conversation/turn-state";
|
||||
import { activeTurnId, chatTurnBusy, pendingTurnStart } from "../../../../../src/features/chat/application/conversation/turn-state";
|
||||
import { messageStreamItems } from "../../../../../src/features/chat/application/state/message-stream";
|
||||
import { type ChatState, chatReducer } from "../../../../../src/features/chat/application/state/root-reducer";
|
||||
import { createChatStateStore } from "../../../../../src/features/chat/application/state/store";
|
||||
|
|
@ -249,23 +244,6 @@ describe("chatReducer", () => {
|
|||
expect(pendingTurnStart(completed)).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps turn lifecycle transition rules explicit", () => {
|
||||
const pending = { anchorItemId: "local-user", promptSubmitHookItemIds: ["hook"] };
|
||||
const optimistic = transitionChatTurnLifecycleState({ kind: "idle" }, { type: "optimistic-started", pendingTurnStart: pending });
|
||||
const running = transitionChatTurnLifecycleState(optimistic, { type: "start-acknowledged", turnId: "turn" });
|
||||
|
||||
expect(optimistic).toEqual({ kind: "starting", pendingTurnStart: pending });
|
||||
expect(running).toEqual({ kind: "running", turnId: "turn" });
|
||||
expect(transitionChatTurnLifecycleState(running, { type: "completed", turnId: "stale-turn" })).toEqual({
|
||||
kind: "running",
|
||||
turnId: "turn",
|
||||
});
|
||||
expect(transitionChatTurnLifecycleState({ kind: "idle" }, { type: "start-acknowledged", turnId: "turn" })).toEqual({
|
||||
kind: "idle",
|
||||
});
|
||||
expect(transitionChatTurnLifecycleState(running, { type: "completed", turnId: "turn" })).toEqual({ kind: "idle" });
|
||||
});
|
||||
|
||||
it("appends streaming assistant deltas after stable history", () => {
|
||||
let state = chatStateFixture();
|
||||
state = withChatStateMessageStreamItems(state, [message("history")]);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { describe, expect, it, vi } from "vitest";
|
|||
import { createChatState } from "../../../../src/features/chat/application/state/root-reducer";
|
||||
import { createChatStateStore } from "../../../../src/features/chat/application/state/store";
|
||||
import type { ThreadManagementActions } from "../../../../src/features/chat/application/threads/thread-management-actions";
|
||||
import { createToolbarPanelActions, createToolbarUiActions } from "../../../../src/features/chat/panel/toolbar-actions";
|
||||
import { createToolbarPanelActions } from "../../../../src/features/chat/panel/toolbar-actions";
|
||||
|
||||
describe("createToolbarPanelActions", () => {
|
||||
it("tracks archive confirmation and delegates archive actions", async () => {
|
||||
|
|
@ -42,103 +42,3 @@ describe("createToolbarPanelActions", () => {
|
|||
expect(stateStore.getState().ui.toolbarPanel).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("createToolbarUiActions", () => {
|
||||
it("maps primary toolbar controls to panel actions", () => {
|
||||
const deps = toolbarActionDeps();
|
||||
const actions = createToolbarUiActions(deps);
|
||||
|
||||
actions.primary.toggleHistory();
|
||||
actions.primary.toggleChatActions();
|
||||
actions.primary.toggleStatusPanel();
|
||||
|
||||
expect(vi.mocked(deps.toolbarPanel.toggleHistory)).toHaveBeenCalledOnce();
|
||||
expect(vi.mocked(deps.toolbarPanel.toggleChatActions)).toHaveBeenCalledOnce();
|
||||
expect(vi.mocked(deps.toolbarPanel.toggleStatus)).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("starts a new thread through thread navigation", () => {
|
||||
const deps = toolbarActionDeps();
|
||||
const actions = createToolbarUiActions(deps);
|
||||
|
||||
actions.chat.startNewThread();
|
||||
|
||||
expect(vi.mocked(deps.navigation.startNewThread)).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("delegates compacting the active thread", () => {
|
||||
const deps = toolbarActionDeps();
|
||||
const actions = createToolbarUiActions(deps);
|
||||
|
||||
actions.chat.compactConversation();
|
||||
|
||||
expect(vi.mocked(deps.threadActions.compactActiveThread)).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("starts the goal editor from the active goal", () => {
|
||||
const deps = toolbarActionDeps();
|
||||
const actions = createToolbarUiActions(deps);
|
||||
|
||||
actions.chat.setGoal();
|
||||
|
||||
expect(vi.mocked(deps.goals.startEditingCurrent)).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("maps thread list actions to navigation, archive, and rename actions", () => {
|
||||
const deps = toolbarActionDeps();
|
||||
const actions = createToolbarUiActions(deps);
|
||||
|
||||
actions.threads.resume("thread");
|
||||
actions.threads.archive.start("thread");
|
||||
actions.threads.archive.confirm("thread", true);
|
||||
actions.threads.rename.start("thread");
|
||||
actions.threads.rename.updateDraft("thread", "Draft");
|
||||
actions.threads.rename.save("thread", "Saved");
|
||||
actions.threads.rename.cancel("thread");
|
||||
actions.threads.rename.autoName("thread");
|
||||
|
||||
expect(vi.mocked(deps.navigation.selectThreadFromToolbar)).toHaveBeenCalledWith("thread");
|
||||
expect(vi.mocked(deps.toolbarPanel.startArchive)).toHaveBeenCalledWith("thread");
|
||||
expect(vi.mocked(deps.toolbarPanel.archiveThread)).toHaveBeenCalledWith("thread", true);
|
||||
expect(vi.mocked(deps.rename.start)).toHaveBeenCalledWith("thread");
|
||||
expect(vi.mocked(deps.rename.updateDraft)).toHaveBeenCalledWith("thread", "Draft");
|
||||
expect(vi.mocked(deps.rename.save)).toHaveBeenCalledWith("thread", "Saved");
|
||||
expect(vi.mocked(deps.rename.cancel)).toHaveBeenCalledWith("thread");
|
||||
expect(vi.mocked(deps.rename.autoNameDraft)).toHaveBeenCalledWith("thread");
|
||||
});
|
||||
});
|
||||
|
||||
function toolbarActionDeps(): Parameters<typeof createToolbarUiActions>[0] {
|
||||
return {
|
||||
connectionController: { refreshStatusPanel: vi.fn() } as unknown as Parameters<
|
||||
typeof createToolbarUiActions
|
||||
>[0]["connectionController"],
|
||||
reconnectPanel: vi.fn(),
|
||||
threadActions: {
|
||||
archiveThread: vi.fn().mockResolvedValue(undefined),
|
||||
compactActiveThread: vi.fn().mockResolvedValue(undefined),
|
||||
compactThread: vi.fn().mockResolvedValue(undefined),
|
||||
} as unknown as ThreadManagementActions,
|
||||
goals: {
|
||||
startEditingCurrent: vi.fn(),
|
||||
} as unknown as Parameters<typeof createToolbarUiActions>[0]["goals"],
|
||||
toolbarPanel: {
|
||||
toggleChatActions: vi.fn(),
|
||||
toggleHistory: vi.fn(),
|
||||
toggleStatus: vi.fn(),
|
||||
startArchive: vi.fn(),
|
||||
archiveThread: vi.fn().mockResolvedValue(undefined),
|
||||
} as unknown as Parameters<typeof createToolbarUiActions>[0]["toolbarPanel"],
|
||||
rename: {
|
||||
start: vi.fn(),
|
||||
updateDraft: vi.fn(),
|
||||
save: vi.fn().mockResolvedValue(undefined),
|
||||
cancel: vi.fn(),
|
||||
autoNameDraft: vi.fn().mockResolvedValue(undefined),
|
||||
} as unknown as Parameters<typeof createToolbarUiActions>[0]["rename"],
|
||||
navigation: {
|
||||
startNewThread: vi.fn().mockResolvedValue(undefined),
|
||||
selectThreadFromToolbar: vi.fn().mockResolvedValue(undefined),
|
||||
} as unknown as Parameters<typeof createToolbarUiActions>[0]["navigation"],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,14 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { Thread } from "../../../src/domain/threads/model";
|
||||
import { type ThreadsRenameState, threadRows, transitionThreadsRenameState } from "../../../src/features/threads-view/state";
|
||||
import { threadRows, transitionThreadsRenameState } from "../../../src/features/threads-view/state";
|
||||
import type { OpenCodexPanelSnapshot } from "../../../src/workspace/panel-coordinator";
|
||||
|
||||
describe("threads view rename state", () => {
|
||||
it("keeps a late auto-name result from reviving a cancelled rename", () => {
|
||||
const generating = generatingRenameState("Original draft", 1);
|
||||
|
||||
expect(
|
||||
transitionThreadsRenameState(undefined, { type: "auto-name-generated", generatingState: generating, title: "Late title" }),
|
||||
).toBeUndefined();
|
||||
expect(transitionThreadsRenameState(undefined, { type: "auto-name-finished", generatingState: generating })).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps a manually edited draft when auto-name finishes later", () => {
|
||||
const generating = generatingRenameState("Original draft", 1);
|
||||
|
||||
const manuallyEdited = transitionThreadsRenameState(generating, { type: "draft-updated", draft: "Manual draft" });
|
||||
|
||||
expect(
|
||||
transitionThreadsRenameState(manuallyEdited, { type: "auto-name-generated", generatingState: generating, title: "Late title" }),
|
||||
).toBe(manuallyEdited);
|
||||
expect(transitionThreadsRenameState(manuallyEdited, { type: "auto-name-finished", generatingState: generating })).toEqual({
|
||||
kind: "editing",
|
||||
draft: "Manual draft",
|
||||
});
|
||||
});
|
||||
|
||||
it("applies generated titles only to the active unchanged generation", () => {
|
||||
const generating = generatingRenameState("Original draft", 1);
|
||||
it("maps threads view auto-name events through the shared rename lifecycle", () => {
|
||||
const editing = transitionThreadsRenameState(undefined, { type: "started", draft: "Original draft" });
|
||||
const generating = transitionThreadsRenameState(editing, { type: "auto-name-started", generationToken: 1 });
|
||||
if (generating?.kind !== "generating") throw new Error("Expected generating state");
|
||||
|
||||
const generated = transitionThreadsRenameState(generating, {
|
||||
type: "auto-name-generated",
|
||||
|
|
@ -47,30 +26,8 @@ describe("threads view rename state", () => {
|
|||
kind: "editing",
|
||||
draft: "Generated title",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps ignored rename lifecycle transitions as no-ops", () => {
|
||||
const editing = editingRenameState("Original draft");
|
||||
const generating = generatingRenameState("Original draft", 1);
|
||||
|
||||
expect(transitionThreadsRenameState(generated, { type: "cancelled" })).toBeUndefined();
|
||||
expect(transitionThreadsRenameState(undefined, { type: "draft-updated", draft: "Stray" })).toBeUndefined();
|
||||
expect(transitionThreadsRenameState(undefined, { type: "auto-name-started", generationToken: 1 })).toBeUndefined();
|
||||
expect(transitionThreadsRenameState(editing, { type: "auto-name-generated", generatingState: generating, title: "Late" })).toBe(
|
||||
editing,
|
||||
);
|
||||
expect(transitionThreadsRenameState(generating, { type: "auto-name-started", generationToken: 2 })).toBe(generating);
|
||||
});
|
||||
|
||||
it("keeps stale auto-name completion from finishing a newer generation", () => {
|
||||
const oldGenerating = generatingRenameState("Original draft", 1);
|
||||
const currentGenerating = generatingRenameState("Original draft", 2);
|
||||
|
||||
expect(
|
||||
transitionThreadsRenameState(currentGenerating, { type: "auto-name-generated", generatingState: oldGenerating, title: "Old" }),
|
||||
).toBe(currentGenerating);
|
||||
expect(transitionThreadsRenameState(currentGenerating, { type: "auto-name-finished", generatingState: oldGenerating })).toBe(
|
||||
currentGenerating,
|
||||
);
|
||||
});
|
||||
|
||||
it("initializes rename drafts from normalized explicit thread names", () => {
|
||||
|
|
@ -96,22 +53,6 @@ describe("threads view rename state", () => {
|
|||
});
|
||||
});
|
||||
|
||||
function editingRenameState(draft: string): ThreadsRenameState {
|
||||
return expectRenameState(transitionThreadsRenameState(undefined, { type: "started", draft }));
|
||||
}
|
||||
|
||||
function generatingRenameState(draft: string, generationToken: number): Extract<ThreadsRenameState, { kind: "generating" }> {
|
||||
const editing = editingRenameState(draft);
|
||||
const generating = transitionThreadsRenameState(editing, { type: "auto-name-started", generationToken });
|
||||
if (generating?.kind !== "generating") throw new Error("Expected generating state");
|
||||
return generating;
|
||||
}
|
||||
|
||||
function expectRenameState(state: ThreadsRenameState | undefined): ThreadsRenameState {
|
||||
if (state) return state;
|
||||
throw new Error("Expected rename state");
|
||||
}
|
||||
|
||||
function thread(overrides: Partial<Thread> = {}): Thread {
|
||||
return {
|
||||
id: "thread",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import { listHookCatalog } from "../../src/app-server/catalog";
|
||||
import type { AppServerClient } from "../../src/app-server/connection/client";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createSettingsDynamicSectionLifecycle, transitionSettingsDynamicSectionLifecycle } from "../../src/settings/lifecycle";
|
||||
|
||||
describe("settings lifecycle", () => {
|
||||
|
|
@ -35,35 +33,4 @@ describe("settings lifecycle", () => {
|
|||
|
||||
expect(transitionSettingsDynamicSectionLifecycle(failed, { type: "reset" })).toEqual(idle);
|
||||
});
|
||||
|
||||
it("uses only hook rows for the requested cwd", async () => {
|
||||
const client = {
|
||||
listHooks: vi.fn().mockResolvedValue({
|
||||
data: [
|
||||
{ cwd: "/other", hooks: [{ key: "other" }], warnings: ["skip"], errors: [{ message: "skip" }] },
|
||||
{ cwd: "/vault", hooks: [{ key: "vault" }], warnings: ["warn"], errors: [{ message: "err" }] },
|
||||
],
|
||||
}),
|
||||
} as unknown as AppServerClient;
|
||||
|
||||
await expect(listHookCatalog(client, "/vault")).resolves.toEqual({
|
||||
hooks: [{ key: "vault" }],
|
||||
warnings: ["warn"],
|
||||
errors: ['{"message":"err"}'],
|
||||
});
|
||||
});
|
||||
|
||||
it("does not fall back to unrelated hook rows", async () => {
|
||||
const client = {
|
||||
listHooks: vi.fn().mockResolvedValue({
|
||||
data: [{ cwd: "/other", hooks: [{ key: "other" }], warnings: ["skip"], errors: [{ message: "skip" }] }],
|
||||
}),
|
||||
} as unknown as AppServerClient;
|
||||
|
||||
await expect(listHookCatalog(client, "/vault")).resolves.toEqual({
|
||||
hooks: [],
|
||||
warnings: [],
|
||||
errors: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue