


















@@ -16,6 +16,32 @@ function expectForwardedCfg(invoke: ReturnType<typeof createInvokeSpy>, cfg: unk
1616expect(invoke.mock.calls[0]?.[1]).toBe(cfg);
1717}
181819+function firstAction(invoke: ReturnType<typeof createInvokeSpy>) {
20+const action = invoke.mock.calls[0]?.[0];
21+if (!action || typeof action !== "object") {
22+throw new Error("expected first invoke action");
23+}
24+return action;
25+}
26+27+function blockAt(action: Record<string, unknown>, index: number) {
28+const blocks = action.blocks as Array<Record<string, unknown>> | undefined;
29+const block = blocks?.[index];
30+if (!block) {
31+throw new Error(`expected Slack block ${index}`);
32+}
33+return block;
34+}
35+36+function elementAt(block: Record<string, unknown>, index: number) {
37+const elements = block.elements as Array<Record<string, unknown>> | undefined;
38+const element = elements?.[index];
39+if (!element) {
40+throw new Error(`expected Slack block element ${index}`);
41+}
42+return element;
43+}
44+1945describe("handleSlackMessageAction", () => {
2046it("merges presentation and interactive blocks when sending", async () => {
2147const invoke = createInvokeSpy();
@@ -44,16 +70,11 @@ describe("handleSlackMessageAction", () => {
4470invoke: invoke as never,
4571});
467247-const action = invoke.mock.calls[0]?.[0] as {
48-blocks?: Array<{ type?: string; elements?: Array<{ value?: string }> }>;
49-};
50-expect(action.blocks).toEqual([
51-expect.objectContaining({ type: "section" }),
52-expect.objectContaining({
53-type: "actions",
54-elements: [expect.objectContaining({ value: "approve" })],
55-}),
56-]);
73+const action = firstAction(invoke);
74+expect(blockAt(action, 0).type).toBe("section");
75+const actionsBlock = blockAt(action, 1);
76+expect(actionsBlock.type).toBe("actions");
77+expect(elementAt(actionsBlock, 0).value).toBe("approve");
5778});
58795980it("keeps generated Slack control ids unique when presentation and interactive controls are merged", async () => {
@@ -88,23 +109,13 @@ describe("handleSlackMessageAction", () => {
88109invoke: invoke as never,
89110});
9011191-const action = invoke.mock.calls[0]?.[0] as {
92-blocks?: Array<{
93-block_id?: string;
94-elements?: Array<{ action_id?: string; value?: string }>;
95-}>;
96-};
97-98-expect(action.blocks).toEqual([
99-expect.objectContaining({
100-block_id: "openclaw_reply_buttons_1",
101-elements: [expect.objectContaining({ action_id: "openclaw:reply_button:1:1" })],
102-}),
103-expect.objectContaining({
104-block_id: "openclaw_reply_buttons_2",
105-elements: [expect.objectContaining({ action_id: "openclaw:reply_button:2:1" })],
106-}),
107-]);
112+const action = firstAction(invoke);
113+const firstButtons = blockAt(action, 0);
114+expect(firstButtons.block_id).toBe("openclaw_reply_buttons_1");
115+expect(elementAt(firstButtons, 0).action_id).toBe("openclaw:reply_button:1:1");
116+const secondButtons = blockAt(action, 1);
117+expect(secondButtons.block_id).toBe("openclaw_reply_buttons_2");
118+expect(elementAt(secondButtons, 0).action_id).toBe("openclaw:reply_button:2:1");
108119});
109120110121it("passes media and rendered interactive blocks through for split Slack delivery", async () => {
@@ -134,23 +145,16 @@ describe("handleSlackMessageAction", () => {
134145});
135146136147expect(invoke).toHaveBeenCalledOnce();
137-expect(invoke).toHaveBeenCalledWith(
138-expect.objectContaining({
139-action: "sendMessage",
140-to: "channel:C1",
141-content: "Approval required",
142-mediaUrl: "https://example.com/report.md",
143-blocks: [
144-expect.objectContaining({
145-type: "actions",
146-elements: [expect.objectContaining({ value: "approve" })],
147-}),
148-],
149-}),
150-cfg,
151-undefined,
152-);
148+const action = firstAction(invoke);
149+expect(action.action).toBe("sendMessage");
150+expect(action.to).toBe("channel:C1");
151+expect(action.content).toBe("Approval required");
152+expect(action.mediaUrl).toBe("https://example.com/report.md");
153+const actionsBlock = blockAt(action, 0);
154+expect(actionsBlock.type).toBe("actions");
155+expect(elementAt(actionsBlock, 0).value).toBe("approve");
153156expectForwardedCfg(invoke, cfg);
157+expect(invoke.mock.calls[0]?.[2]).toBeUndefined();
154158});
155159156160it("passes replyBroadcast through for Slack thread sends", async () => {
@@ -172,17 +176,14 @@ describe("handleSlackMessageAction", () => {
172176invoke: invoke as never,
173177});
174178175-expect(invoke).toHaveBeenCalledWith(
176-expect.objectContaining({
177-action: "sendMessage",
178-to: "channel:C1",
179-content: "Visible from the channel",
180-threadTs: "111.222",
181-replyBroadcast: true,
182-}),
183-cfg,
184-undefined,
185-);
179+const action = firstAction(invoke);
180+expect(action.action).toBe("sendMessage");
181+expect(action.to).toBe("channel:C1");
182+expect(action.content).toBe("Visible from the channel");
183+expect(action.threadTs).toBe("111.222");
184+expect(action.replyBroadcast).toBe(true);
185+expectForwardedCfg(invoke, cfg);
186+expect(invoke.mock.calls[0]?.[2]).toBeUndefined();
186187});
187188188189it("passes topLevel through so same-channel Slack sends can suppress thread inheritance", async () => {
@@ -203,17 +204,14 @@ describe("handleSlackMessageAction", () => {
203204invoke: invoke as never,
204205});
205206206-expect(invoke).toHaveBeenCalledWith(
207-expect.objectContaining({
208-action: "sendMessage",
209-to: "channel:C1",
210-content: "Visible in the parent channel",
211-threadTs: undefined,
212-topLevel: true,
213-}),
214-cfg,
215-undefined,
216-);
207+const action = firstAction(invoke);
208+expect(action.action).toBe("sendMessage");
209+expect(action.to).toBe("channel:C1");
210+expect(action.content).toBe("Visible in the parent channel");
211+expect(action.threadTs).toBeUndefined();
212+expect(action.topLevel).toBe(true);
213+expectForwardedCfg(invoke, cfg);
214+expect(invoke.mock.calls[0]?.[2]).toBeUndefined();
217215});
218216219217it("treats threadId null as a Slack top-level send request", async () => {
@@ -234,15 +232,12 @@ describe("handleSlackMessageAction", () => {
234232invoke: invoke as never,
235233});
236234237-expect(invoke).toHaveBeenCalledWith(
238-expect.objectContaining({
239-action: "sendMessage",
240-threadTs: undefined,
241-topLevel: true,
242-}),
243-cfg,
244-undefined,
245-);
235+const action = firstAction(invoke);
236+expect(action.action).toBe("sendMessage");
237+expect(action.threadTs).toBeUndefined();
238+expect(action.topLevel).toBe(true);
239+expectForwardedCfg(invoke, cfg);
240+expect(invoke.mock.calls[0]?.[2]).toBeUndefined();
246241});
247242248243it("maps upload-file to the internal uploadFile action", async () => {
@@ -266,20 +261,16 @@ describe("handleSlackMessageAction", () => {
266261invoke: invoke as never,
267262});
268263269-expect(invoke).toHaveBeenCalledWith(
270-expect.objectContaining({
271-action: "uploadFile",
272-to: "user:U1",
273-filePath: "/tmp/report.png",
274-initialComment: "fresh build",
275-filename: "build.png",
276-title: "Build Screenshot",
277-threadTs: "111.222",
278-}),
279-cfg,
280-undefined,
281-);
264+const action = firstAction(invoke);
265+expect(action.action).toBe("uploadFile");
266+expect(action.to).toBe("user:U1");
267+expect(action.filePath).toBe("/tmp/report.png");
268+expect(action.initialComment).toBe("fresh build");
269+expect(action.filename).toBe("build.png");
270+expect(action.title).toBe("Build Screenshot");
271+expect(action.threadTs).toBe("111.222");
282272expectForwardedCfg(invoke, cfg);
273+expect(invoke.mock.calls[0]?.[2]).toBeUndefined();
283274});
284275285276it("rejects replyBroadcast for upload-file", async () => {
@@ -320,18 +311,14 @@ describe("handleSlackMessageAction", () => {
320311invoke: invoke as never,
321312});
322313323-expect(invoke).toHaveBeenCalledWith(
324-expect.objectContaining({
325-action: "uploadFile",
326-to: "C1",
327-filePath: "/tmp/chart.png",
328-initialComment: "chart attached",
329-threadTs: "333.444",
330-}),
331-cfg,
332-undefined,
333-);
314+const action = firstAction(invoke);
315+expect(action.action).toBe("uploadFile");
316+expect(action.to).toBe("C1");
317+expect(action.filePath).toBe("/tmp/chart.png");
318+expect(action.initialComment).toBe("chart attached");
319+expect(action.threadTs).toBe("333.444");
334320expectForwardedCfg(invoke, cfg);
321+expect(invoke.mock.calls[0]?.[2]).toBeUndefined();
335322});
336323337324it("maps upload-file path alias to filePath", async () => {
@@ -352,17 +339,13 @@ describe("handleSlackMessageAction", () => {
352339invoke: invoke as never,
353340});
354341355-expect(invoke).toHaveBeenCalledWith(
356-expect.objectContaining({
357-action: "uploadFile",
358-to: "channel:C1",
359-filePath: "/tmp/report.txt",
360-initialComment: "path alias",
361-}),
362-cfg,
363-undefined,
364-);
342+const action = firstAction(invoke);
343+expect(action.action).toBe("uploadFile");
344+expect(action.to).toBe("channel:C1");
345+expect(action.filePath).toBe("/tmp/report.txt");
346+expect(action.initialComment).toBe("path alias");
365347expectForwardedCfg(invoke, cfg);
348+expect(invoke.mock.calls[0]?.[2]).toBeUndefined();
366349});
367350368351it("forwards messageId for read actions", async () => {
@@ -381,14 +364,11 @@ describe("handleSlackMessageAction", () => {
381364invoke: invoke as never,
382365});
383366384-expect(invoke).toHaveBeenCalledWith(
385-expect.objectContaining({
386-action: "readMessages",
387-channelId: "C1",
388-messageId: "1712345678.654321",
389-}),
390-{},
391-);
367+const action = firstAction(invoke);
368+expect(action.action).toBe("readMessages");
369+expect(action.channelId).toBe("C1");
370+expect(action.messageId).toBe("1712345678.654321");
371+expect(invoke.mock.calls[0]?.[1]).toEqual({});
392372});
393373394374it("requires filePath, path, or media for upload-file", async () => {
@@ -425,15 +405,11 @@ describe("handleSlackMessageAction", () => {
425405invoke: invoke as never,
426406});
427407428-expect(invoke).toHaveBeenCalledWith(
429-expect.objectContaining({
430-action: "downloadFile",
431-fileId: "F123",
432-channelId: "C1",
433-threadId: "111.222",
434-}),
435-cfg,
436-);
408+const action = firstAction(invoke);
409+expect(action.action).toBe("downloadFile");
410+expect(action.fileId).toBe("F123");
411+expect(action.channelId).toBe("C1");
412+expect(action.threadId).toBe("111.222");
437413expectForwardedCfg(invoke, cfg);
438414});
439415@@ -455,15 +431,11 @@ describe("handleSlackMessageAction", () => {
455431invoke: invoke as never,
456432});
457433458-expect(invoke).toHaveBeenCalledWith(
459-expect.objectContaining({
460-action: "downloadFile",
461-fileId: "F999",
462-channelId: "channel:C2",
463-threadId: "333.444",
464-}),
465-cfg,
466-);
434+const action = firstAction(invoke);
435+expect(action.action).toBe("downloadFile");
436+expect(action.fileId).toBe("F999");
437+expect(action.channelId).toBe("channel:C2");
438+expect(action.threadId).toBe("333.444");
467439expectForwardedCfg(invoke, cfg);
468440});
469441此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。