diff --git a/src/cli/cliOutputFormatter.ts b/src/cli/cliOutputFormatter.ts index 5e9afa58d5..37ef0b3338 100644 --- a/src/cli/cliOutputFormatter.ts +++ b/src/cli/cliOutputFormatter.ts @@ -107,7 +107,11 @@ const formatObjectTable = ( } return renderTable( headers, - items.map((item) => headers.map((key) => markdownCell(item[key]))), + items.map((item) => + headers.map((key) => + markdownCell(Object.hasOwn(item, key) ? item[key] : undefined), + ), + ), ); }; diff --git a/tests/boundary/cli/filteredArrayOutput.test.ts b/tests/boundary/cli/filteredArrayOutput.test.ts index cedf2e0789..beec75c0e1 100644 --- a/tests/boundary/cli/filteredArrayOutput.test.ts +++ b/tests/boundary/cli/filteredArrayOutput.test.ts @@ -22,11 +22,16 @@ const freezeRecursively = (value: T): T => { const runFilteredCli = async ( value: unknown, format: "json" | "jsonl" | "yaml" | "toon" | "md", - filter: string, + filter?: string, ): Promise => { const cli = Cli.create("filtered-array-test", { sync: false }); cli.command("rows", { run: () => value }); - const args = ["rows", "--format", format, "--filter-output", filter]; + const args = [ + "rows", + "--format", + format, + ...(filter === undefined ? [] : ["--filter-output", filter]), + ]; const chunks: Buffer[] = []; const output = createCliResultOutput( parseCliOutputArguments(args), @@ -54,6 +59,38 @@ const runFilteredCli = async ( return Buffer.concat(chunks).toString("utf8"); }; +it("renders only each record's own fields in heterogeneous Markdown tables", async () => { + const value: unknown = JSON.parse( + '[{"name":"first","constructor":"ctor","toString":"text","__proto__":"proto","enabled":false,"count":0},{"name":"second"},{"name":"third","constructor":0,"toString":false,"__proto__":null}]', + ); + freezeRecursively(value); + const json = await runFilteredCli(value, "json"); + expect(JSON.parse(json)).toEqual(value); + const markdown = await runFilteredCli(value, "md"); + const rows = markdown + .trim() + .split("\n") + .map((line) => + line + .split("|") + .slice(1, -1) + .map((cell) => cell.trim()), + ); + expect(rows[0]).toEqual([ + "name", + "constructor", + "toString", + "__proto__", + "enabled", + "count", + ]); + expect(rows.slice(2)).toEqual([ + ["first", "ctor", "text", "proto", "false", "0"], + ["second", "", "", "", "", ""], + ["third", "0", "false", "null", "", ""], + ]); +}); + it.each( (["json", "jsonl"] as const).flatMap((format) => [false, true].map((full) => ({ format, full })),