Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-docgen

Documentation publishing for Pudu: articles, API references, navigation, search, and static websites

0.1.0Apache-2.01

InstallClose

NavigationTest.pudu

Pudu108 lines8.0 KB

GitHub ↗
1/** @Docgen.Navigation.Tests — tables of contents parse, nest, and bind to pages */2module PuduLangDocgen.NavigationTest34import Std.Io as Io5import Std.Test as Test6import PuduLangDocgen.Constants.Codes as Codes7import PuduLangDocgen as Docgen8import PuduLangDocgen.Navigation as Navigation9import PuduLangDocgen.Navigation.Resolve as Resolve1011/// An item with a title, destination, and children.12fn item(title: Str, href: Str, children: Array[Docgen.TocItem]) -> Docgen.TocItem {13  Docgen.TocItem{title: title, href: href, uid: "", expanded: false, children: children}14}1516/// Parsed items, or none when parsing fails.17fn parsed(path: Str, text: Str) -> Array[Docgen.TocItem] {18  match Navigation.parse(path, text) {19    case Ok(found) => found.items20    case Err(_) => []21  }22}2324/// Table-level metadata of a parsed table of contents.25fn meta(path: Str, text: Str) -> Array[(Str, Docgen.Meta)] {26  match Navigation.parse(path, text) {27    case Ok(found) => found.meta28    case Err(_) => []29  }30}3132/// The first diagnostic code of a failed parse.33fn refused(path: Str, text: Str) -> Str {34  match Navigation.parse(path, text) {35    case Ok(_) => "accepted"36    case Err(found) => found[0].code37  }38}3940/// A docset of two folders with nested tables of contents.41fn links() -> Resolve.Links {42  let tocs = mapOf([43      ("docs/toc.yml", parsed("docs/toc.yml", "- name: Home\n  href: index.md\n- name: Guide\n  href: guide/\n- name: API\n  uid: Api.Root\n- name: Site\n  href: https://example.org\n- name: Missing\n  href: gone.md")),44      ("docs/guide/toc.yml", parsed("docs/guide/toc.yml", "- href: start.md\n- name: Deep\n  items:\n  - name: Advanced\n    href: advanced.md#tuning")),45      ("docs/loop/toc.yml", parsed("docs/loop/toc.yml", "- name: Back\n  href: ../loop/toc.yml"))46    ])47  let outputs = mapOf([("docs/index.md", "index.html"), ("docs/guide/start.md", "guide/start.html"), ("docs/guide/advanced.md", "guide/advanced.html")])48  let references = mapOf([("Api.Root", Docgen.Reference{uid: "Api.Root", name: "Root", fullName: "Api.Root", href: "api/Api.Root.html", kind: "module"})])49  Resolve.Links{tocs: tocs, outputs: outputs, references: references, titles: mapOf([("guide/start.html", "Start")])}50}5152/// A table of a section label and a page, resolved.53fn labels() -> Array[Docgen.TocItem] {54  let (items, _problems) = Resolve.resolve("docs/labels.yml", &Resolve.Links{..links(), tocs: mapOf([("docs/labels.yml", parsed("docs/labels.yml", "- name: Section\n- name: Home\n  href: index.md"))])})55  items56}5758/// Runs every parsing, resolution, governing, trail, and ordering contract.59fn main() -> Int {60  let (root, problems) = Resolve.resolve("docs/toc.yml", &links())61  let (_loop, loopProblems) = Resolve.resolve("docs/loop/toc.yml", &links())62  let candidates = [Resolve.Candidate{output: "toc.json", order: 0, pages: []}, Resolve.Candidate{output: "guide/toc.json", order: 0, pages: []}]63  let checks = Test.suite("Navigation", &[64      Test.equals("yaml items", &parsed("toc.yml", "- name: A\n  href: a.md\n  items:\n  - name: B\n    href: b.md"), &[item("A", "a.md", [item("B", "b.md", [])])]),65      Test.equals("compact nested sequence", &parsed("toc.yml", "- name: A\n  items:\n  - name: B\n    href: b.md"), &[item("A", "", [item("B", "b.md", [])])]),66      Test.equals("items object", &parsed("toc.yml", "items:\n- name: A\n  href: a.md"), &[item("A", "a.md", [])]),67      Test.equals("json items", &parsed("toc.json", "[\{\"name\": \"A\", \"href\": \"a.md\"\}]"), &[item("A", "a.md", [])]),68      Test.equals("markdown items nest", &parsed("toc.md", "# [A](a.md)\n## [B](b.md)\n# Group\n## [C](c.md)"), &[item("A", "a.md", [item("B", "b.md", [])]), item("Group", "", [item("C", "c.md", [])])]),69      Test.equals("display name wins", &parsed("toc.yml", "- name: a\n  displayName: Shown\n  href: a.md")[0].title, &"Shown"),70      Test.equals("expanded flag", &parsed("toc.yml", "- name: a\n  href: a.md\n  expanded: true")[0].expanded, &true),71      Test.equals("empty file", &parsed("toc.yml", ""), &[]),72      Test.equals("invalid yaml", &refused("toc.yml", "- name: a\n\thref: b"), &Codes.TOC_SYNTAX),73      Test.equals("invalid json", &refused("toc.json", "[\{"), &Codes.TOC_SYNTAX),74      Test.equals("not a list", &refused("toc.yml", "name: a"), &Codes.TOC_SHAPE),75      Test.equals("unknown field", &refused("toc.yml", "- name: a\n  colour: red"), &Codes.TOC_UNKNOWN_FIELD),76      Test.equals("nameless item", &refused("toc.yml", "- expanded: true"), &Codes.TOC_NAMELESS),77      Test.equals("table metadata", &meta("toc.yml", "order: 200\npdf: true\nitems:\n- name: A\n  href: a.md"), &[("order", Docgen.Whole(200)), ("pdf", Docgen.Flag(true))]),78      Test.equals("titles taken from pages", &root[1].children[0].title, &"Start"),79      Test.equals("unsafe href", &refused("toc.yml", "- name: a\n  href: javascript:x"), &Codes.TOC_UNSAFE),80      Test.equals("unsafe markdown href", &refused("toc.md", "# [a](javascript:x)"), &Codes.TOC_UNSAFE),81      Test.equals("recognized names", &["toc.yml", "a/TOC.md", "b/toc.json", "toc.txt"].map(|path: Str| Navigation.isToc(path)), &[true, true, true, false]),82      Test.equals("resolved titles", &root.map(|found: Docgen.TocItem| found.title), &["Home", "Guide", "API", "Site"]),83      Test.equals("local href mapped", &root[0].href, &"index.html"),84      Test.equals("folder brings nested items", &root[1].children.map(|found: Docgen.TocItem| found.title), &["Start", "Deep"]),85      Test.equals("folder links its first page", &root[1].href, &"guide/start.html"),86      Test.equals("fragment kept", &root[1].children[1].children[0].href, &"guide/advanced.html#tuning"),87      Test.equals("uid resolves", &root[2].href, &"api/Api.Root.html"),88      Test.equals("remote kept", &root[3].href, &"https://example.org"),89      Test.equals("missing target warns", &problems.map(|found: Docgen.Diagnostic| found.code), &[Codes.TOC_TARGET_MISSING]),90      Test.equals("cycles refused", &loopProblems.map(|found: Docgen.Diagnostic| found.code), &[Codes.TOC_CYCLE]),91      Test.equals("governing by folder", &[Resolve.governing("guide/start.html", &candidates), Resolve.governing("index.html", &candidates), Resolve.governing("guides/x.html", &[candidates[1]])], &["guide/toc.json", "toc.json", ""]),92      Test.equals("linking tables win", &Resolve.governing("guide/start.html", &[candidates[0], candidates[1], Resolve.Candidate{output: "other/toc.json", order: 0, pages: ["guide/start.html"]}]), &"other/toc.json"),93      Test.equals("lower order wins", &Resolve.governing("a/b.html", &[Resolve.Candidate{output: "a/toc.json", order: 100, pages: ["a/b.html"]}, Resolve.Candidate{output: "toc.json", order: 0, pages: ["a/b.html"]}]), &"toc.json"),94      Test.equals("pages of a tree", &Resolve.pagesOf(&root), &["index.html", "guide/start.html", "guide/advanced.html", "api/Api.Root.html"]),95      Test.equals("output of toc", &[Resolve.outputOf("docs/toc.yml"), Resolve.outputOf("toc.md")], &["docs/toc.json", "toc.json"]),96      Test.equals("trail", &Resolve.trail(&root, "guide/advanced.html").map(|found: Docgen.TocItem| found.title), &["Guide", "Deep", "Advanced"]),97      Test.equals("deepest item wins the trail", &Resolve.trail(&root, "guide/start.html").map(|found: Docgen.TocItem| found.title), &["Guide", "Start"]),98      Test.equals("name-only items become labels", &labels().map(|found: Docgen.TocItem| (found.title, found.href)), &[("Section", ""), ("Home", "index.html")]),99      Test.equals("reading order", &Resolve.readingOrder(&root).map(|found: Docgen.TocItem| found.href), &["index.html", "guide/start.html", "guide/advanced.html#tuning", "api/Api.Root.html"]),100      Test.equals("neighbors", &Resolve.neighbors(&root, "guide/start.html"), &(Some(root[0]), Some(root[1].children[1].children[0]))),101      Test.equals("unlisted page has no neighbors", &Resolve.neighbors(&root, "other.html"), &(None, None)),102      Test.equals("pages fallback", &Navigation.forPages(&[Docgen.Page{path: "b.html", source: "b.md", kind: "article", title: "B", uid: "", summary: "", body: "", headings: [], links: [], meta: []}]), &[item("B", "b.html", [])])103    ])104  let ran = Test.run(&checks)105  for failure in Test.failuresOf(&ran) { let _printed = Io.writeErrorLine(failure) }106  Test.report(&ran)107}108