
ApiTest.pudu
Pudu161 lines11.2 KB
1/** @Docgen.Api.Tests — Pudu declarations become linked reference pages */2module PuduLangDocgen.ApiTest34import Std.Io as Io5import Std.Result as Result6import Std.Test as Test7import PuduLangDocgen.Api.Catalog as Catalog8import PuduLangDocgen.Api.Lexer as Lexer9import PuduLangDocgen.Api.Model as Model10import PuduLangDocgen.Api.Pages as Pages11import PuduLangDocgen.Api.Parser as Parser12import PuduLangDocgen.Api.Signature as Signature13import PuduLangDocgen as Docgen14import PuduLangDocgen.Markdown.Phrase as Phrase15import PuduLangDocgen.Yaml as Yaml161718fn shapesSource() -> Str {19 [20 "/** @Demo.Shapes.Module — geometric shapes and their areas */",21 "module Demo.Shapes",22 "",23 "import Demo.Units as Units",24 "",25 "/** @Demo.Shapes.Shape — a closed figure */",26 "export type Shape = Circle(Int) | Square(Int)",27 "",28 "/// A point on the plane.",29 "export type Point = \{",30 " /// Horizontal position.",31 " x: Int,",32 " y: Int",33 "\}",34 "",35 "/// Measured width.",36 "export type Width = Units.Length",37 "",38 "/// Anything with an area.",39 "export trait Measured \{",40 " fn area(self: &Self) -> Int",41 "\}",42 "",43 "impl Measured for Shape \{",44 " /// The shape's area.",45 " fn area(self: &Self) -> Int \{ 0 \}",46 "\}",47 "",48 "/// The area of a shape in square units.",49 "///",50 "/// ## Examples",51 "///",52 "/// Circles round down.",53 "export fn area(shape: &Shape, scale: Units.Length) -> Int \{",54 " let text = \"\{ nested \}\"",55 " 0",56 "\}",57 "",58 "/// Largest side accepted.",59 "export const LIMIT: Int = 100",60 "",61 "fn hidden() -> Int \{ 1 \}",62 ""63 ].join("\n")64}656667const UNITS: Str = "module Demo.Units\n\n/// A length in millimetres.\nexport type Length = Millimetres(Int)\n"686970fn options() -> Catalog.Options {71 Catalog.Options{..Catalog.defaults(), sourceUrl: "https://src.example/\{path\}#L\{line\}"}72}737475fn units() -> Array[Model.Unit] {76 Catalog.select(&[Parser.parse("src/Demo/Shapes.pudu", shapesSource()), Parser.parse("src/Demo/Units.pudu", UNITS)], &options())77}787980fn context() -> Pages.Context {81 let all = units()82 var references: Array[(Str, Docgen.Reference)] = []83 for reference in Catalog.references(&all, &options()) { references = references.push((reference.uid, reference)) }84 Pages.Context{options: options(), references: mapOf(references), outputs: mapOf([]), settings: Phrase.rendering(), units: all}85}868788fn pages() -> Array[Docgen.Page] {89 let all = units()90 let (found, _problems) = Pages.pages(&all[0], &context())91 found92}939495fn body(path: Str) -> Str {96 for page in pages() {97 if page.path == path { return page.body }98 }99 ""100}101102103fn main() -> Int {104 let shapes = Parser.parse("src/Demo/Shapes.pudu", shapesSource())105 let members = shapes.members106 let tokens = Lexer.tokens("/* a /* b */ c */ x \"s \{ \"in\" \} t\" 'y' '\\n' /// doc")107 let checks = Test.suite("Api", &[108 Test.equals("tokens skip nested comments and keep literals", &tokens.map(|token: Lexer.Token| token.text), &["x", "\"s \{ \"in\" \} t\"", "'y'", "'\\n'", "doc"]),109 Test.equals("module identity and anchor text", &(shapes.uid, shapes.doc), &("Demo.Shapes", "Geometric shapes and their areas")),110 Test.equals("imports", &shapes.imports, &[("Units", "Demo.Units")]),111 Test.equals("record signature without docs", &members[1].signature, &"type Point = \{ x: Int, y: Int \}"),112 Test.equals("long records print a field per line", &Parser.parse("a.pudu", "module A\nexport type R = \{ a: Int, /// b\n b: Str, c: Bool \}").members[0].signature, &"type R = \{\n a: Int,\n b: Str,\n c: Bool\n\}"),113 Test.equals("declaration kinds", &members.map(|member: Model.Member| member.kind), &["union", "record", "alias", "trait", "function", "constant", "function"]),114 Test.equals("exported flags", &members.map(|member: Model.Member| member.exported), &[true, true, true, true, true, true, false]),115 Test.equals("variants", &members[0].members.map(|member: Model.Member| member.signature), &["Circle(Int)", "Square(Int)"]),116 Test.equals("fields and their docs", &members[1].members.map(|member: Model.Member| (member.name, member.returns, member.doc)), &[("x", "Int", "Horizontal position."), ("y", "Int", "")]),117 Test.equals("alias target", &members[2].returns, &"Units.Length"),118 Test.equals("trait methods", &members[3].members.map(|member: Model.Member| member.signature), &["fn area(self: &Self) -> Int"]),119 Test.equals("function signature", &members[4].signature, &"fn area(shape: &Shape, scale: Units.Length) -> Int"),120 Test.equals("function parameters", &members[4].parameters, &[Model.Parameter{name: "shape", kind: "&Shape"}, Model.Parameter{name: "scale", kind: "Units.Length"}]),121 Test.equals("constant value shown", &members[5].signature, &"const LIMIT: Int = 100"),122 Test.equals("implementation", &shapes.implementations.map(|held: Model.Implementation| (held.contract, held.target, held.methods.length())), &[("Measured", "Shape", 1)]),123 Test.equals("summary and remarks", &(Model.summary(members[4].doc), Model.remarks(members[4].doc)), &("The area of a shape in square units.", "## Examples\n\nCircles round down.")),124 Test.equals("private members dropped", &units()[0].members.length(), &6),125 Test.equals("private members kept on request", &Catalog.select(&[shapes], &Catalog.Options{..options(), includePrivate: true})[0].members.length(), &7),126 Test.equals("exclude patterns", &Catalog.select(&[shapes], &Catalog.Options{..options(), exclude: ["Demo.Shapes.area", "Demo.Shapes.LIMIT"]})[0].members.length(), &4),127 Test.equals("include patterns keep the module", &Catalog.select(&[shapes], &Catalog.Options{..options(), include: ["Demo.Shapes.Point"]})[0].members.map(|member: Model.Member| member.name), &["Point"]),128 Test.equals("reference destinations", &Catalog.references(&units(), &options()).map(|reference: Docgen.Reference| reference.href).slice(0, 6), &["api/Demo.Shapes.html", "api/Demo.Shapes.Shape.html", "api/Demo.Shapes.Shape.html#Circle", "api/Demo.Shapes.Shape.html#Square", "api/Demo.Shapes.Point.html", "api/Demo.Shapes.Point.html#x"]),129 Test.equals("toc lists types", &Catalog.toc(&units(), &options())[0].children.map(|item: Docgen.TocItem| item.title), &["Measured", "Point", "Shape", "Width"]),130 Test.equals("source link", &Catalog.sourceLink(&options(), "src/a.pudu", 7), &"https://src.example/src/a.pudu#L7"),131 Test.equals("signature links through imports", &Signature.html(&shapes, "fn f(s: &Shape) -> Units.Length", &context().references, "api/Demo.Shapes.html"), &"<span class=\"hl-k\">fn</span> f(s: &<a class=\"xref\" href=\"Demo.Shapes.Shape.html\">Shape</a>) -> <a class=\"xref\" href=\"Demo.Units.Length.html\">Units.Length</a>"),132 Test.equals("unknown names stay text", &Signature.resolve(&shapes, "Str", &context().references), &""),133 Test.equals("pages per module and type", &pages().map(|page: Docgen.Page| page.path), &["api/Demo.Shapes.html", "api/Demo.Shapes.Shape.html", "api/Demo.Shapes.Point.html", "api/Demo.Shapes.Width.html", "api/Demo.Shapes.Measured.html"]),134 Test.that("module page sections", body("api/Demo.Shapes.html").contains("<h2 id=\"types\">") && body("api/Demo.Shapes.html").contains("<h2 id=\"functions\">") && body("api/Demo.Shapes.html").contains("<h3 id=\"area\">")),135 Test.that("demoted doc headings", body("api/Demo.Shapes.html").contains("<h4 id=\"examples\">Examples")),136 Test.that("parameters listed", body("api/Demo.Shapes.html").contains("<dt><code>scale</code></dt>")),137 Test.that("source links shown", body("api/Demo.Shapes.html").contains("https://src.example/src/Demo/Shapes.pudu#L34")),138 Test.that("variants table", body("api/Demo.Shapes.Shape.html").contains("<tr id=\"Circle\"><td><code>Circle</code></td>")),139 Test.that("implemented traits", body("api/Demo.Shapes.Shape.html").contains("<h2 id=\"implements\">") && body("api/Demo.Shapes.Shape.html").contains("Demo.Shapes.Measured")),140 Test.that("trait implementors", body("api/Demo.Shapes.Measured.html").contains("<h2 id=\"implementations\">")),141 Test.that("related functions", body("api/Demo.Shapes.Shape.html").contains("<h2 id=\"related\">")),142 Test.that("field docs rendered", body("api/Demo.Shapes.Point.html").contains("<p>Horizontal position.</p>")),143 Test.equals("nested module navigation", &Catalog.toc(&units(), &Catalog.Options{..options(), nested: true}).map(|item: Docgen.TocItem| (item.title, item.href, item.children.map(|child: Docgen.TocItem| child.title))), &[("Demo", "", ["Shapes", "Units"])]),144 Test.equals("separate member pages", &Catalog.references(&units(), &Catalog.Options{..options(), separate: true}).filter(|reference: Docgen.Reference| reference.uid == "Demo.Shapes.area").map(|reference: Docgen.Reference| reference.href), &["api/Demo.Shapes.area.html"]),145 Test.that("plain documentation", {146 let (found, _problems) = Pages.pages(&units()[0], &Pages.Context{..context(), options: Catalog.Options{..options(), skipMarkup: true}})147 found[0].body.contains("<p>The area of a shape in square units.</p>") && found[0].body.contains("## Examples")148 }),149 Test.equals("filter rules", &match Catalog.rules(&Result.unwrapOr(Yaml.decode("apiRules:\n- exclude:\n uidRegex: \\.area$\n type: Function\n- include:\n uidRegex: .*"), Docgen.Nothing)) { case Ok(found) => Catalog.select(&[shapes], &Catalog.Options{..options(), rules: found})[0].members.map(|member: Model.Member| member.name) case Err(reason) => [reason] }, &["Shape", "Point", "Width", "Measured", "LIMIT"]),150 Test.equals("bad filter", &match Catalog.rules(&Docgen.Fields([("apiRules", Docgen.Items([Docgen.Fields([("exclude", Docgen.Fields([("uidRegex", Docgen.Text("("))]))])]))])) { case Ok(_) => "accepted" case Err(reason) => reason.take(9) }, &"uidRegex "),151 Test.equals("category labels", &Catalog.toc(&units(), &Catalog.Options{..options(), categories: "flattened"})[0].children.map(|item: Docgen.TocItem| item.title), &["Types", "Point", "Shape", "Width", "Traits", "Measured"]),152 Test.equals("category groups", &Catalog.toc(&units(), &Catalog.Options{..options(), categories: "nested"})[0].children.map(|item: Docgen.TocItem| (item.title, item.children.length())), &[("Types", 3), ("Traits", 1)]),153 Test.equals("alphabetic variants", &Catalog.select(&[Parser.parse("a.pudu", "module A\nexport type T = Zed | Alpha")], &Catalog.Options{..options(), alphabetic: true})[0].members[0].members.map(|member: Model.Member| member.name), &["Alpha", "Zed"]),154 Test.equals("source links excluded", &Catalog.sourceLink(&Catalog.Options{..options(), sourceExclude: ["src/**"]}, "src/a.pudu", 1), &""),155 Test.equals("kind names", &[Pages.kindName("union"), Pages.kindName("alias"), Pages.kindName("other")], &["Union", "Type alias", "other"])156 ])157 let ran = Test.run(&checks)158 for failure in Test.failuresOf(&ran) { let _printed = Io.writeErrorLine(failure) }159 Test.report(&ran)160}161