Skip to content

Commit 1150497

Browse files
committed
test: clear slash command broad matchers
1 parent e43d246 commit 1150497

1 file changed

Lines changed: 84 additions & 68 deletions

File tree

ui/src/ui/chat/slash-commands.node.test.ts

Lines changed: 84 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -11,108 +11,129 @@ afterEach(() => {
1111
resetSlashCommandsForTest();
1212
});
1313

14+
function isRecord(value: unknown): value is Record<string, unknown> {
15+
return typeof value === "object" && value !== null && !Array.isArray(value);
16+
}
17+
18+
function requireRecord(value: unknown, label: string): Record<string, unknown> {
19+
if (!isRecord(value)) {
20+
throw new Error(`expected ${label} to be an object`);
21+
}
22+
return value;
23+
}
24+
25+
function requireArray(value: unknown, label: string): unknown[] {
26+
if (!Array.isArray(value)) {
27+
throw new Error(`expected ${label} to be an array`);
28+
}
29+
return value;
30+
}
31+
32+
function expectRecordFields(value: unknown, label: string, expected: Record<string, unknown>) {
33+
const record = requireRecord(value, label);
34+
for (const [key, expectedValue] of Object.entries(expected)) {
35+
expect(record[key]).toEqual(expectedValue);
36+
}
37+
}
38+
39+
function requireCommandByName(name: string): Record<string, unknown> {
40+
return requireRecord(
41+
SLASH_COMMANDS.find((entry) => entry.name === name),
42+
`slash command ${name}`,
43+
);
44+
}
45+
46+
function requireCommandByKey(key: string): Record<string, unknown> {
47+
return requireRecord(
48+
SLASH_COMMANDS.find((entry) => entry.key === key),
49+
`slash command ${key}`,
50+
);
51+
}
52+
53+
function expectParsedSlash(input: string, commandFields: Record<string, unknown>, args: string) {
54+
const parsed = requireRecord(parseSlashCommand(input), `parsed ${input}`);
55+
expectRecordFields(parsed.command, `parsed ${input} command`, commandFields);
56+
expect(parsed.args).toBe(args);
57+
}
58+
1459
describe("parseSlashCommand", () => {
1560
it("parses commands with an optional colon separator", () => {
16-
expect(parseSlashCommand("/think: high")).toMatchObject({
17-
command: { name: "think" },
18-
args: "high",
19-
});
20-
expect(parseSlashCommand("/think:high")).toMatchObject({
21-
command: { name: "think" },
22-
args: "high",
23-
});
24-
expect(parseSlashCommand("/help:")).toMatchObject({
25-
command: { name: "help" },
26-
args: "",
27-
});
61+
expectParsedSlash("/think: high", { name: "think" }, "high");
62+
expectParsedSlash("/think:high", { name: "think" }, "high");
63+
expectParsedSlash("/help:", { name: "help" }, "");
2864
});
2965

3066
it("still parses space-delimited commands", () => {
31-
expect(parseSlashCommand("/verbose full")).toMatchObject({
32-
command: { name: "verbose" },
33-
args: "full",
34-
});
67+
expectParsedSlash("/verbose full", { name: "verbose" }, "full");
3568
});
3669

3770
it("parses fast commands", () => {
38-
expect(parseSlashCommand("/fast:on")).toMatchObject({
39-
command: { name: "fast" },
40-
args: "on",
41-
});
71+
expectParsedSlash("/fast:on", { name: "fast" }, "on");
4272
});
4373

4474
it("keeps /status on the agent path", () => {
4575
const status = SLASH_COMMANDS.find((entry) => entry.name === "status");
4676
expect(status?.executeLocal).not.toBe(true);
47-
expect(parseSlashCommand("/status")).toMatchObject({
48-
command: { name: "status" },
49-
args: "",
50-
});
77+
expectParsedSlash("/status", { name: "status" }, "");
5178
});
5279

5380
it("includes shared /tools with shared arg hints", () => {
54-
const tools = SLASH_COMMANDS.find((entry) => entry.name === "tools");
55-
expect(tools).toMatchObject({
81+
const tools = requireCommandByName("tools");
82+
expectRecordFields(tools, "tools command", {
5683
key: "tools",
5784
description: "List available runtime tools.",
5885
argOptions: ["compact", "verbose"],
5986
executeLocal: false,
6087
});
61-
expect(parseSlashCommand("/tools verbose")).toMatchObject({
62-
command: { name: "tools" },
63-
args: "verbose",
64-
});
88+
expectParsedSlash("/tools verbose", { name: "tools" }, "verbose");
6589
});
6690

6791
it("parses slash aliases through the shared registry", () => {
68-
const exportCommand = SLASH_COMMANDS.find((entry) => entry.key === "export-session");
69-
expect(exportCommand).toMatchObject({
92+
const exportCommand = requireCommandByKey("export-session");
93+
expectRecordFields(exportCommand, "export-session command", {
7094
name: "export-session",
7195
aliases: ["export"],
7296
executeLocal: true,
7397
});
74-
expect(parseSlashCommand("/export")).toMatchObject({
75-
command: { key: "export-session" },
76-
args: "",
77-
});
78-
expect(parseSlashCommand("/export-session")).toMatchObject({
79-
command: { key: "export-session" },
80-
args: "",
81-
});
82-
expect(parseSlashCommand("/side what changed?")).toMatchObject({
83-
command: { key: "btw", name: "btw", aliases: expect.arrayContaining(["side"]) },
84-
args: "what changed?",
85-
});
98+
expectParsedSlash("/export", { key: "export-session" }, "");
99+
expectParsedSlash("/export-session", { key: "export-session" }, "");
100+
const side = requireRecord(parseSlashCommand("/side what changed?"), "parsed /side");
101+
expectRecordFields(side.command, "side command", { key: "btw", name: "btw" });
102+
expect(
103+
requireArray(requireRecord(side.command, "side command").aliases, "side aliases"),
104+
).toContain("side");
105+
expect(side.args).toBe("what changed?");
86106
});
87107

88108
it("keeps canonical long-form slash names as the primary menu command", () => {
89-
expect(SLASH_COMMANDS.find((entry) => entry.key === "verbose")).toMatchObject({
109+
expectRecordFields(requireCommandByKey("verbose"), "verbose command", {
90110
name: "verbose",
91111
aliases: ["v"],
92112
});
93-
expect(SLASH_COMMANDS.find((entry) => entry.key === "think")).toMatchObject({
113+
const think = requireCommandByKey("think");
114+
expectRecordFields(think, "think command", {
94115
name: "think",
95-
aliases: expect.arrayContaining(["thinking", "t"]),
96116
});
117+
const aliases = requireArray(think.aliases, "think aliases");
118+
expect(aliases).toContain("thinking");
119+
expect(aliases).toContain("t");
97120
});
98121

99122
it("keeps a single local /steer entry with the control-ui metadata", () => {
100123
const steerEntries = SLASH_COMMANDS.filter((entry) => entry.name === "steer");
101124
expect(steerEntries).toHaveLength(1);
102-
expect(steerEntries[0]).toMatchObject({
125+
const steer = requireRecord(steerEntries[0], "steer command");
126+
expectRecordFields(steer, "steer command", {
103127
key: "steer",
104128
description: "Inject a message into the active run",
105129
args: "[id] <message>",
106-
aliases: expect.arrayContaining(["tell"]),
107130
executeLocal: true,
108131
});
132+
expect(requireArray(steer.aliases, "steer aliases")).toContain("tell");
109133
});
110134

111135
it("keeps focus as a local slash command", () => {
112-
expect(parseSlashCommand("/focus")).toMatchObject({
113-
command: { key: "focus", executeLocal: true },
114-
args: "",
115-
});
136+
expectParsedSlash("/focus", { key: "focus", executeLocal: true }, "");
116137
});
117138

118139
it("refreshes runtime commands from commands.list so docks, plugins, and direct skills appear", async () => {
@@ -154,23 +175,20 @@ describe("parseSlashCommand", () => {
154175
agentId: "main",
155176
});
156177

157-
expect(SLASH_COMMANDS.find((entry) => entry.name === "dock-discord")).toMatchObject({
178+
expectRecordFields(requireCommandByName("dock-discord"), "dock-discord command", {
158179
aliases: ["dock_discord"],
159180
category: "tools",
160181
executeLocal: false,
161182
});
162-
expect(SLASH_COMMANDS.find((entry) => entry.name === "dreaming")).toMatchObject({
183+
expectRecordFields(requireCommandByName("dreaming"), "dreaming command", {
163184
key: "dreaming",
164185
executeLocal: false,
165186
});
166-
expect(SLASH_COMMANDS.find((entry) => entry.name === "prose")).toMatchObject({
187+
expectRecordFields(requireCommandByName("prose"), "prose command", {
167188
key: "prose",
168189
executeLocal: false,
169190
});
170-
expect(parseSlashCommand("/dock_discord")).toMatchObject({
171-
command: { name: "dock-discord" },
172-
args: "",
173-
});
191+
expectParsedSlash("/dock_discord", { name: "dock-discord" }, "");
174192
});
175193

176194
it("does not let remote commands collide with reserved local commands", async () => {
@@ -200,12 +218,12 @@ describe("parseSlashCommand", () => {
200218
agentId: "main",
201219
});
202220

203-
expect(SLASH_COMMANDS.find((entry) => entry.name === "redirect")).toMatchObject({
221+
expectRecordFields(requireCommandByName("redirect"), "redirect command", {
204222
key: "redirect",
205223
executeLocal: true,
206224
description: "Abort and restart with a new message",
207225
});
208-
expect(SLASH_COMMANDS.find((entry) => entry.name === "kill")).toMatchObject({
226+
expectRecordFields(requireCommandByName("kill"), "kill command", {
209227
key: "kill",
210228
executeLocal: true,
211229
description: "Kill a running subagent (or all).",
@@ -239,14 +257,12 @@ describe("parseSlashCommand", () => {
239257
agentId: "main",
240258
});
241259

242-
expect(SLASH_COMMANDS.find((entry) => entry.name === "safe-name")).toMatchObject({
260+
expectRecordFields(requireCommandByName("safe-name"), "safe-name command", {
243261
name: "safe-name",
244262
});
245263
expect(SLASH_COMMANDS.find((entry) => entry.name === "prose now")).toBeUndefined();
246264
expect(SLASH_COMMANDS.find((entry) => entry.name === "bad:alias")).toBeUndefined();
247-
expect(parseSlashCommand("/safe-name")).toMatchObject({
248-
command: { name: "safe-name" },
249-
});
265+
expectParsedSlash("/safe-name", { name: "safe-name" }, "");
250266
});
251267

252268
it("caps remote command payload size and long metadata before it reaches UI state", async () => {
@@ -364,11 +380,11 @@ describe("parseSlashCommand", () => {
364380
client: { request } as never,
365381
agentId: "main",
366382
});
367-
expect(SLASH_COMMANDS.find((entry) => entry.name === "valid")).toMatchObject({
383+
expectRecordFields(requireCommandByName("valid"), "valid command", {
368384
name: "valid",
369385
description: "",
370386
});
371-
expect(SLASH_COMMANDS.find((entry) => entry.name === "pair")).toMatchObject({
387+
expectRecordFields(requireCommandByName("pair"), "pair command", {
372388
name: "pair",
373389
});
374390
});

0 commit comments

Comments
 (0)