
SiteLandingTest.pudu
Pudu103 lines7.8 KB
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 Yaml141516const 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"171819fn 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}222324fn 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}282930fn 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}484950fn located() -> Phrase.Scope {51 Phrase.Scope{page: "index.html", source: "index.yml", outputs: mapOf([("guide.md", "guide.html")]), references: mapOf([]), settings: Phrase.rendering()}52}535455fn rendered(text: Str) -> Result[Landing.Rendered, Str] {56 Landing.render(&Yaml.decode(text) ?, &located())57}585960fn refused(text: Str) -> Str {61 match rendered(text) {62 case Ok(_) => "accepted"63 case Err(reason) => reason64 }65}666768fn 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 <widgets> 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