Add frontmatter, markdown, path, xml, rss, and web packages
Sync local packages into the registry repo and update index.json and README.md to include all 9 packages.
This commit is contained in:
3
packages/xml/.gitignore
vendored
Normal file
3
packages/xml/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
_site/
|
||||
.lux_packages/
|
||||
*.bak
|
||||
109
packages/xml/lib.lux
Normal file
109
packages/xml/lib.lux
Normal file
@@ -0,0 +1,109 @@
|
||||
// xml - XML builder for Lux
|
||||
//
|
||||
// Provides a simple API for constructing well-formed XML documents.
|
||||
// Designed for generating RSS feeds, sitemaps, and other XML output.
|
||||
|
||||
// An XML attribute (key-value pair)
|
||||
type Attr =
|
||||
| Attr(String, String)
|
||||
|
||||
// An XML node: element with tag, attributes, and children, or text content
|
||||
type Node =
|
||||
| Element(String, List<Attr>, List<Node>)
|
||||
| Text(String)
|
||||
| CData(String)
|
||||
| Raw(String)
|
||||
|
||||
// Escape special XML characters in text content
|
||||
pub fn escape(s: String): String =
|
||||
String.replace(
|
||||
String.replace(
|
||||
String.replace(
|
||||
String.replace(
|
||||
String.replace(s, "&", "&"),
|
||||
"<", "<"),
|
||||
">", ">"),
|
||||
"\"", """),
|
||||
"'", "'")
|
||||
|
||||
// Create a text node
|
||||
pub fn text(content: String): Node = Text(content)
|
||||
|
||||
// Create a CDATA section
|
||||
pub fn cdata(content: String): Node = CData(content)
|
||||
|
||||
// Create a raw (unescaped) node
|
||||
pub fn raw(content: String): Node = Raw(content)
|
||||
|
||||
// Create an element with tag, attributes, and children
|
||||
pub fn element(tag: String, attrs: List<Attr>, children: List<Node>): Node =
|
||||
Element(tag, attrs, children)
|
||||
|
||||
// Create an element with no attributes
|
||||
pub fn el(tag: String, children: List<Node>): Node =
|
||||
Element(tag, [], children)
|
||||
|
||||
// Create a self-closing element with attributes and no children
|
||||
pub fn selfClosing(tag: String, attrs: List<Attr>): Node =
|
||||
Element(tag, attrs, [])
|
||||
|
||||
// Create an attribute
|
||||
pub fn attr(key: String, value: String): Attr =
|
||||
Attr(key, value)
|
||||
|
||||
// Create an element with text content
|
||||
pub fn textEl(tag: String, content: String): Node =
|
||||
Element(tag, [], [Text(content)])
|
||||
|
||||
// Create an element with attributes and text content
|
||||
pub fn textElAttr(tag: String, attrs: List<Attr>, content: String): Node =
|
||||
Element(tag, attrs, [Text(content)])
|
||||
|
||||
// Render a single attribute to string
|
||||
fn renderAttr(a: Attr): String =
|
||||
match a {
|
||||
Attr(k, v) => " " + k + "=\"" + escape(v) + "\"",
|
||||
}
|
||||
|
||||
// Render a list of attributes to string
|
||||
fn renderAttrs(attrs: List<Attr>): String =
|
||||
match List.head(attrs) {
|
||||
None => "",
|
||||
Some(a) => renderAttr(a) + match List.tail(attrs) {
|
||||
Some(rest) => renderAttrs(rest),
|
||||
None => "",
|
||||
},
|
||||
}
|
||||
|
||||
// Render child nodes to string
|
||||
fn renderChildren(nodes: List<Node>): String =
|
||||
match List.head(nodes) {
|
||||
None => "",
|
||||
Some(node) => render(node) + match List.tail(nodes) {
|
||||
Some(rest) => renderChildren(rest),
|
||||
None => "",
|
||||
},
|
||||
}
|
||||
|
||||
// Render a node to an XML string
|
||||
pub fn render(node: Node): String =
|
||||
match node {
|
||||
Element(tag, attrs, children) => {
|
||||
let attrStr = renderAttrs(attrs)
|
||||
if List.length(children) == 0 then
|
||||
"<" + tag + attrStr + "/>"
|
||||
else
|
||||
"<" + tag + attrStr + ">" + renderChildren(children) + "</" + tag + ">"
|
||||
},
|
||||
Text(content) => escape(content),
|
||||
CData(content) => "<![CDATA[" + content + "]]>",
|
||||
Raw(content) => content,
|
||||
}
|
||||
|
||||
// Render a complete XML document with declaration
|
||||
pub fn document(root: Node): String =
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + render(root)
|
||||
|
||||
// Render with a custom declaration (e.g., for RSS stylesheets)
|
||||
pub fn documentWithDecl(decl: String, root: Node): String =
|
||||
decl + "\n" + render(root)
|
||||
8
packages/xml/lux.toml
Normal file
8
packages/xml/lux.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[project]
|
||||
name = "xml"
|
||||
version = "0.1.0"
|
||||
description = "XML builder for Lux"
|
||||
authors = ["Brandon Lucas"]
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
105
packages/xml/test_integration.lux
Normal file
105
packages/xml/test_integration.lux
Normal file
@@ -0,0 +1,105 @@
|
||||
import lib
|
||||
|
||||
// Integration tests: building real-world XML documents
|
||||
|
||||
// Build an HTML page structure
|
||||
fn test_build_html_page(): Unit with {Test} = {
|
||||
let head = lib.el("head", [
|
||||
lib.textEl("title", "My Page"),
|
||||
lib.selfClosing("meta", [lib.attr("charset", "utf-8")]),
|
||||
lib.selfClosing("link", [lib.attr("rel", "stylesheet"), lib.attr("href", "style.css")])
|
||||
])
|
||||
let body = lib.el("body", [
|
||||
lib.el("header", [lib.textEl("h1", "Welcome")]),
|
||||
lib.el("main", [lib.textEl("p", "Hello world")]),
|
||||
lib.el("footer", [lib.textEl("p", "Copyright 2025")])
|
||||
])
|
||||
let html = lib.el("html", [head, body])
|
||||
let result = lib.render(html)
|
||||
Test.assert(String.contains(result, "<title>My Page</title>"), "page has title")
|
||||
Test.assert(String.contains(result, "<meta charset=\"utf-8\"/>"), "page has meta charset")
|
||||
Test.assert(String.contains(result, "<h1>Welcome</h1>"), "page has h1")
|
||||
Test.assert(String.contains(result, "<p>Hello world</p>"), "page has paragraph")
|
||||
Test.assert(String.startsWith(result, "<html>"), "starts with html tag")
|
||||
Test.assert(String.endsWith(result, "</html>"), "ends with html tag")
|
||||
}
|
||||
|
||||
// Build a sitemap.xml
|
||||
fn test_build_sitemap(): Unit with {Test} = {
|
||||
let url1 = lib.el("url", [
|
||||
lib.textEl("loc", "https://example.com/"),
|
||||
lib.textEl("lastmod", "2025-01-29"),
|
||||
lib.textEl("priority", "1.0")
|
||||
])
|
||||
let url2 = lib.el("url", [
|
||||
lib.textEl("loc", "https://example.com/about"),
|
||||
lib.textEl("lastmod", "2025-01-15"),
|
||||
lib.textEl("priority", "0.8")
|
||||
])
|
||||
let urlset = lib.element("urlset", [lib.attr("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9")], [url1, url2])
|
||||
let sitemap = lib.document(urlset)
|
||||
Test.assert(String.startsWith(sitemap, "<?xml version=\"1.0\""), "sitemap has XML declaration")
|
||||
Test.assert(String.contains(sitemap, "xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\""), "sitemap has namespace")
|
||||
Test.assert(String.contains(sitemap, "<loc>https://example.com/</loc>"), "sitemap has first URL")
|
||||
Test.assert(String.contains(sitemap, "<loc>https://example.com/about</loc>"), "sitemap has second URL")
|
||||
}
|
||||
|
||||
// Build a form with various input types
|
||||
fn test_build_form(): Unit with {Test} = {
|
||||
let form = lib.element("form", [lib.attr("action", "/submit"), lib.attr("method", "post")], [
|
||||
lib.selfClosing("input", [lib.attr("type", "text"), lib.attr("name", "username")]),
|
||||
lib.selfClosing("input", [lib.attr("type", "password"), lib.attr("name", "pass")]),
|
||||
lib.textElAttr("button", [lib.attr("type", "submit")], "Login")
|
||||
])
|
||||
let result = lib.render(form)
|
||||
Test.assert(String.contains(result, "action=\"/submit\""), "form has action")
|
||||
Test.assert(String.contains(result, "type=\"text\""), "has text input")
|
||||
Test.assert(String.contains(result, "type=\"password\""), "has password input")
|
||||
Test.assert(String.contains(result, "<button type=\"submit\">Login</button>"), "has submit button")
|
||||
}
|
||||
|
||||
// Build nested table structure
|
||||
fn test_build_table(): Unit with {Test} = {
|
||||
let headerRow = lib.el("tr", [lib.textEl("th", "Name"), lib.textEl("th", "Age")])
|
||||
let row1 = lib.el("tr", [lib.textEl("td", "Alice"), lib.textEl("td", "30")])
|
||||
let row2 = lib.el("tr", [lib.textEl("td", "Bob"), lib.textEl("td", "25")])
|
||||
let table = lib.el("table", [lib.el("thead", [headerRow]), lib.el("tbody", [row1, row2])])
|
||||
let result = lib.render(table)
|
||||
Test.assert(String.contains(result, "<th>Name</th>"), "has header Name")
|
||||
Test.assert(String.contains(result, "<td>Alice</td>"), "has cell Alice")
|
||||
Test.assert(String.contains(result, "<td>25</td>"), "has cell 25")
|
||||
}
|
||||
|
||||
// documentWithDecl for custom processing instructions
|
||||
fn test_custom_declaration(): Unit with {Test} = {
|
||||
let decl = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<?xml-stylesheet type=\"text/xsl\" href=\"transform.xsl\"?>"
|
||||
let root = lib.textEl("data", "value")
|
||||
let result = lib.documentWithDecl(decl, root)
|
||||
Test.assert(String.contains(result, "xml-stylesheet"), "has stylesheet PI")
|
||||
Test.assert(String.contains(result, "<data>value</data>"), "has root element")
|
||||
}
|
||||
|
||||
// Build SVG-like structure
|
||||
fn test_build_svg(): Unit with {Test} = {
|
||||
let svg = lib.element("svg", [lib.attr("xmlns", "http://www.w3.org/2000/svg"), lib.attr("width", "100"), lib.attr("height", "100")], [
|
||||
lib.selfClosing("circle", [lib.attr("cx", "50"), lib.attr("cy", "50"), lib.attr("r", "40")]),
|
||||
lib.selfClosing("rect", [lib.attr("x", "10"), lib.attr("y", "10"), lib.attr("width", "80"), lib.attr("height", "80")])
|
||||
])
|
||||
let result = lib.render(svg)
|
||||
Test.assert(String.contains(result, "<svg xmlns=\"http://www.w3.org/2000/svg\""), "svg has namespace")
|
||||
Test.assert(String.contains(result, "<circle"), "has circle")
|
||||
Test.assert(String.contains(result, "<rect"), "has rect")
|
||||
}
|
||||
|
||||
// Content with special characters throughout
|
||||
fn test_special_chars_in_content(): Unit with {Test} = {
|
||||
let node = lib.el("message", [
|
||||
lib.textEl("from", "Alice & Bob"),
|
||||
lib.textEl("subject", "Re: <Important>"),
|
||||
lib.el("body", [lib.cdata("Use <html> & \"xml\" freely")])
|
||||
])
|
||||
let result = lib.render(node)
|
||||
Test.assert(String.contains(result, "Alice & Bob"), "from is escaped")
|
||||
Test.assert(String.contains(result, "<Important>"), "subject is escaped")
|
||||
Test.assert(String.contains(result, "<![CDATA[Use <html> & \"xml\" freely]]>"), "cdata preserves content")
|
||||
}
|
||||
95
packages/xml/test_snapshot.lux
Normal file
95
packages/xml/test_snapshot.lux
Normal file
@@ -0,0 +1,95 @@
|
||||
import lib
|
||||
|
||||
// Snapshot tests: compare full XML output against golden strings
|
||||
|
||||
// Snapshot: simple HTML page
|
||||
fn test_snapshot_html_page(): Unit with {Test} = {
|
||||
let page = lib.el("html", [
|
||||
lib.el("head", [lib.textEl("title", "Test")]),
|
||||
lib.el("body", [lib.textEl("p", "Hello")])
|
||||
])
|
||||
let expected = "<html><head><title>Test</title></head><body><p>Hello</p></body></html>"
|
||||
Test.assertEqualMsg(expected, lib.render(page), "snap: HTML page")
|
||||
}
|
||||
|
||||
// Snapshot: self-closing tags
|
||||
fn test_snapshot_self_closing(): Unit with {Test} = {
|
||||
let node = lib.el("head", [
|
||||
lib.selfClosing("meta", [lib.attr("charset", "utf-8")]),
|
||||
lib.selfClosing("link", [lib.attr("rel", "stylesheet"), lib.attr("href", "/css/main.css")])
|
||||
])
|
||||
let expected = "<head><meta charset=\"utf-8\"/><link rel=\"stylesheet\" href=\"/css/main.css\"/></head>"
|
||||
Test.assertEqualMsg(expected, lib.render(node), "snap: self-closing tags")
|
||||
}
|
||||
|
||||
// Snapshot: XML document with declaration
|
||||
fn test_snapshot_xml_document(): Unit with {Test} = {
|
||||
let root = lib.el("config", [
|
||||
lib.textEl("name", "MyApp"),
|
||||
lib.textEl("version", "1.0")
|
||||
])
|
||||
let expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<config><name>MyApp</name><version>1.0</version></config>"
|
||||
Test.assertEqualMsg(expected, lib.document(root), "snap: XML document")
|
||||
}
|
||||
|
||||
// Snapshot: element with escaped content
|
||||
fn test_snapshot_escaped_content(): Unit with {Test} = {
|
||||
let node = lib.textEl("message", "Hello <world> & \"everyone\"")
|
||||
let expected = "<message>Hello <world> & "everyone"</message>"
|
||||
Test.assertEqualMsg(expected, lib.render(node), "snap: escaped content")
|
||||
}
|
||||
|
||||
// Snapshot: CDATA section
|
||||
fn test_snapshot_cdata(): Unit with {Test} = {
|
||||
let node = lib.el("script", [lib.cdata("if (a < b && c > d) then run()")])
|
||||
let expected = "<script><![CDATA[if (a < b && c > d) then run()]]></script>"
|
||||
Test.assertEqualMsg(expected, lib.render(node), "snap: CDATA in script")
|
||||
}
|
||||
|
||||
// Snapshot: mixed node types
|
||||
fn test_snapshot_mixed_nodes(): Unit with {Test} = {
|
||||
let node = lib.el("content", [
|
||||
lib.text("Normal "),
|
||||
lib.raw("<br/>"),
|
||||
lib.text(" text & "),
|
||||
lib.cdata("<special>")
|
||||
])
|
||||
let expected = "<content>Normal <br/> text & <![CDATA[<special>]]></content>"
|
||||
Test.assertEqualMsg(expected, lib.render(node), "snap: mixed node types")
|
||||
}
|
||||
|
||||
// Snapshot: deeply nested structure
|
||||
fn test_snapshot_deep_nesting(): Unit with {Test} = {
|
||||
let node = lib.el("l1", [lib.el("l2", [lib.el("l3", [lib.el("l4", [lib.el("l5", [lib.text("deep")])])])])])
|
||||
let expected = "<l1><l2><l3><l4><l5>deep</l5></l4></l3></l2></l1>"
|
||||
Test.assertEqualMsg(expected, lib.render(node), "snap: 5-level nesting")
|
||||
}
|
||||
|
||||
// Snapshot: attribute escaping
|
||||
fn test_snapshot_attr_escaping(): Unit with {Test} = {
|
||||
let node = lib.selfClosing("div", [
|
||||
lib.attr("title", "a \"test\" & <more>"),
|
||||
lib.attr("class", "a&b")
|
||||
])
|
||||
let expected = "<div title=\"a "test" & <more>\" class=\"a&b\"/>"
|
||||
Test.assertEqualMsg(expected, lib.render(node), "snap: attribute escaping")
|
||||
}
|
||||
|
||||
// Snapshot: empty elements
|
||||
fn test_snapshot_empty_elements(): Unit with {Test} = {
|
||||
Test.assertEqualMsg("<br/>", lib.render(lib.el("br", [])), "snap: empty el")
|
||||
Test.assertEqualMsg("<hr/>", lib.render(lib.selfClosing("hr", [])), "snap: selfClosing")
|
||||
Test.assertEqualMsg("<p></p>", lib.render(lib.textEl("p", "")), "snap: textEl with empty content")
|
||||
}
|
||||
|
||||
// Snapshot: sitemap entry
|
||||
fn test_snapshot_sitemap_entry(): Unit with {Test} = {
|
||||
let url = lib.el("url", [
|
||||
lib.textEl("loc", "https://example.com/page"),
|
||||
lib.textEl("lastmod", "2025-01-29"),
|
||||
lib.textEl("changefreq", "weekly"),
|
||||
lib.textEl("priority", "0.8")
|
||||
])
|
||||
let expected = "<url><loc>https://example.com/page</loc><lastmod>2025-01-29</lastmod><changefreq>weekly</changefreq><priority>0.8</priority></url>"
|
||||
Test.assertEqualMsg(expected, lib.render(url), "snap: sitemap URL entry")
|
||||
}
|
||||
118
packages/xml/test_unit.lux
Normal file
118
packages/xml/test_unit.lux
Normal file
@@ -0,0 +1,118 @@
|
||||
import lib
|
||||
|
||||
// --- escape edge cases ---
|
||||
|
||||
fn test_escape_all_chars(): Unit with {Test} = {
|
||||
Test.assertEqualMsg("&", lib.escape("&"), "escape ampersand")
|
||||
Test.assertEqualMsg("<", lib.escape("<"), "escape less-than")
|
||||
Test.assertEqualMsg(">", lib.escape(">"), "escape greater-than")
|
||||
Test.assertEqualMsg(""", lib.escape("\""), "escape double quote")
|
||||
Test.assertEqualMsg("'", lib.escape("'"), "escape single quote")
|
||||
}
|
||||
|
||||
fn test_escape_empty(): Unit with {Test} =
|
||||
Test.assertEqualMsg("", lib.escape(""), "escape empty string")
|
||||
|
||||
fn test_escape_no_special(): Unit with {Test} =
|
||||
Test.assertEqualMsg("hello world 123", lib.escape("hello world 123"), "escape plain text unchanged")
|
||||
|
||||
fn test_escape_all_special(): Unit with {Test} =
|
||||
Test.assertEqualMsg("<&>"'", lib.escape("<&>\"'"), "escape all special chars together")
|
||||
|
||||
fn test_escape_mixed(): Unit with {Test} =
|
||||
Test.assertEqualMsg("a & b < c", lib.escape("a & b < c"), "escape mixed content")
|
||||
|
||||
// --- node constructors ---
|
||||
|
||||
fn test_text_node_simple(): Unit with {Test} =
|
||||
Test.assertEqualMsg("hello", lib.render(lib.text("hello")), "text node renders content")
|
||||
|
||||
fn test_text_node_escapes(): Unit with {Test} =
|
||||
Test.assertEqualMsg("<script>", lib.render(lib.text("<script>")), "text node escapes HTML")
|
||||
|
||||
fn test_text_node_empty(): Unit with {Test} =
|
||||
Test.assertEqualMsg("", lib.render(lib.text("")), "empty text node")
|
||||
|
||||
fn test_cdata_preserves_special(): Unit with {Test} =
|
||||
Test.assertEqualMsg("<![CDATA[<b>bold & stuff</b>]]>", lib.render(lib.cdata("<b>bold & stuff</b>")), "cdata preserves special chars")
|
||||
|
||||
fn test_raw_no_escape(): Unit with {Test} =
|
||||
Test.assertEqualMsg("<br/>", lib.render(lib.raw("<br/>")), "raw passes through unchanged")
|
||||
|
||||
fn test_raw_empty(): Unit with {Test} =
|
||||
Test.assertEqualMsg("", lib.render(lib.raw("")), "empty raw node")
|
||||
|
||||
// --- element rendering ---
|
||||
|
||||
fn test_self_closing_no_attrs(): Unit with {Test} =
|
||||
Test.assertEqualMsg("<br/>", lib.render(lib.selfClosing("br", [])), "self-closing no attrs")
|
||||
|
||||
fn test_self_closing_with_attr(): Unit with {Test} =
|
||||
Test.assertEqualMsg("<img src=\"photo.jpg\"/>", lib.render(lib.selfClosing("img", [lib.attr("src", "photo.jpg")])), "self-closing with attr")
|
||||
|
||||
fn test_self_closing_multiple_attrs(): Unit with {Test} = {
|
||||
let node = lib.selfClosing("input", [lib.attr("type", "text"), lib.attr("name", "q"), lib.attr("value", "")])
|
||||
Test.assertEqualMsg("<input type=\"text\" name=\"q\" value=\"\"/>", lib.render(node), "self-closing with multiple attrs")
|
||||
}
|
||||
|
||||
fn test_el_empty_children(): Unit with {Test} =
|
||||
Test.assertEqualMsg("<div/>", lib.render(lib.el("div", [])), "el with no children self-closes")
|
||||
|
||||
fn test_el_with_children(): Unit with {Test} = {
|
||||
let node = lib.el("ul", [lib.textEl("li", "a"), lib.textEl("li", "b")])
|
||||
Test.assertEqualMsg("<ul><li>a</li><li>b</li></ul>", lib.render(node), "el with children")
|
||||
}
|
||||
|
||||
fn test_text_el(): Unit with {Test} =
|
||||
Test.assertEqualMsg("<title>Hello</title>", lib.render(lib.textEl("title", "Hello")), "textEl renders correctly")
|
||||
|
||||
fn test_text_el_escapes(): Unit with {Test} =
|
||||
Test.assertEqualMsg("<p>a & b</p>", lib.render(lib.textEl("p", "a & b")), "textEl escapes content")
|
||||
|
||||
fn test_text_el_attr(): Unit with {Test} = {
|
||||
let node = lib.textElAttr("a", [lib.attr("href", "/page")], "click")
|
||||
Test.assertEqualMsg("<a href=\"/page\">click</a>", lib.render(node), "textElAttr renders correctly")
|
||||
}
|
||||
|
||||
// --- attribute edge cases ---
|
||||
|
||||
fn test_attr_value_escaping(): Unit with {Test} = {
|
||||
let node = lib.selfClosing("div", [lib.attr("data", "a&b<c\"d'e")])
|
||||
Test.assertEqualMsg("<div data=\"a&b<c"d'e\"/>", lib.render(node), "attr value escapes all special chars")
|
||||
}
|
||||
|
||||
fn test_attr_empty_value(): Unit with {Test} = {
|
||||
let node = lib.selfClosing("input", [lib.attr("disabled", "")])
|
||||
Test.assertEqualMsg("<input disabled=\"\"/>", lib.render(node), "attr with empty value")
|
||||
}
|
||||
|
||||
// --- nesting depth ---
|
||||
|
||||
fn test_deeply_nested(): Unit with {Test} = {
|
||||
let deep = lib.el("a", [lib.el("b", [lib.el("c", [lib.el("d", [lib.text("deep")])])])])
|
||||
Test.assertEqualMsg("<a><b><c><d>deep</d></c></b></a>", lib.render(deep), "4 levels deep")
|
||||
}
|
||||
|
||||
// --- document ---
|
||||
|
||||
fn test_document_declaration(): Unit with {Test} = {
|
||||
let doc = lib.document(lib.textEl("root", ""))
|
||||
Test.assert(String.startsWith(doc, "<?xml version=\"1.0\""), "document starts with XML declaration")
|
||||
}
|
||||
|
||||
fn test_document_with_custom_decl(): Unit with {Test} = {
|
||||
let doc = lib.documentWithDecl("<?xml version=\"1.1\"?>", lib.textEl("root", ""))
|
||||
Test.assert(String.startsWith(doc, "<?xml version=\"1.1\"?>"), "custom declaration")
|
||||
}
|
||||
|
||||
// --- mixed children ---
|
||||
|
||||
fn test_mixed_children_types(): Unit with {Test} = {
|
||||
let node = lib.el("div", [
|
||||
lib.text("Hello "),
|
||||
lib.raw("<strong>world</strong>"),
|
||||
lib.text(" & "),
|
||||
lib.cdata("raw data")
|
||||
])
|
||||
Test.assertEqualMsg("<div>Hello <strong>world</strong> & <![CDATA[raw data]]></div>", lib.render(node), "mixed children types")
|
||||
}
|
||||
74
packages/xml/test_xml.lux
Normal file
74
packages/xml/test_xml.lux
Normal file
@@ -0,0 +1,74 @@
|
||||
import lib
|
||||
|
||||
fn test_escape(): Unit with {Test} = {
|
||||
Test.assertEqualMsg("<b>hello</b>", lib.escape("<b>hello</b>"), "escape angle brackets")
|
||||
Test.assertEqualMsg("a & b", lib.escape("a & b"), "escape ampersand")
|
||||
Test.assertEqualMsg(""quoted"", lib.escape("\"quoted\""), "escape quotes")
|
||||
}
|
||||
|
||||
fn test_text_node(): Unit with {Test} = {
|
||||
Test.assertEqualMsg("hello", lib.render(lib.text("hello")), "text node simple")
|
||||
Test.assertEqualMsg("<b>", lib.render(lib.text("<b>")), "text node escaped")
|
||||
}
|
||||
|
||||
fn test_cdata(): Unit with {Test} = {
|
||||
Test.assertEqualMsg("<![CDATA[hello <b>world</b>]]>", lib.render(lib.cdata("hello <b>world</b>")), "cdata node")
|
||||
}
|
||||
|
||||
fn test_raw(): Unit with {Test} = {
|
||||
Test.assertEqualMsg("<br/>", lib.render(lib.raw("<br/>")), "raw node")
|
||||
}
|
||||
|
||||
fn test_text_element(): Unit with {Test} = {
|
||||
Test.assertEqualMsg("<title>Hello</title>", lib.render(lib.textEl("title", "Hello")), "text element")
|
||||
}
|
||||
|
||||
fn test_element_with_attrs(): Unit with {Test} = {
|
||||
let linkEl = lib.element("link", [lib.attr("href", "http://example.com"), lib.attr("rel", "alternate")], [])
|
||||
Test.assertEqualMsg("<link href=\"http://example.com\" rel=\"alternate\"/>", lib.render(linkEl), "element with attrs self-closing")
|
||||
}
|
||||
|
||||
fn test_self_closing(): Unit with {Test} = {
|
||||
Test.assertEqualMsg("<br/>", lib.render(lib.selfClosing("br", [])), "self-closing no attrs")
|
||||
Test.assertEqualMsg("<img src=\"photo.jpg\"/>", lib.render(lib.selfClosing("img", [lib.attr("src", "photo.jpg")])), "self-closing with attr")
|
||||
}
|
||||
|
||||
fn test_nested_elements(): Unit with {Test} = {
|
||||
let nested = lib.el("root", [lib.textEl("child1", "a"), lib.textEl("child2", "b")])
|
||||
Test.assertEqualMsg("<root><child1>a</child1><child2>b</child2></root>", lib.render(nested), "nested elements")
|
||||
}
|
||||
|
||||
fn test_element_with_attrs_and_children(): Unit with {Test} = {
|
||||
let withAttr = lib.element("item", [lib.attr("id", "1")], [lib.textEl("name", "Test")])
|
||||
Test.assertEqualMsg("<item id=\"1\"><name>Test</name></item>", lib.render(withAttr), "element with attrs and children")
|
||||
}
|
||||
|
||||
fn test_text_element_with_attrs(): Unit with {Test} = {
|
||||
let attrText = lib.textElAttr("a", [lib.attr("href", "/page")], "Click")
|
||||
Test.assertEqualMsg("<a href=\"/page\">Click</a>", lib.render(attrText), "text element with attrs")
|
||||
}
|
||||
|
||||
fn test_xml_document(): Unit with {Test} = {
|
||||
let doc = lib.document(lib.textEl("root", "content"))
|
||||
let expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>content</root>"
|
||||
Test.assertEqualMsg(expected, doc, "xml document")
|
||||
}
|
||||
|
||||
fn test_deeply_nested(): Unit with {Test} = {
|
||||
let deep = lib.el("a", [lib.el("b", [lib.el("c", [lib.text("deep")])])])
|
||||
Test.assertEqualMsg("<a><b><c>deep</c></b></a>", lib.render(deep), "deeply nested")
|
||||
}
|
||||
|
||||
fn test_empty_element(): Unit with {Test} = {
|
||||
Test.assertEqualMsg("<empty/>", lib.render(lib.el("empty", [])), "el empty children self-closes")
|
||||
}
|
||||
|
||||
fn test_attribute_value_escaping(): Unit with {Test} = {
|
||||
let escapedAttr = lib.selfClosing("div", [lib.attr("data", "a&b<c")])
|
||||
Test.assertEqualMsg("<div data=\"a&b<c\"/>", lib.render(escapedAttr), "attribute value escaping")
|
||||
}
|
||||
|
||||
fn test_mixed_children(): Unit with {Test} = {
|
||||
let mixed = lib.el("p", [lib.text("Hello "), lib.raw("<b>world</b>")])
|
||||
Test.assertEqualMsg("<p>Hello <b>world</b></p>", lib.render(mixed), "mixed text and raw children")
|
||||
}
|
||||
Reference in New Issue
Block a user