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

BuildTest.pudu

Pudu119 lines7.4 KB

GitHub ↗
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 Docgen1112/// Project files of the sample site.13fn 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![Logo](../images/logo.png)\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}2425/// A module documented by the sample site.26fn 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}2930/// The sample site's configuration with overridable parts.31fn 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}4142/// The sample site as build input.43fn 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}5354/// A successful plan, or an empty one.55fn 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}6465/// The text of a planned artifact.66fn artifact(plan: &Docgen.Plan, path: Str) -> Str {67  for held in plan.artifacts {68    if held.path == path { return held.content }69  }70  ""71}7273/// Codes of a failed plan.74fn 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}8081/// Runs every whole-site planning contract.82fn 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