
NavigationTest.pudu
Pudu108 lines8.0 KB
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 Resolve101112fn item(title: Str, href: Str, children: Array[Docgen.TocItem]) -> Docgen.TocItem {13 Docgen.TocItem{title: title, href: href, uid: "", expanded: false, children: children}14}151617fn parsed(path: Str, text: Str) -> Array[Docgen.TocItem] {18 match Navigation.parse(path, text) {19 case Ok(found) => found.items20 case Err(_) => []21 }22}232425fn 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}313233fn refused(path: Str, text: Str) -> Str {34 match Navigation.parse(path, text) {35 case Ok(_) => "accepted"36 case Err(found) => found[0].code37 }38}394041fn 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}515253fn 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}575859fn 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