Ctrl K
ComunidadGuías…

tests/headings.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// ONE HEADING CONTRACT — the editor's `[[Note#` offers, the outline, the
// anchor table and the reading view's ids are the same list.
//
// shared/headings.ts is the rule; this file holds four consumers to it on one
// note built to hit every seam that used to split them: frontmatter holding a
// YAML `# comment`, an indented heading, a closed-ATX heading, an alignment
// marker, a furigana reading, inline markdown, a heading inside a fence, and a
// repeated title.

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { describe, it } from "node:test";
import { findAnchor, markdownAnchors, noteAnchors } from "../shared/anchors.ts";
import { findHeadingLine, HEADING_RE, headingTitle, isHeadingLine, scanHeadings } from "../shared/headings.ts";
import { extractHeadings as outline } from "../client/reading/toc.ts";

const NOTE = [
  "---",
  "# a YAML comment, not a heading",
  "title: Contract",
  "tags: [a]",
  "---",
  "# Contract",
  "",
  "   ## Indented three",
  "",
  "## Closed ##",
  "",
  "## Centred {.center}",
  "",
  "## {漢字|かんじ} reading",
  "",
  "## **Bold** and `code`",
  "",
  "```md",
  "## Fenced, not a heading",
  "```",
  "",
  "    ## Four spaces is code",
  "",
  "## Repeat",
  "",
  "## Repeat",
  "",
  "#notatag heading",
  "",
].join("\n");

/** What the editor's `[[Note#` completion offers: the anchor table's heading
 *  ids (client/editor/autocomplete.ts). */
const editorOffers = (md: string): string[] =>
  markdownAnchors(md).filter((a) => a.kind === "heading").map((a) => a.id);

const TITLES = ["Contract", "Indented three", "Closed", "Centred", "漢字 reading", "Bold and code", "Repeat", "Repeat"];
const IDS = ["contract", "indented-three", "closed", "centred", "漢字-reading", "bold-and-code", "repeat", "repeat-1"];

describe("the heading contract (shared/headings.ts)", () => {
  it("the shared scan finds exactly the headings, with the reading view's ids", () => {
    const all = scanHeadings(NOTE);
    assert.deepEqual(all.map((h) => h.title), TITLES);
    assert.deepEqual(all.map((h) => h.id), IDS);
  });

  it("the editor offers exactly the ids the outline and the reading view use", () => {
    assert.deepEqual(editorOffers(NOTE), IDS);
    assert.deepEqual(outline(NOTE).map((h) => h.text), TITLES);
    assert.deepEqual(outline(NOTE).map((h) => h.slug), IDS);
  });

  it("the anchor table files the same titles under the same ids, on the same lines", () => {
    const anchors = markdownAnchors(NOTE).filter((a) => a.kind === "heading");
    assert.deepEqual(anchors.map((a) => a.title), TITLES);
    assert.deepEqual(anchors.map((a) => a.id), IDS);
    assert.deepEqual(anchors.map((a) => a.line), scanHeadings(NOTE).map((h) => h.line));
    assert.deepEqual(anchors.map((a) => a.line), outline(NOTE).map((h) => h.line));
  });

  it("every offer lands: the editor and the anchor table resolve it to one line", () => {
    const anchors = noteAnchors("Contract.md", NOTE);
    for (const offer of new Set(editorOffers(NOTE))) {
      const inEditor = findHeadingLine(NOTE, offer);
      const inReader = findAnchor(anchors, offer)?.line ?? null;
      assert.notEqual(inEditor, null, `editor cannot land on ${offer}`);
      assert.equal(inEditor, inReader, `#${offer}`);
    }
  });

  it("a YAML `# comment` is never a heading, in any view", () => {
    const comment = "a YAML comment, not a heading";
    assert.ok(!editorOffers(NOTE).some((t) => t.includes("yaml")));
    assert.equal(findHeadingLine(NOTE, comment), null);
    assert.equal(findAnchor(noteAnchors("Contract.md", NOTE), comment), null);
  });

  it("one line shape: indent up to three, a space after the hashes, seven is not a heading", () => {
    assert.ok(isHeadingLine("# a"));
    assert.ok(isHeadingLine("   ###### a"));
    assert.ok(isHeadingLine("##\ta"));
    assert.ok(!isHeadingLine("    # code"));
    assert.ok(!isHeadingLine("#tag"));
    assert.ok(!isHeadingLine("####### seven"));
    assert.equal(headingTitle("Title {.right}"), "Title");
    assert.equal(headingTitle("Closed ##"), "Closed");
  });

  it("the reading renderer and the outline import the shared rule rather than keeping a copy", () => {
    const render = readFileSync(new URL("../client/reading/render.ts", import.meta.url), "utf8");
    const toc = readFileSync(new URL("../client/reading/toc.ts", import.meta.url), "utf8");
    assert.match(render, /import \{ HEADING_RE \} from "..\/..\/shared\/headings.ts"/);
    assert.doesNotMatch(render, /const HEADING_RE =/);
    assert.doesNotMatch(toc, /const HEADING_RE =/);
    assert.equal(HEADING_RE.source, "^ {0,3}(#{1,6})[ \\t]+(.*)$");
  });
});