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

SiteLandingTest.pudu

Pudu103 lines7.8 KB

GitHub ↗
1/** @Docgen.Site.LandingTests — hub pages render banners, highlights, topic lists, and site footers */2module PuduLangDocgen.SiteLandingTest34import Std.Io as Io5import Std.Result as Result6import Std.Test as Test7import PuduLangDocgen.Build as Build8import PuduLangDocgen.Configuration as Configuration9import PuduLangDocgen.Constants.Codes as Codes10import PuduLangDocgen as Docgen11import PuduLangDocgen.Markdown.Phrase as Phrase12import PuduLangDocgen.Site.Landing as Landing13import PuduLangDocgen.Yaml as Yaml1415/// A hub page using every section.16const HUB: Str = "### YamlMime:Landing\ntitle: Widget documentation\nmetadata:\n  description: All about widgets.\nsummary: Build <widgets> fast.\nhighlightedContent:\n  items:\n  - title: What is a widget?\n    itemType: overview\n    url: guide.md\n  - title: Releases\n    itemType: whats-new\n    url: https://example.org/releases\nconceptualContent:\n  title: Explore\n  items:\n  - title: Write\n    links:\n    - text: Guide\n      url: guide.md#start\n    footerLink:\n      text: More\n      url: guide.md\nadditionalContent:\n  sections:\n  - title: Related content\n    items:\n    - title: Pudu\n      summary: The language.\n      url: https://www.pudu-lang.org/\n"1718/// Project files of the sample site.19fn files() -> Array[(Str, Str)] {20  [("docs/index.yml", HUB), ("docs/guide.md", "# Guide\n\n## Start\n\nText."), ("docs/toc.yml", "- name: Home\n  href: index.yml\n- name: Guide\n  href: guide.md")]21}2223/// The sample site's configuration with footer metadata.24fn config() -> Configuration.Config {25  let text = "\{\"build\": \{\"content\": [\{\"files\": [\"**/*.md\", \"**/*.yml\"], \"src\": \"docs\"\}], \"globalMetadata\": \{\"_appName\": \"Widgets\", \"_copyright\": \"Widget Team\", \"_footerLinks\": [\{\"name\": \"License\", \"href\": \"https://example.org/license\"\}, \{\"name\": \"Guide\", \"href\": \"guide.html\"\}, \{\"name\": \"Bad\", \"href\": \"javascript:x\"\}, \{\"name\": \"\", \"href\": \"x.html\"\}]\}\}\}"26  Result.unwrapOr(Configuration.parse("docgen.json", text), Configuration.defaults())27}2829/// The published text of a path in the sample site.30fn published(path: Str) -> Str {31  let all = files()32  var paths: Array[Str] = []33  for (held, _text) in all { paths = paths.push(held) }34  let input = Build.Input {35    config: config(), paths: paths, files: mapOf(all), resources: [], units: [], external: [], template: [], assets: [], history: mapOf([]),36    repository: Configuration.Contribution{repo: "https://github.com/example/docs", branch: "main", path: ""}, diagrams: mapOf([]), generator: "0.1.0", today: "2026-09-29"37  }38  match Build.plan(&input, &Build.extensions()) {39    case Ok(plan) => {40      for artifact in plan.artifacts {41        if artifact.path == path { return artifact.content }42      }43      "missing " + path44    }45    case Err(problems) => problems.map(|problem: Docgen.Diagnostic| Docgen.describe(&problem)).join("\n")46  }47}4849/// Scope of a page at the site root that knows one published guide.50fn located() -> Phrase.Scope {51  Phrase.Scope{page: "index.html", source: "index.yml", outputs: mapOf([("guide.md", "guide.html")]), references: mapOf([]), settings: Phrase.rendering()}52}5354/// A landing file rendered directly, or its refusal.55fn rendered(text: Str) -> Result[Landing.Rendered, Str] {56  Landing.render(&Yaml.decode(text) ?, &located())57}5859/// The refusal of a landing file, or `accepted`.60fn refused(text: Str) -> Str {61  match rendered(text) {62    case Ok(_) => "accepted"63    case Err(reason) => reason64  }65}6667/// Runs every hub page and footer contract.68fn main() -> Int {69  let home = published("index.html")70  let guide = published("guide.html")71  let direct = Result.unwrapOr(rendered(HUB), Landing.Rendered{title: "", body: "", headings: [], links: [], diagnostics: []})72  let broken = Result.unwrapOr(rendered("title: T\nhighlightedContent:\n  items:\n  - title: Gone\n    itemType: concept\n    url: gone.md\n"), Landing.Rendered{title: "", body: "", headings: [], links: [], diagnostics: []})73  let checks = Test.suite("Site landing", &[74      Test.that("landing layout", home.contains("class=\"kind-landing layout-landing\"")),75      Test.that("declared metadata", home.contains("<meta name=\"description\" content=\"All about widgets.\">")),76      Test.that("banner with escaped summary", home.contains("<section class=\"landing-hero\">") && home.contains("<h1 id=\"widget-documentation\">Widget documentation</h1>") && home.contains("<p class=\"landing-summary\">Build &lt;widgets&gt; fast.</p>")),77      Test.that("highlight card", direct.body.contains("<li class=\"landing-highlight\"><a href=\"guide.html\"><span class=\"landing-kind\">Overview</span><span class=\"landing-link\">What is a widget?</span></a></li>")),78      Test.that("remote highlight kept", direct.body.contains("<a href=\"https://example.org/releases\">") && direct.body.contains("<span class=\"landing-link\">Releases</span>")),79      Test.that("topic card", direct.body.contains("<article class=\"landing-card\"><h3>Write</h3><ul><li><a href=\"guide.html#start\">Guide</a></li></ul><p class=\"landing-more\"><a href=\"guide.html\">More</a></p></article>")),80      Test.that("related card", direct.body.contains("<h3><a href=\"https://www.pudu-lang.org/\">Pudu</a></h3><p>The language.</p>")),81      Test.equals("outline", &direct.headings.map(|held: Docgen.Heading| (held.id, held.level)), &[("widget-documentation", 1), ("explore", 2), ("related-content", 2)]),82      Test.equals("links recorded for checks", &direct.links.map(|held: Docgen.Link| held.target), &["guide.html", "guide.html#start", "guide.html"]),83      Test.equals("missing page reported", &broken.diagnostics.map(|held: Docgen.Diagnostic| held.code), &[Codes.LINK_BROKEN]),84      Test.that("no page actions on landing pages", !home.contains("class=\"page-actions\"")),85      Test.that("page actions on articles", guide.contains("class=\"page-actions\"")),86      Test.that("footer links from metadata", guide.contains("<nav class=\"footer-links\" aria-label=\"Footer\">") && guide.contains("<li><a href=\"https://example.org/license\">License</a></li>") && guide.contains("<li><a href=\"guide.html\">Guide</a></li>")),87      Test.that("unsafe and nameless footer links dropped", !guide.contains("javascript:") && !guide.contains("href=\"x.html\"")),88      Test.that("copyright dated by the build", guide.contains("<p class=\"footer-copyright\">© 2026 Widget Team</p>")),89      Test.that("navigation toggle", guide.contains("class=\"icon-button nav-toggle\" aria-controls=\"navbar\"") && guide.contains("<nav id=\"navbar\" class=\"navbar\"")),90      Test.equals("needs an object", &refused("- a"), &"landing page must be an object"),91      Test.equals("needs a title", &refused("summary: s"), &"landing page needs a title"),92      Test.equals("known item types", &refused("title: T\nhighlightedContent:\n  items:\n  - title: X\n    itemType: poster\n    url: a.md"), &"highlighted item has an unknown itemType: poster"),93      Test.equals("highlights need titles", &refused("title: T\nhighlightedContent:\n  items:\n  - itemType: overview\n    url: guide.md"), &"each highlighted item needs a title"),94      Test.equals("topic links need text", &refused("title: T\nconceptualContent:\n  items:\n  - title: A\n    links:\n    - url: guide.md"), &"each topic link needs a text"),95      Test.equals("lists must be lists", &refused("title: T\nadditionalContent:\n  sections: x"), &"sections must be a list"),96      Test.equals("list entries must be objects", &refused("title: T\nconceptualContent:\n  items:\n  - x"), &"each topic must be an object"),97      Test.equals("banner alone", &Result.map(rendered("title: T"), |found: Landing.Rendered| found.body), &Ok("<section class=\"landing-hero\"><div class=\"landing-inner\"><h1 id=\"t\">T</h1></div></section>"))98    ])99  let ran = Test.run(&checks)100  for failure in Test.failuresOf(&ran) { let _printed = Io.writeErrorLine(failure) }101  Test.report(&ran)102}103