import { describe, expect, it, vi } from "vitest"; import { _buildMarkdownPostRows, buildOutboundPayload, patchTextMessage, sendTextMessage, type FeishuRuntime, } from "../../src/feishu/client.js"; describe("Feishu markdown outbound payloads", () => { it("uses text messages for plain text", () => { const payload = buildOutboundPayload("hello from cph"); expect(payload.msgType).toBe("text"); expect(JSON.parse(payload.content)).toEqual({ text: "hello from cph" }); }); it("keeps fenced code blocks in separate post rows", () => { const content = "Before\n```ts\nconst x = 1;\n```\nAfter"; expect(_buildMarkdownPostRows(content)).toEqual([ [{ tag: "md", text: "Before\n" }], [{ tag: "md", text: "```ts\nconst x = 1;\n```" }], [{ tag: "md", text: "\nAfter" }], ]); }); it("forces markdown tables to plain text", () => { const content = "| A | B |\n|---|---|\n| 1 | 2 |"; const payload = buildOutboundPayload(content); expect(payload.msgType).toBe("text"); expect(JSON.parse(payload.content)).toEqual({ text: content }); }); it("uses post messages for markdown headers, bold text, and lists", () => { const content = "# Title\n\nThis is **important**.\n\n- first\n- second"; const payload = buildOutboundPayload(content); expect(payload.msgType).toBe("post"); expect(JSON.parse(payload.content)).toEqual({ zh_cn: { content: [[{ tag: "md", text: content }]], }, }); }); it.each([ ["header", "# Heading"], ["list", "- item"], ["code block", "```ts\nconst x = 1;\n```"], ["inline code", "Use `code` here"], ["bold", "**bold**"], ["italic", "_italic_"], ["strikethrough", "~~removed~~"], ["link", "[docs](https://example.com)"], ["blockquote", "> quoted"], ])("detects markdown %s syntax", (_name, content) => { expect(buildOutboundPayload(content).msgType).toBe("post"); }); it("falls back from rejected post payloads to stripped plain text", async () => { const messageCreate = vi .fn() .mockRejectedValueOnce(new Error("content format of the post type is incorrect")) .mockResolvedValueOnce({ data: { message_id: "message-1" } }); const rt = mockRuntime({ messageCreate }); await expect(sendTextMessage(rt, "chat-1", "# Title\n\n**bold** and `code`")).resolves.toBe("message-1"); expect(messageCreate).toHaveBeenCalledTimes(2); expect(messageCreate).toHaveBeenNthCalledWith(1, { params: { receive_id_type: "chat_id" }, data: { receive_id: "chat-1", msg_type: "post", content: JSON.stringify({ zh_cn: { content: [[{ tag: "md", text: "# Title\n\n**bold** and `code`" }]], }, }), }, }); expect(messageCreate).toHaveBeenNthCalledWith(2, { params: { receive_id_type: "chat_id" }, data: { receive_id: "chat-1", msg_type: "text", content: JSON.stringify({ text: "Title\n\nbold and code" }), }, }); }); it("patches markdown post rows as separate interactive card elements", async () => { const patch = vi.fn(async () => ({})); const rt = mockRuntime({ patch }); await patchTextMessage(rt, "message-1", "Before\n```ts\nconst x = 1;\n```\nAfter"); expect(patch).toHaveBeenCalledWith({ path: { message_id: "message-1" }, params: { msg_type: "interactive" }, data: { content: JSON.stringify({ elements: [ { tag: "div", text: { tag: "lark_md", content: "Before\n" } }, { tag: "div", text: { tag: "lark_md", content: "```ts\nconst x = 1;\n```" } }, { tag: "div", text: { tag: "lark_md", content: "\nAfter" } }, ], }), }, }); }); }); function mockRuntime(options: { readonly messageCreate?: (payload: unknown) => Promise; readonly patch?: (payload: unknown) => Promise; }): FeishuRuntime { return { client: { im: { v1: { message: { create: options.messageCreate ?? vi.fn(async () => ({ data: { message_id: "message-1" } })), patch: options.patch ?? vi.fn(async () => ({})), }, }, }, } as unknown as FeishuRuntime["client"], logger: { info() {}, warn() {}, error() {}, debug() {}, fatal() {}, child() { return this; }, level: "silent", } as unknown as FeishuRuntime["logger"], }; }