Phase 12: RPC no-ui command safety — SPEC §11.
Slash commands persist state changes in hasUI=false mode without calling notify; export handler for regression tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { handleSkillReinjectCommand } from "../src/commands";
|
||||
import { createInitialState, createRuntimeFlags } from "../src/state";
|
||||
|
||||
function createNoUiCommandContext() {
|
||||
const notify = vi.fn();
|
||||
return {
|
||||
hasUI: false,
|
||||
mode: "rpc" as const,
|
||||
ui: { notify, setStatus: vi.fn() },
|
||||
cwd: process.cwd(),
|
||||
isProjectTrusted: () => true,
|
||||
isIdle: () => true,
|
||||
};
|
||||
}
|
||||
|
||||
function createDeps() {
|
||||
const state = createInitialState();
|
||||
const persistState = vi.fn();
|
||||
const pi = {
|
||||
sendUserMessage: vi.fn(),
|
||||
registerCommand: vi.fn(),
|
||||
appendEntry: vi.fn(),
|
||||
};
|
||||
return {
|
||||
state,
|
||||
persistState,
|
||||
pi,
|
||||
runtime: createRuntimeFlags(),
|
||||
getRegisteredSkills: () => [],
|
||||
};
|
||||
}
|
||||
|
||||
describe("handleSkillReinjectCommand without UI", () => {
|
||||
it("does not throw on status", async () => {
|
||||
const ctx = createNoUiCommandContext();
|
||||
const deps = createDeps();
|
||||
|
||||
await expect(handleSkillReinjectCommand("", ctx as never, deps as never)).resolves.toBeUndefined();
|
||||
expect(ctx.ui.notify).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("persists session toggle without notify", async () => {
|
||||
const ctx = createNoUiCommandContext();
|
||||
const deps = createDeps();
|
||||
|
||||
await handleSkillReinjectCommand("on", ctx as never, deps as never);
|
||||
|
||||
expect(deps.state.sessionOverride).toBe(true);
|
||||
expect(deps.persistState).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.ui.notify).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("clears tracked skills without notify", async () => {
|
||||
const ctx = createNoUiCommandContext();
|
||||
const deps = createDeps();
|
||||
deps.state.skills.push({
|
||||
name: "alpha",
|
||||
filePath: "/a/SKILL.md",
|
||||
baseDir: "/a",
|
||||
firstSeenAt: 1,
|
||||
lastSeenAt: 1,
|
||||
sources: ["slash"],
|
||||
});
|
||||
|
||||
await handleSkillReinjectCommand("clear", ctx as never, deps as never);
|
||||
|
||||
expect(deps.state.skills).toEqual([]);
|
||||
expect(deps.persistState).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.ui.notify).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("persists integration override without notify", async () => {
|
||||
const ctx = createNoUiCommandContext();
|
||||
const deps = createDeps();
|
||||
|
||||
await handleSkillReinjectCommand("integration defer", ctx as never, deps as never);
|
||||
|
||||
expect(deps.state.sessionIntegrationOverride).toBe("defer");
|
||||
expect(deps.persistState).toHaveBeenCalledTimes(1);
|
||||
expect(ctx.ui.notify).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user