| 1 | import test from "node:test" ; |
| 2 | import assert from "node:assert/strict" ; |
| 3 | import { FakeNode, setup } from "./rooms.test.mjs" ; |
| 4 | |
| 5 | test("chat tables render semantic headers, alignment and uneven rows" , () => { |
| 6 | const s = setup(); |
| 7 | const body = s.rooms.chatMarkdown(new FakeNode("div" ), "| Name | Notes | Score |\n| :--- | :---: | ---: |\n| Ada | **ready** | 10 |\n| Grace | `a|b` |\n| Alan | left" ); |
| 8 | const region = body.children[0], table = region.querySelector("table" ); |
| 9 | assert.equal(region.getAttribute("data-markdown-table" ), "" ); |
| 10 | assert.equal(region.getAttribute("role" ), "region" ); |
| 11 | assert.equal(region.getAttribute("aria-label" ), "Table" ); |
| 12 | assert.equal(region.getAttribute("tabindex" ), "0" ); |
| 13 | assert.deepEqual(table.querySelectorAll("th" ).map(cell => [cell.getAttribute("scope" ), cell.getAttribute("data-align" )]), [["col" , "left" ], ["col" , "center" ], ["col" , "right" ]]); |
| 14 | assert.equal(table.querySelectorAll("tbody tr" ).length, 3); |
| 15 | assert.equal(table.querySelectorAll("tbody tr" )[1].children.length, 3); |
| 16 | assert.equal(table.querySelectorAll("tbody tr" )[1].children[1].querySelector("code" ).textContent, "a|b" ); |
| 17 | assert.equal(table.querySelectorAll("tbody tr" )[2].children[2].textContent, "" ); |
| 18 | assert.equal(table.querySelector("strong" ).textContent, "ready" ); |
| 19 | }); |
| 20 | |
| 21 | test("chat tables accept escaped pipes and keep inline content safe" , () => { |
| 22 | const s = setup(); |
| 23 | const body = s.rooms.chatMarkdown(new FakeNode("div" ), "Label | Link\n--- | ---\nA \\| B | [go](javascript:alert(1)) <img>" ); |
| 24 | const table = body.querySelector("table" ); |
| 25 | assert.equal(table.querySelector("tbody td" ).textContent, "A | B" ); |
| 26 | assert.equal(table.querySelectorAll("a" ).length, 0); |
| 27 | assert.equal(table.textContent.includes("<img>" ), true); |
| 28 | }); |
| 29 | |
| 30 | test("chat tables preserve single columns and distinguish escaped trailing pipes" , () => { |
| 31 | const s = setup(); |
| 32 | const body = s.rooms.chatMarkdown(new FakeNode("div" ), "| Value |\n| --- |\n| `a\\|b` |" ); |
| 33 | assert.equal(body.querySelectorAll("table" ).length, 1); |
| 34 | assert.equal(body.querySelector("th" ).textContent, "Value" ); |
| 35 | assert.equal(body.querySelector("tbody code" ).textContent, "a|b" ); |
| 36 | const prose = s.rooms.chatMarkdown(new FakeNode("div" ), "A \\\\| B\n| --- |" ); |
| 37 | assert.equal(prose.querySelectorAll("table" ).length, 0); |
| 38 | }); |
| 39 | |
| 40 | test("malformed tables and tables in fenced code remain prose or code" , () => { |
| 41 | const s = setup(); |
| 42 | const body = s.rooms.chatMarkdown(new FakeNode("div" ), "| Header | Other\n| -- | ---\n\n```\n| A | B |\n| --- | --- |\n```" ); |
| 43 | assert.equal(body.querySelectorAll("[data-markdown-table]" ).length, 0); |
| 44 | assert.equal(body.querySelectorAll("pre" ).length, 1); |
| 45 | assert.equal(body.querySelector("p" ).textContent, "| Header | Other| -- | ---" ); |
| 46 | assert.equal(body.querySelector("pre" ).textContent.includes("| A | B |" ), true); |
| 47 | }); |
| 48 | |
| 49 | test("rerendering edited chat content creates a fresh table node" , () => { |
| 50 | const s = setup(); |
| 51 | const body = new FakeNode("div" ); |
| 52 | s.rooms.chatMarkdown(body, "| Before | State |\n| --- | --- |\n| old | draft |" ); |
| 53 | assert.equal(body.querySelector("tbody td" ).textContent, "old" ); |
| 54 | body.replaceChildren(); |
| 55 | s.rooms.chatMarkdown(body, "| After | State |\n| --- | --- |\n| new | sent |" ); |
| 56 | assert.equal(body.querySelector("th" ).textContent, "After" ); |
| 57 | assert.equal(body.querySelector("tbody td" ).textContent, "new" ); |
| 58 | }); |
| 59 | |
| 60 | test("tables stop before headings and preserve backtick and tilde fences" , () => { |
| 61 | const s = setup(); |
| 62 | for (const marker of ["```" , " ~~~~" ]) { |
| 63 | const body = s.rooms.chatMarkdown(new FakeNode("div" ), "| Name | Value |\n| --- | --- |\n| one | two |\n" + marker + "text | example\n| A | B |\n|---|---|\n" + marker + "\n# Heading | text" ); |
| 64 | assert.equal(body.querySelectorAll("table" ).length, 1); |
| 65 | assert.equal(body.querySelector("th" ).getAttribute("data-align" ), null); |
| 66 | assert.equal(body.querySelectorAll("tbody tr" ).length, 1); |
| 67 | assert.equal(body.querySelector("pre" ).textContent, "| A | B |\n|---|---|" ); |
| 68 | assert.equal(body.children.find(node => node.localName === "h1" ).textContent, "Heading | text" ); |
| 69 | } |
| 70 | }); |