
BuildTest.pudu
Pudu119 lines7.4 KB
1/** @Docgen.Build.Tests — whole-site plans validate before any output is produced */2module PuduLangDocgen.BuildTest34import Std.Io as Io5import Std.Test as Test6import PuduLangDocgen.Api.Parser as Parser7import PuduLangDocgen.Build as Build8import PuduLangDocgen.Configuration as Configuration9import PuduLangDocgen.Constants.Codes as Codes10import PuduLangDocgen as Docgen111213fn files() -> Array[(Str, Str)] {14 [15 ("docs/index.md", "---\nuid: home\ntitle: Welcome\n---\nRead the [guide](guide/start.md#install) or <xref:Demo.Shapes>.\n\n## Overview\n\nText."),16 ("docs/guide/start.md", "# Start\n\n## Install\n\n\n\nSee @home and [bad](missing.md)."),17 ("docs/guide/moved.md", "---\nredirect_url: start.md\n---\n"),18 ("docs/toc.yml", "- name: Home\n href: index.md\n- name: Guide\n href: guide/\n- name: API\n href: api/\n- name: Pets\n href: rest/pets.yml"),19 ("docs/guide/toc.yml", "- href: start.md"),20 ("docs/rest/pets.yml", "openapi: 3.0.0\ninfo:\n title: Pets\npaths:\n /pets:\n get:\n operationId: list\n responses:\n '200':\n description: ok"),21 ("docs/overwrite/shapes.md", "---\nuid: Demo.Shapes.area\nsummary: *content\n---\nComputes the **area**.")22 ]23}242526fn source() -> Str {27 "module Demo.Shapes\n\n/// A figure.\nexport type Shape = Circle(Int) | Square(Int)\n\n/// Area.\nexport fn area(shape: &Shape) -> Int \{ 0 \}\n"28}293031fn config(extra: Str) -> Configuration.Config {32 let text = "\{\"metadata\": [\{\"src\": [\{\"files\": \"src/**/*.pudu\"\}], \"dest\": \"api\"\}], \"build\": \{\"content\": [\{\"files\": [\"**/*.md\", \"**/toc.yml\", \"rest/*.yml\"], \"exclude\": [\"overwrite/**\"], \"src\": \"docs\"\}], \"resource\": [\{\"files\": \"images/**\", \"src\": \"docs\"\}], \"overwrite\": [\{\"files\": \"overwrite/*.md\", \"src\": \"docs\"\}], \"globalMetadata\": \{\"_appTitle\": \"Demo\", \"_baseUrl\": \"https://docs.example/\"\}, \"sitemap\": \{\"baseUrl\": \"https://docs.example\"\}" + extra + "\}\}"33 match Configuration.parse("docgen.json", text) {34 case Ok(found) => found35 case Err(problems) => {36 let _said = Io.writeErrorLine(show(problems))37 Configuration.defaults()38 }39 }40}414243fn input(extra: Str) -> Build.Input {44 let all = files()45 var paths: Array[Str] = ["docs/images/logo.png", "src/Demo/Shapes.pudu"]46 for (path, _text) in all { paths = paths.push(path) }47 Build.Input {48 config: config(extra), paths: paths, files: mapOf(all), resources: [("docs/images/logo.png", Docgen.Resource{path: "images/logo.png", content: "PNG".toBytes()})],49 units: [[Parser.parse("src/Demo/Shapes.pudu", source())]], external: [], template: [], assets: [], history: mapOf([("docs/index.md", "2026-09-01")]),50 repository: Configuration.Contribution{repo: "https://github.com/example/docs", branch: "main", path: ""}, diagrams: mapOf([]), generator: "0.1.0", today: "2026-09-29"51 }52}535455fn planned(extra: Str) -> Docgen.Plan {56 match Build.plan(&input(extra), &Build.extensions()) {57 case Ok(found) => found58 case Err(problems) => {59 for problem in problems { let _said = Io.writeErrorLine(Docgen.describe(&problem)) }60 Docgen.Plan{artifacts: [], resources: [], diagnostics: []}61 }62 }63}646566fn artifact(plan: &Docgen.Plan, path: Str) -> Str {67 for held in plan.artifacts {68 if held.path == path { return held.content }69 }70 ""71}727374fn failures(extra: Str) -> Array[Str] {75 match Build.plan(&input(extra), &Build.extensions()) {76 case Ok(_) => []77 case Err(problems) => problems.map(|problem: Docgen.Diagnostic| problem.code)78 }79}808182fn main() -> Int {83 let plan = planned("")84 let paths = plan.artifacts.map(|held: Docgen.Artifact| held.path)85 let home = artifact(&plan, "index.html")86 let start = artifact(&plan, "guide/start.html")87 let checks = Test.suite("Build", &[88 Test.equals("artifacts", &paths, &["404.html", "_assets/site.css", "_assets/site.js", "api/Demo.Shapes.Shape.html", "api/Demo.Shapes.html", "api/toc.json", "guide/moved.html", "guide/start.html", "guide/toc.json", "index.html", "index.json", "manifest.json", "rest/pets.html", "sitemap.xml", "toc.json", "xrefmap.yml"]),89 Test.equals("resources copied", &plan.resources.map(|held: Docgen.Resource| held.path), &["images/logo.png"]),90 Test.equals("warnings travel with the plan", &plan.diagnostics.map(|held: Docgen.Diagnostic| held.code), &[Codes.LINK_BROKEN]),91 Test.that("title with site suffix", home.contains("<title>Welcome | Demo</title>")),92 Test.that("metadata title becomes the heading", home.contains("<h1 id=\"welcome\">Welcome</h1>")),93 Test.that("navbar from the root table", home.contains("<a href=\"guide/start.html\">Guide</a>") && home.contains("<a href=\"api/Demo.Shapes.html\">API</a>")),94 Test.that("xref to API page", home.contains("<a class=\"xref\" href=\"api/Demo.Shapes.html\">Shapes</a>")),95 Test.that("edit link", home.contains("https://github.com/example/docs/blob/main/docs/index.md")),96 Test.that("last updated", home.contains("<time datetime=\"2026-09-01\">")),97 Test.that("canonical address", home.contains("<link rel=\"canonical\" href=\"https://docs.example/index.html\">")),98 Test.that("governing table in the folder", start.contains("aria-current=\"page\">Start</a>")),99 Test.that("breadcrumb trail", start.contains("<a href=\"start.html\">Guide</a>") || start.contains("Guide</a></li>")),100 Test.that("image resolved from nested page", start.contains("src=\"../images/logo.png\"")),101 Test.that("mention resolved", start.contains("<a class=\"xref\" href=\"../index.html\">Welcome</a>")),102 Test.that("redirect page", artifact(&plan, "guide/moved.html").contains("url=start.md") || artifact(&plan, "guide/moved.html").contains("refresh")),103 Test.that("overwrite replaced summary", artifact(&plan, "api/Demo.Shapes.html").contains("Computes the <strong>area</strong>.")),104 Test.that("rest page", artifact(&plan, "rest/pets.html").contains("<span class=\"method method-get\">GET</span>")),105 Test.that("not found page uses the base path", artifact(&plan, "404.html").contains("href=\"/_assets/site.css\"")),106 Test.that("search index", artifact(&plan, "index.json").contains("\"href\":\"guide/start.html\"")),107 Test.that("sitemap dates", artifact(&plan, "sitemap.xml").contains("<loc>https://docs.example/index.html</loc>\n <lastmod>2026-09-01</lastmod>")),108 Test.that("xrefmap", artifact(&plan, "xrefmap.yml").contains("- uid: home\n name: Welcome")),109 Test.that("manifest version", artifact(&plan, "manifest.json").contains("\"version\": \"0.1.0\"")),110 Test.equals("warnings as errors", &failures(", \"warningsAsErrors\": true"), &[Codes.LINK_BROKEN]),111 Test.equals("rules silence codes", &planned(", \"rules\": \{\"DG123\": \"off\"\}").diagnostics, &[]),112 Test.equals("rules raise codes", &failures(", \"rules\": \{\"DG123\": \"error\"\}"), &[Codes.LINK_BROKEN]),113 Test.that("model export", planned(", \"exportRawModel\": true").artifacts.filter(|held: Docgen.Artifact| held.path == "index.html.raw.json").length() == 1)114 ])115 let ran = Test.run(&checks)116 for failure in Test.failuresOf(&ran) { let _printed = Io.writeErrorLine(failure) }117 Test.report(&ran)118}119