





















@@ -112,6 +112,11 @@ afterAll(() => {
112112113113describe("createFeishuReplyDispatcher streaming behavior", () => {
114114type ReplyDispatcherArgs = Parameters<typeof createFeishuReplyDispatcher>[0];
115+type TypingDispatcherOptions = {
116+onReplyStart?: () => Promise<void> | void;
117+onIdle?: () => Promise<void> | void;
118+deliver: (payload: { text: string }, meta: { kind: string }) => Promise<void> | void;
119+};
115120116121beforeEach(() => {
117122vi.clearAllMocks();
@@ -177,7 +182,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
177182chatId: "oc_chat",
178183});
179184180-return createReplyDispatcherWithTypingMock.mock.calls.at(0)?.[0];
185+return firstMockArg(createReplyDispatcherWithTypingMock, "reply dispatcher options");
181186}
182187183188function createRuntimeLogger() {
@@ -227,7 +232,39 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
227232callIndex = 0,
228233argIndex = 0,
229234): Record<string, unknown> {
230-return expectRecordFields(mock.mock.calls.at(callIndex)?.[argIndex], label, expected);
235+return expectRecordFields(mockArg(mock, callIndex, argIndex, label), label, expected);
236+}
237+238+function mockArg(
239+mock: ReturnType<typeof vi.fn>,
240+callIndex: number,
241+argIndex: number,
242+label: string,
243+) {
244+const call = mock.mock.calls[callIndex];
245+if (!call) {
246+throw new Error(`missing ${label} call ${callIndex + 1}`);
247+}
248+return call[argIndex];
249+}
250+251+function firstMockArg(mock: ReturnType<typeof vi.fn>, label: string, argIndex = 0) {
252+return mockArg(mock, 0, argIndex, label);
253+}
254+255+function firstTypingDispatcherOptions(): TypingDispatcherOptions {
256+return firstMockArg(
257+createReplyDispatcherWithTypingMock,
258+"reply dispatcher options",
259+) as TypingDispatcherOptions;
260+}
261+262+function firstStreamingCloseText(instanceIndex = 0): string {
263+const close = streamingInstances[instanceIndex]?.close;
264+if (!close) {
265+throw new Error(`Expected streaming instance ${instanceIndex}`);
266+}
267+return String(firstMockArg(close, "streaming close"));
231268}
232269233270function expectLastMockArgFields(
@@ -248,9 +285,13 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
248285if (!start) {
249286throw new Error(`Expected streaming instance ${instanceIndex}`);
250287}
251-expect(start.mock.calls.at(0)?.[0]).toBe("oc_chat");
252-expect(start.mock.calls.at(0)?.[1]).toBe("chat_id");
253-return expectRecordFields(start.mock.calls.at(0)?.[2], "streaming start options", expected);
288+expect(firstMockArg(start, "streaming start")).toBe("oc_chat");
289+expect(firstMockArg(start, "streaming start", 1)).toBe("chat_id");
290+return expectRecordFields(
291+firstMockArg(start, "streaming start", 2),
292+"streaming start options",
293+expected,
294+);
254295}
255296256297function streamingUpdateTexts(instanceIndex = 0): string[] {
@@ -280,7 +321,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
280321replyToMessageId: "om_parent",
281322});
282323283-const options = createReplyDispatcherWithTypingMock.mock.calls.at(0)?.[0];
324+const options = firstTypingDispatcherOptions();
284325await options.onReplyStart?.();
285326286327expect(addTypingIndicatorMock).not.toHaveBeenCalled();
@@ -296,7 +337,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
296337messageCreateTimeMs: Date.now() - 3 * 60_000,
297338});
298339299-const options = createReplyDispatcherWithTypingMock.mock.calls.at(0)?.[0];
340+const options = firstTypingDispatcherOptions();
300341await options.onReplyStart?.();
301342302343expect(addTypingIndicatorMock).not.toHaveBeenCalled();
@@ -312,7 +353,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
312353messageCreateTimeMs: Math.floor((Date.now() - 3 * 60_000) / 1000),
313354});
314355315-const options = createReplyDispatcherWithTypingMock.mock.calls.at(0)?.[0];
356+const options = firstTypingDispatcherOptions();
316357await options.onReplyStart?.();
317358318359expect(addTypingIndicatorMock).not.toHaveBeenCalled();
@@ -328,7 +369,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
328369messageCreateTimeMs: Date.now() - 30_000,
329370});
330371331-const options = createReplyDispatcherWithTypingMock.mock.calls.at(0)?.[0];
372+const options = firstTypingDispatcherOptions();
332373await options.onReplyStart?.();
333374334375expect(addTypingIndicatorMock).toHaveBeenCalledTimes(1);
@@ -353,7 +394,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
353394await options.deliver({ text: "plain text" }, { kind: "final" });
354395355396expect(sendMessageFeishuMock).toHaveBeenCalledTimes(1);
356-expect(sendMessageFeishuMock.mock.calls.at(0)?.[0]).not.toHaveProperty("mentions");
397+expect(firstMockArg(sendMessageFeishuMock, "send message params")).not.toHaveProperty(
398+"mentions",
399+);
357400});
358401359402it("does not attach automatic mentions to card replies", async () => {
@@ -374,7 +417,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
374417await options.deliver({ text: "card text" }, { kind: "final" });
375418376419expect(sendStructuredCardFeishuMock).toHaveBeenCalledTimes(1);
377-expect(sendStructuredCardFeishuMock.mock.calls.at(0)?.[0]).not.toHaveProperty("mentions");
420+expect(firstMockArg(sendStructuredCardFeishuMock, "structured card params")).not.toHaveProperty(
421+"mentions",
422+);
378423});
379424380425it("suppresses internal block payload delivery", async () => {
@@ -1017,7 +1062,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
10171062}
1018106310191064expect(streamingInstances[0].close).toHaveBeenCalledTimes(1);
1020-const closeArg = streamingInstances[0].close.mock.calls.at(0)?.[0] as string;
1065+const closeArg = firstStreamingCloseText();
10211066expect(closeArg).toContain("> 💭 **Thinking**");
10221067expect(closeArg).toContain("---");
10231068expect(closeArg).toContain("answer part final");
@@ -1075,7 +1120,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
1075112010761121expect(streamingInstances).toHaveLength(1);
10771122expect(streamingInstances[0].close).toHaveBeenCalledTimes(1);
1078-const closeArg = streamingInstances[0].close.mock.calls.at(0)?.[0] as string;
1123+const closeArg = firstStreamingCloseText();
10791124expect(closeArg).toContain("> 💭 **Thinking**");
10801125expect(closeArg).toContain("> deep thought");
10811126expect(closeArg).not.toContain("Reasoning:");
@@ -1095,7 +1140,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
10951140await options.onIdle?.();
1096114110971142expect(streamingInstances).toHaveLength(1);
1098-const closeArg = streamingInstances[0].close.mock.calls.at(0)?.[0] as string;
1143+const closeArg = firstStreamingCloseText();
10991144expect(closeArg).not.toContain("Thinking");
11001145expect(closeArg).toBe("```ts\ncode\n```");
11011146});
@@ -1432,7 +1477,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
14321477chatId: "oc_chat",
14331478});
143414791435-const options = createReplyDispatcherWithTypingMock.mock.calls.at(0)?.[0];
1480+const options = firstTypingDispatcherOptions();
1436148114371482// First deliver with markdown triggers startStreaming - which will fail
14381483await options.deliver({ text: "```ts\nconst x = 1\n```" }, { kind: "final" });
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。