| 1 | import assert from "node:assert/strict"; |
| 2 | import { readFileSync } from "node:fs"; |
| 3 | import { test } from "node:test"; |
| 4 | |
| 5 | import { rehypeTaskLines } from "./markdown-plugins.ts"; |
| 6 | import { markdownTree } from "./markdown-tree.ts"; |
| 7 | import { taskCheckOf } from "./task-check.ts"; |
| 8 | import { taskList } from "@g1t/contracts/tasks"; |
| 9 | import rehypeRaw from "rehype-raw"; |
| 10 | import rehypeSanitize, { defaultSchema } from "rehype-sanitize"; |
| 11 | import remarkGfm from "remark-gfm"; |
| 12 | import { visit } from "unist-util-visit"; |
| 13 | |
| 14 | function form(fields: Record<string, string>): FormData { |
| 15 | const data = new FormData(); |
| 16 | for (const [key, value] of Object.entries(fields)) data.set(key, value); |
| 17 | return data; |
| 18 | } |
| 19 | |
| 20 | test("reads the box a form names", () => { |
| 21 | assert.deepEqual(taskCheckOf(form({ task: "2", expected: "Write the docs", checked: "1" })), { task: 2, expected: "Write the docs", checked: true }); |
| 22 | assert.deepEqual(taskCheckOf(form({ task: "3", checked: "" })), { task: 3, checked: false }); |
| 23 | // Nothing usable is left out, for the work service to refuse. |
| 24 | assert.deepEqual(taskCheckOf(form({ task: "0" })), { checked: false }); |
| 25 | assert.deepEqual(taskCheckOf(form({ task: "two", checked: "1" })), { checked: true }); |
| 26 | }); |
| 27 | |
| 28 | /** The line each box markdown draws says, in order, as the site renders it. */ |
| 29 | function drawnLines(source: string): (number | undefined)[] { |
| 30 | const tree = markdownTree(source, { |
| 31 | remarkPlugins: [remarkGfm], |
| 32 | rehypePlugins: [rehypeRaw, [rehypeSanitize, defaultSchema], rehypeTaskLines], |
| 33 | }); |
| 34 | const lines: (number | undefined)[] = []; |
| 35 | visit(tree, "element", (node) => { |
| 36 | if (node.tagName === "input") lines.push(node.properties.dataTaskLine as number | undefined); |
| 37 | }); |
| 38 | return lines; |
| 39 | } |
| 40 | |
| 41 | test("each box markdown draws is a box the work service counts, on its line", () => { |
| 42 | const source = [ |
| 43 | "Intro", |
| 44 | "", |
| 45 | "- [ ] one", |
| 46 | " - [x] nested", |
| 47 | "- plain", |
| 48 | "", |
| 49 | "> - [ ] quoted", |
| 50 | "", |
| 51 | "```", |
| 52 | "- [ ] code", |
| 53 | "```", |
| 54 | "", |
| 55 | "1. [X] numbered", |
| 56 | "", |
| 57 | "- [ ] loose", |
| 58 | "", |
| 59 | "- [x] list", |
| 60 | ].join("\n"); |
| 61 | const drawn = drawnLines(source); |
| 62 | assert.deepEqual(drawn, [3, 4, 7, 13, 15, 17]); |
| 63 | assert.deepEqual( |
| 64 | taskList(source).map((task) => task.line), |
| 65 | drawn, |
| 66 | ); |
| 67 | }); |
| 68 | |
| 69 | test("markdown draws the boxes of every shared case where the work service counts them", () => { |
| 70 | const fixtures = JSON.parse(readFileSync(new URL("../../../../packages/contracts/src/tasks.fixtures.json", import.meta.url), "utf8")) as { |
| 71 | lists: { body: string; tasks: { line: number }[] }[]; |
| 72 | }; |
| 73 | for (const { body, tasks } of fixtures.lists) { |
| 74 | assert.deepEqual( |
| 75 | drawnLines(body), |
| 76 | tasks.map((task) => task.line), |
| 77 | JSON.stringify(body), |
| 78 | ); |
| 79 | } |
| 80 | }); |
| 81 | |
| 82 | test("a box written as HTML only shows", () => { |
| 83 | assert.deepEqual(drawnLines('<ul><li class="task-list-item"><input type="checkbox" checked> by hand</li></ul>\n\n- [ ] by markdown'), [undefined, 3]); |
| 84 | }); |