mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
94 lines
3.6 KiB
TypeScript
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();
|
|
});
|
|
});
|