murashit_codex-panel/tests/features/chat/controllers/submission/plan-implementation-controller.test.ts
2026-06-05 12:39:08 +09:00

94 lines
3.6 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import type { AppServerClient } from "../../../../../src/app-server/client";
import { createChatState, createChatStateStore, type ChatStateStore } from "../../../../../src/features/chat/chat-state";
import { implementPlanCandidateFromState } from "../../../../../src/features/chat/plan-implementation";
import {
PlanImplementationController,
type PlanImplementationControllerHost,
} from "../../../../../src/features/chat/controllers/submission/plan-implementation-controller";
import { createSubmissionStatePort } from "../../../../../src/features/chat/controllers/state-ports";
import type { DisplayItem } from "../../../../../src/features/chat/display/types";
const planItem = (id: string): DisplayItem => ({
id,
kind: "message",
role: "assistant",
text: "Plan",
messageKind: "proposedPlan",
messageState: "completed",
});
function resumeThread(stateStore: ChatStateStore, displayItems: readonly DisplayItem[]): void {
stateStore.dispatch({
type: "thread/resumed",
thread: { id: "thread", cliVersion: "test" } as never,
cwd: "/vault",
model: null,
reasoningEffort: null,
serviceTier: null,
approvalPolicy: null,
approvalsReviewer: null,
activePermissionProfile: null,
displayItems,
});
stateStore.dispatch({ type: "runtime/requested-collaboration-mode-set", collaborationMode: "plan" });
}
function createController({ client = {} as AppServerClient } = {}) {
const stateStore = createChatStateStore(createChatState());
const ensureConnected = vi.fn().mockResolvedValue(undefined);
const sendTurnText = vi.fn().mockResolvedValue(undefined);
const host: PlanImplementationControllerHost = {
state: createSubmissionStatePort(stateStore),
connection: {
currentClient: () => client,
ensureConnected,
},
submission: {
sendTurnText,
},
};
return { controller: new PlanImplementationController(host), ensureConnected, sendTurnText, stateStore };
}
describe("PlanImplementationController", () => {
it("finds the latest proposed plan only when the thread is implementable", () => {
const stateStore = createChatStateStore(createChatState());
const first = planItem("first");
const latest = planItem("latest");
resumeThread(stateStore, [first, latest]);
expect(implementPlanCandidateFromState(stateStore.getState())).toBe(latest);
stateStore.dispatch({ type: "composer/draft-set", draft: "edit first" });
expect(implementPlanCandidateFromState(stateStore.getState())).toBeNull();
});
it("switches out of plan mode and submits the implementation prompt", async () => {
const { controller, ensureConnected, sendTurnText, stateStore } = createController();
const plan = planItem("plan");
resumeThread(stateStore, [plan]);
stateStore.dispatch({ type: "ui/panel-set", panel: "status-panel" });
await controller.implement(plan);
expect(ensureConnected).toHaveBeenCalledOnce();
expect(stateStore.getState().selectedCollaborationMode).toBe("default");
expect(stateStore.getState().openDetails.has("status-panel")).toBe(false);
expect(sendTurnText).toHaveBeenCalledWith("Please implement this plan.");
});
it("ignores stale plan items", async () => {
const { controller, ensureConnected, sendTurnText, stateStore } = createController();
const first = planItem("first");
const latest = planItem("latest");
resumeThread(stateStore, [first, latest]);
await controller.implement(first);
expect(ensureConnected).not.toHaveBeenCalled();
expect(sendTurnText).not.toHaveBeenCalled();
});
});