



















@@ -186,6 +186,20 @@ function sendStructuredCardCall(index = 0): Record<string, any> | undefined {
186186return calls[index]?.[0];
187187}
188188189+function commentThreadParams(index = 0): Record<string, any> | undefined {
190+const calls = deliverCommentThreadTextMock.mock.calls as unknown as Array<
191+[unknown, Record<string, any>]
192+>;
193+return calls[index]?.[1];
194+}
195+196+function cleanupReactionCall(index = 0): Record<string, any> | undefined {
197+const calls = cleanupAmbientCommentTypingReactionMock.mock.calls as unknown as Array<
198+[Record<string, any>]
199+>;
200+return calls[index]?.[0];
201+}
202+189203function expectFeishuResult(result: unknown, messageId: string) {
190204const typedResult = result as { channel?: string; messageId?: string } | undefined;
191205expect(typedResult?.channel).toBe("feishu");
@@ -530,25 +544,19 @@ describe("feishuOutbound.sendPayload native cards", () => {
530544});
531545532546const card = sendCardFeishuMock.mock.calls[0][0].card;
533-expect(card.body.elements).toEqual(
534-expect.arrayContaining([
535-{ tag: "markdown", content: 'Choose <at id="ou_1">' },
536-{
537-tag: "markdown",
538-content:
539-"<font color='grey'></font><at id=\"ou_2\">Injected</at></font>",
540-},
541-{
542-tag: "action",
543-actions: [
544-expect.objectContaining({
545-text: { tag: "plain_text", content: "Open" },
546-url: "https://example.com/path",
547-}),
548-],
549-},
550-]),
547+expect(card.body.elements[0]).toEqual({
548+tag: "markdown",
549+content: 'Choose <at id="ou_1">',
550+});
551+expect(card.body.elements[1]).toEqual({
552+tag: "markdown",
553+content: "<font color='grey'></font><at id=\"ou_2\">Injected</at></font>",
554+});
555+const actionElement = card.body.elements.find(
556+(element: { tag?: string }) => element.tag === "action",
551557);
558+expect(actionElement?.actions[0]?.text).toEqual({ tag: "plain_text", content: "Open" });
559+expect(actionElement?.actions[0]?.url).toBe("https://example.com/path");
552560expect(JSON.stringify(card)).not.toContain("javascript:");
553561});
554562@@ -602,10 +610,12 @@ describe("feishuOutbound.sendPayload native cards", () => {
602610{
603611tag: "action",
604612actions: [
605-expect.objectContaining({
613+{
614+tag: "button",
606615text: { tag: "plain_text", content: "Good link" },
616+type: "default",
607617url: "https://example.com",
608-}),
618+},
609619],
610620},
611621]);
@@ -629,23 +639,13 @@ describe("feishuOutbound.sendPayload native cards", () => {
629639},
630640});
631641632-expect(sendMediaFeishuMock).toHaveBeenCalledWith(
633-expect.objectContaining({
634-to: "chat_1",
635-mediaUrl: "/tmp/image.png",
636-mediaLocalRoots: ["/tmp"],
637-accountId: "main",
638-}),
639-);
640-expect(sendCardFeishuMock).toHaveBeenCalledWith(
641-expect.objectContaining({
642-to: "chat_1",
643-accountId: "main",
644-}),
645-);
646-expect(result).toEqual(
647-expect.objectContaining({ channel: "feishu", messageId: "native_card_msg" }),
648-);
642+expect(sendMediaCall()?.to).toBe("chat_1");
643+expect(sendMediaCall()?.mediaUrl).toBe("/tmp/image.png");
644+expect(sendMediaCall()?.mediaLocalRoots).toEqual(["/tmp"]);
645+expect(sendMediaCall()?.accountId).toBe("main");
646+expect(sendCardCall()?.to).toBe("chat_1");
647+expect(sendCardCall()?.accountId).toBe("main");
648+expectFeishuResult(result, "native_card_msg");
649649});
650650651651it("keeps text/media fallback behavior for non-card payloads, including local image text", async () => {
@@ -661,18 +661,12 @@ describe("feishuOutbound.sendPayload native cards", () => {
661661});
662662663663expect(sendCardFeishuMock).not.toHaveBeenCalled();
664-expect(sendMediaFeishuMock).toHaveBeenCalledWith(
665-expect.objectContaining({
666-to: "chat_1",
667-mediaUrl: file,
668-mediaLocalRoots: [dir],
669-accountId: "main",
670-}),
671-);
664+expect(sendMediaCall()?.to).toBe("chat_1");
665+expect(sendMediaCall()?.mediaUrl).toBe(file);
666+expect(sendMediaCall()?.mediaLocalRoots).toEqual([dir]);
667+expect(sendMediaCall()?.accountId).toBe("main");
672668expect(sendMessageFeishuMock).not.toHaveBeenCalled();
673-expect(result).toEqual(
674-expect.objectContaining({ channel: "feishu", messageId: "media_msg" }),
675-);
669+expectFeishuResult(result, "media_msg");
676670} finally {
677671await fs.rm(dir, { recursive: true, force: true });
678672}
@@ -693,13 +687,8 @@ describe("feishuOutbound.sendPayload native cards", () => {
693687});
694688695689expect(sendCardFeishuMock).not.toHaveBeenCalled();
696-expect(deliverCommentThreadTextMock).toHaveBeenCalledWith(
697-expect.anything(),
698-expect.objectContaining({
699-content: "Review this\n\n- Approve",
700-}),
701-);
702-expect(result).toEqual(expect.objectContaining({ channel: "feishu", messageId: "reply_msg" }));
690+expect(commentThreadParams()?.content).toBe("Review this\n\n- Approve");
691+expectFeishuResult(result, "reply_msg");
703692});
704693});
705694@@ -716,17 +705,12 @@ describe("feishuOutbound comment-thread routing", () => {
716705accountId: "main",
717706});
718707719-expect(deliverCommentThreadTextMock).toHaveBeenCalledWith(
720-expect.anything(),
721-expect.objectContaining({
722-file_token: "doxcn123",
723-file_type: "docx",
724-comment_id: "7623358762119646411",
725-content: "handled in thread",
726-}),
727-);
708+expect(commentThreadParams()?.file_token).toBe("doxcn123");
709+expect(commentThreadParams()?.file_type).toBe("docx");
710+expect(commentThreadParams()?.comment_id).toBe("7623358762119646411");
711+expect(commentThreadParams()?.content).toBe("handled in thread");
728712expect(sendMessageFeishuMock).not.toHaveBeenCalled();
729-expect(result).toEqual(expect.objectContaining({ channel: "feishu", messageId: "reply_msg" }));
713+expectFeishuResult(result, "reply_msg");
730714});
731715732716it("routes comment-thread code-block replies through deliverCommentThreadText instead of IM cards", async () => {
@@ -737,18 +721,13 @@ describe("feishuOutbound comment-thread routing", () => {
737721accountId: "main",
738722});
739723740-expect(deliverCommentThreadTextMock).toHaveBeenCalledWith(
741-expect.anything(),
742-expect.objectContaining({
743-file_token: "doxcn123",
744-file_type: "docx",
745-comment_id: "7623358762119646411",
746-content: "```ts\nconst x = 1\n```",
747-}),
748-);
724+expect(commentThreadParams()?.file_token).toBe("doxcn123");
725+expect(commentThreadParams()?.file_type).toBe("docx");
726+expect(commentThreadParams()?.comment_id).toBe("7623358762119646411");
727+expect(commentThreadParams()?.content).toBe("```ts\nconst x = 1\n```");
749728expect(sendStructuredCardFeishuMock).not.toHaveBeenCalled();
750729expect(sendMarkdownCardFeishuMock).not.toHaveBeenCalled();
751-expect(result).toEqual(expect.objectContaining({ channel: "feishu", messageId: "reply_msg" }));
730+expectFeishuResult(result, "reply_msg");
752731});
753732754733it("routes comment-thread replies through deliverCommentThreadText even when renderMode=card", async () => {
@@ -759,18 +738,13 @@ describe("feishuOutbound comment-thread routing", () => {
759738accountId: "main",
760739});
761740762-expect(deliverCommentThreadTextMock).toHaveBeenCalledWith(
763-expect.anything(),
764-expect.objectContaining({
765-file_token: "doxcn123",
766-file_type: "docx",
767-comment_id: "7623358762119646411",
768-content: "handled in thread",
769-}),
770-);
741+expect(commentThreadParams()?.file_token).toBe("doxcn123");
742+expect(commentThreadParams()?.file_type).toBe("docx");
743+expect(commentThreadParams()?.comment_id).toBe("7623358762119646411");
744+expect(commentThreadParams()?.content).toBe("handled in thread");
771745expect(sendStructuredCardFeishuMock).not.toHaveBeenCalled();
772746expect(sendMarkdownCardFeishuMock).not.toHaveBeenCalled();
773-expect(result).toEqual(expect.objectContaining({ channel: "feishu", messageId: "reply_msg" }));
747+expectFeishuResult(result, "reply_msg");
774748});
775749776750it("falls back to a text-only comment reply for media payloads", async () => {
@@ -782,14 +756,9 @@ describe("feishuOutbound comment-thread routing", () => {
782756accountId: "main",
783757});
784758785-expect(deliverCommentThreadTextMock).toHaveBeenCalledWith(
786-expect.anything(),
787-expect.objectContaining({
788-content: "see attachment\n\nhttps://example.com/file.png",
789-}),
790-);
759+expect(commentThreadParams()?.content).toBe("see attachment\n\nhttps://example.com/file.png");
791760expect(sendMediaFeishuMock).not.toHaveBeenCalled();
792-expect(result).toEqual(expect.objectContaining({ channel: "feishu", messageId: "reply_msg" }));
761+expectFeishuResult(result, "reply_msg");
793762});
794763795764it("preserves comment-thread routing when deliverCommentThreadText falls back to add_comment", async () => {
@@ -806,21 +775,11 @@ describe("feishuOutbound comment-thread routing", () => {
806775accountId: "main",
807776});
808777809-expect(deliverCommentThreadTextMock).toHaveBeenCalledWith(
810-expect.anything(),
811-expect.objectContaining({
812-file_token: "doxcn123",
813-file_type: "docx",
814-comment_id: "7623358762119646411",
815-content: "whole-comment follow-up",
816-}),
817-);
818-expect(result).toEqual(
819-expect.objectContaining({
820-channel: "feishu",
821-messageId: "reply_from_add_comment",
822-}),
823-);
778+expect(commentThreadParams()?.file_token).toBe("doxcn123");
779+expect(commentThreadParams()?.file_type).toBe("docx");
780+expect(commentThreadParams()?.comment_id).toBe("7623358762119646411");
781+expect(commentThreadParams()?.content).toBe("whole-comment follow-up");
782+expectFeishuResult(result, "reply_from_add_comment");
824783});
825784826785it("does not wait for ambient comment typing cleanup before sending comment-thread replies", async () => {
@@ -844,13 +803,11 @@ describe("feishuOutbound comment-thread routing", () => {
844803845804expect(status).toBe("done");
846805expect(deliverCommentThreadTextMock).toHaveBeenCalled();
847-expect(cleanupAmbientCommentTypingReactionMock).toHaveBeenCalledWith({
848-client: expect.anything(),
849-deliveryContext: {
850-channel: "feishu",
851-to: "comment:docx:doxcn123:7623358762119646411",
852-threadId: "reply_ambient_1",
853-},
806+expect(cleanupReactionCall()?.client).toBeDefined();
807+expect(cleanupReactionCall()?.deliveryContext).toEqual({
808+channel: "feishu",
809+to: "comment:docx:doxcn123:7623358762119646411",
810+threadId: "reply_ambient_1",
854811});
855812856813resolveCleanup?.(false);
@@ -872,14 +829,10 @@ describe("feishuOutbound.sendText replyToId forwarding", () => {
872829accountId: "main",
873830});
874831875-expect(sendMessageFeishuMock).toHaveBeenCalledWith(
876-expect.objectContaining({
877-to: "chat_1",
878-text: "hello",
879-replyToMessageId: "om_reply_target",
880-accountId: "main",
881-}),
882-);
832+expect(sendMessageCall()?.to).toBe("chat_1");
833+expect(sendMessageCall()?.text).toBe("hello");
834+expect(sendMessageCall()?.replyToMessageId).toBe("om_reply_target");
835+expect(sendMessageCall()?.accountId).toBe("main");
883836});
884837885838it("forwards replyToId to sendStructuredCardFeishu when renderMode=card", async () => {
@@ -891,11 +844,7 @@ describe("feishuOutbound.sendText replyToId forwarding", () => {
891844accountId: "main",
892845});
893846894-expect(sendStructuredCardFeishuMock).toHaveBeenCalledWith(
895-expect.objectContaining({
896-replyToMessageId: "om_reply_target",
897-}),
898-);
847+expect(sendStructuredCardCall()?.replyToMessageId).toBe("om_reply_target");
899848});
900849901850it("does not pass replyToMessageId when replyToId is absent", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。