diff --git a/extensions/line/src/markdown-to-line.test.ts b/extensions/line/src/markdown-to-line.test.ts index a663c21f8bcf..a2632ce546af 100644 --- a/extensions/line/src/markdown-to-line.test.ts +++ b/extensions/line/src/markdown-to-line.test.ts @@ -255,6 +255,79 @@ print("done") expect(result.text).not.toContain("[here]"); }); + it.each([ + { name: "plain two-column at the receipt limit", columns: 2, rowCount: 12, styled: false }, + { name: "plain two-column beyond the receipt limit", columns: 2, rowCount: 13, styled: false }, + { + name: "styled two-column at the former generic limit", + columns: 2, + rowCount: 10, + styled: true, + }, + { + name: "styled two-column beyond the former generic limit", + columns: 2, + rowCount: 11, + styled: true, + }, + { name: "three-column at the former generic limit", columns: 3, rowCount: 10, styled: false }, + { + name: "three-column beyond the former generic limit", + columns: 3, + rowCount: 11, + styled: false, + }, + ])("keeps every row visible for $name", ({ columns, rowCount, styled }) => { + const headers = Array.from({ length: columns }, (_, column) => `Header ${column + 1}`); + const rows = Array.from({ length: rowCount }, (_, row) => { + const cells = Array.from({ length: columns }, (_unused, column) => { + const value = `row-${row + 1}-column-${column + 1}`; + if (styled && column === 0) { + return `**${value}**`; + } + return styled && column === 1 ? `[${value}](https://example.test/rows/${row + 1})` : value; + }); + return `| ${cells.join(" | ")} |`; + }); + const table = [ + `| ${headers.join(" | ")} |`, + `| ${headers.map(() => "---").join(" | ")} |`, + ...rows, + ].join("\n"); + + const result = processLineMessage(table); + const delivered = JSON.stringify(result.flexMessages); + + expect(result.flexMessages).toHaveLength(1); + for (let row = 1; row <= rowCount; row += 1) { + for (let column = 1; column <= columns; column += 1) { + expect(delivered).toContain(`row-${row}-column-${column}`); + } + if (styled) { + expect(delivered).toContain(`https://example.test/rows/${row}`); + } + } + expect( + Buffer.byteLength(JSON.stringify(result.flexMessages[0]?.contents), "utf8"), + ).toBeLessThanOrEqual(30_000); + expect(result.segments).toBeUndefined(); + }); + + it("falls back with every row when a complete table exceeds the provider size limit", () => { + const rows = Array.from( + { length: 13 }, + (_, row) => `| row-${row + 1} | ${row === 12 ? "x".repeat(30_000) : `value-${row + 1}`} |`, + ); + const result = processLineMessage(["| Name | Value |", "|---|---|", ...rows].join("\n")); + + expect(result.flexMessages).toHaveLength(0); + for (let row = 1; row <= rows.length; row += 1) { + expect(result.text).toContain(`row-${row}`); + } + expect(result.text).toContain("x".repeat(30_000)); + expect(result.segments).toEqual([{ type: "text", text: result.text }]); + }); + it("keeps an oversized table visible as canonical bullet text instead of an invalid Flex bubble", () => { const value = "x".repeat(30_000); const result = processLineMessage( diff --git a/extensions/line/src/markdown-to-line.ts b/extensions/line/src/markdown-to-line.ts index 37709693f0e4..f3646b6ff117 100644 --- a/extensions/line/src/markdown-to-line.ts +++ b/extensions/line/src/markdown-to-line.ts @@ -329,7 +329,8 @@ export function convertTableToFlexBubble(table: MarkdownTable): FlexBubble { headerCells.some((cell) => cell.hasMarkup) || rowCells.some((row) => row.some((cell) => cell.hasMarkup)); - if (table.headers.length === 2 && !hasInlineMarkup) { + // Receipt cards clip after 12 rows; larger tables need the complete generic layout. + if (table.headers.length === 2 && !hasInlineMarkup && rowCells.length <= 12) { return createReceiptCard({ title: headerCells.map((cell) => cell.text).join(" / "), items: rowCells.map((row) => ({ @@ -355,7 +356,7 @@ export function convertTableToFlexBubble(table: MarkdownTable): FlexBubble { paddingBottom: "sm", } as FlexBox; - const dataRows: FlexComponent[] = rowCells.slice(0, 10).map((row, rowIndex) => ({ + const dataRows: FlexComponent[] = rowCells.map((row, rowIndex) => ({ type: "box", layout: "horizontal", contents: table.headers.map((_, colIndex) => {