murashit_codex-panel/tests/features/threads/thread-title-service.test.ts
2026-06-24 14:54:53 +09:00

69 lines
2.7 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { THREAD_TITLE_CONTEXT_UNAVAILABLE_MESSAGE, type ThreadTitleContext } from "../../../src/domain/threads/title-generation-model";
import {
createThreadTitleService,
type ThreadTitleService,
type ThreadTitleServiceHost,
} from "../../../src/features/threads/thread-title-service";
import { DEFAULT_SETTINGS } from "../../../src/settings/model";
describe("ThreadTitleService", () => {
it("generates a title from visible context without saving it", async () => {
const generateThreadTitle = vi.fn().mockResolvedValue("Generated title");
const withClient = vi.fn().mockRejectedValue(new Error("should not read persisted context"));
const service = titleService({
visibleContext: () => titleContext("visible request", "visible response"),
generateThreadTitle,
clientAccess: { withClient },
});
await expect(service.generateTitle("thread")).resolves.toBe("Generated title");
expect(withClient).not.toHaveBeenCalled();
expect(generateThreadTitle).toHaveBeenCalledWith(titleContext("visible request", "visible response"));
});
it("throws the existing unavailable-context error when no context can be resolved", async () => {
const service = titleService();
await expect(service.generateTitle("thread")).rejects.toThrow(THREAD_TITLE_CONTEXT_UNAVAILABLE_MESSAGE);
});
it("prefers visible completed-turn context over completed summaries", () => {
const service = titleService({
visibleCompletedTurnContext: () => titleContext("visible turn", "visible answer"),
});
expect(service.completedTurnContext("turn", { userText: "summary turn", assistantText: "summary answer" })).toEqual(
titleContext("visible turn", "visible answer"),
);
});
it("falls back from visible completed-turn context to completed summaries", () => {
const service = titleService({
visibleCompletedTurnContext: () => null,
});
expect(service.completedTurnContext("turn", { userText: "summary turn", assistantText: "summary answer" })).toEqual(
titleContext("summary turn", "summary answer"),
);
});
});
function titleService(options: Partial<ThreadTitleServiceHost> = {}): ThreadTitleService {
return createThreadTitleService({
codexPath: () => "codex",
vaultPath: "/vault",
threadNamingModel: () => DEFAULT_SETTINGS.threadNamingModel,
threadNamingEffort: () => DEFAULT_SETTINGS.threadNamingEffort,
clientAccess: {
withClient: vi.fn().mockResolvedValue(null),
},
...options,
});
}
function titleContext(userRequest: string, assistantResponse: string): ThreadTitleContext {
return { userRequest, assistantResponse };
}