//! General template tests for Pugz engine const helper = @import("helper.zig"); const expectOutput = helper.expectOutput; // ───────────────────────────────────────────────────────────────────────────── // Test Case 1: Simple interpolation // ───────────────────────────────────────────────────────────────────────────── test "Simple interpolation" { // Quotes don't need escaping in text content (only in attribute values) try expectOutput( "p #{name}'s Pug source code!", .{ .name = "ankit patial" }, "

ankit patial's Pug source code!

", ); } test "Interpolation only as text" { try expectOutput( "h1.header #{header}", .{ .header = "MyHeader" }, "

MyHeader

", ); } // TODO: Each loop with interpolation requires runtime iteration support // test "Interpolation in each loop" { // try expectOutput( // \\ul.list // \\ each item in list // \\ li.item #{item} // , .{ .list = &[_][]const u8{ "a", "b" } }, // \\ // ); // } // ───────────────────────────────────────────────────────────────────────────── // Test Case 2: Attributes with inline text // ───────────────────────────────────────────────────────────────────────────── test "Link with href attribute" { try expectOutput( "a(href='//google.com') Google", .{}, "Google", ); } test "Link with class and href (space separated)" { try expectOutput( "a(class='button' href='//google.com') Google", .{}, "Google", ); } test "Link with class and href (comma separated)" { try expectOutput( "a(class='button', href='//google.com') Google", .{}, "Google", ); } // ───────────────────────────────────────────────────────────────────────────── // Test Case 3: Boolean attributes (multiline) // ───────────────────────────────────────────────────────────────────────────── test "Checkbox with boolean checked attribute" { // HTML5 terse boolean attribute try expectOutput( \\input( \\ type='checkbox' \\ name='agreement' \\ checked \\) , .{}, "", ); } // ───────────────────────────────────────────────────────────────────────────── // Test Case 4: Backtick template literal with multiline JSON // TODO: Backtick template literals require JS expression evaluation // ───────────────────────────────────────────────────────────────────────────── // test "Input with multiline JSON data attribute" { // try expectOutput( // \\input(data-json=` // \\ { // \\ "very-long": "piece of ", // \\ "data": true // \\ } // \\`) // , // .{}, // \\ // , // ); // } // ───────────────────────────────────────────────────────────────────────────── // Test Case 5: Escaped vs unescaped attribute values // ───────────────────────────────────────────────────────────────────────────── test "Escaped attribute value" { try expectOutput( "div(escaped=\"\")", .{}, "
", ); } // TODO: Unescaped attribute syntax != not yet implemented // test "Unescaped attribute value" { // try expectOutput( // "div(unescaped!=\"\")", // .{}, // "
\">
", // ); // } // ───────────────────────────────────────────────────────────────────────────── // Test Case 6: Boolean attributes with true/false values // ───────────────────────────────────────────────────────────────────────────── test "Checkbox with checked (no value)" { // HTML5 terse mode: boolean attribute without value try expectOutput( "input(type='checkbox' checked)", .{}, "", ); } test "Checkbox with checked=true" { // HTML5 terse mode: checked=true renders as checked try expectOutput( "input(type='checkbox' checked=true)", .{}, "", ); } test "Checkbox with checked=false (omitted)" { // checked=false means no checked attribute try expectOutput( "input(type='checkbox' checked=false)", .{}, "", ); } // ───────────────────────────────────────────────────────────────────────────── // Test Case 7: Object literal as style attribute // TODO: JS object literal parsing in attributes not yet implemented // ───────────────────────────────────────────────────────────────────────────── // test "Style object literal" { // try expectOutput( // "a(style={color: 'red', background: 'green'})", // .{}, // "", // ); // } // ───────────────────────────────────────────────────────────────────────────── // Test Case 8: Array literals for class attribute // TODO: JS array literal parsing in attributes not yet implemented // ───────────────────────────────────────────────────────────────────────────── // test "Class array literal" { // try expectOutput("a(class=['foo', 'bar', 'baz'])", .{}, ""); // } // test "Class array merged with shorthand and array" { // try expectOutput( // "a.bang(class=['foo', 'bar', 'baz'] class=['bing'])", // .{}, // "", // ); // } // ───────────────────────────────────────────────────────────────────────────── // Test Case 9: Shorthand class syntax // ───────────────────────────────────────────────────────────────────────────── test "Shorthand class on anchor" { try expectOutput("a.button", .{}, ""); } test "Implicit div with class" { try expectOutput(".content", .{}, "
"); } test "Shorthand ID on anchor" { try expectOutput("a#main-link", .{}, ""); } test "Implicit div with ID" { try expectOutput("#content", .{}, "
"); } // ───────────────────────────────────────────────────────────────────────────── // Test Case 10: &attributes spread operator // TODO: &attributes spread with JS object literal not yet implemented // ───────────────────────────────────────────────────────────────────────────── // test "Attributes spread with &attributes" { // try expectOutput( // "div#foo(data-bar=\"foo\")&attributes({'data-foo': 'bar'})", // .{}, // "
", // ); // } // ───────────────────────────────────────────────────────────────────────────── // Test Case 11: case/when/default // TODO: case/when requires runtime expression evaluation // ───────────────────────────────────────────────────────────────────────────── // test "Case statement with friends=1" { // try expectOutput( // \\case friends // \\ when 0 // \\ p you have no friends // \\ when 1 // \\ p you have a friend // \\ default // \\ p you have #{friends} friends // , .{ .friends = @as(i64, 1) }, "

you have a friend

"); // } // test "Case statement with friends=10" { // try expectOutput( // \\case friends // \\ when 0 // \\ p you have no friends // \\ when 1 // \\ p you have a friend // \\ default // \\ p you have #{friends} friends // , .{ .friends = @as(i64, 10) }, "

you have 10 friends

"); // } // ───────────────────────────────────────────────────────────────────────────── // Test Case 12: Conditionals (if/else if/else) // TODO: Conditionals require runtime expression evaluation // ───────────────────────────────────────────────────────────────────────────── // test "If condition true" { // try expectOutput( // \\if showMessage // \\ p Hello! // , .{ .showMessage = true }, "

Hello!

"); // } // test "If condition false (no data)" { // try expectOutput( // \\if showMessage // \\ p Hello! // , .{}, ""); // } // test "If condition false with else" { // try expectOutput( // \\if showMessage // \\ p Hello! // \\else // \\ p Goodbye! // , .{ .showMessage = false }, "

Goodbye!

"); // } // test "Unless condition (negated if)" { // try expectOutput( // \\unless isHidden // \\ p Visible content // , .{ .isHidden = false }, "

Visible content

"); // } // ───────────────────────────────────────────────────────────────────────────── // Test Case 13: Nested conditionals with dot notation // TODO: Dot notation (user.description) and conditionals require runtime expression evaluation // ───────────────────────────────────────────────────────────────────────────── // test "Condition with nested user.description" { // try expectOutput( // \\#user // \\ if user.description // \\ h2.green Description // \\ p.description= user.description // \\ else if authorised // \\ h2.blue Description // \\ p.description No description (authorised) // \\ else // \\ h2.red Description // \\ p.description User has no description // , .{ .user = .{ .description = "foo bar baz" }, .authorised = false }, // \\
// \\

Description

// \\

foo bar baz

// \\
// ); // } // test "Condition with nested user.description and autorized" { // try expectOutput( // \\#user // \\ if user.description // \\ h2.green Description // \\ p.description= user.description // \\ else if authorised // \\ h2.blue Description // \\ p.description No description (authorised) // \\ else // \\ h2.red Description // \\ p.description User has no description // , .{ .authorised = true }, // \\
// \\

Description

// \\

No description (authorised)

// \\
// ); // } // test "Condition with nested user.description and no data" { // try expectOutput( // \\#user // \\ if user.description // \\ h2.green Description // \\ p.description= user.description // \\ else if authorised // \\ h2.blue Description // \\ p.description No description (authorised) // \\ else // \\ h2.red Description // \\ p.description User has no description // , .{}, // \\
// \\

Description

// \\

User has no description

// \\
// ); // } // ───────────────────────────────────────────────────────────────────────────── // Tag Interpolation Tests // TODO: Tag interpolation #[tag] requires runtime rendering support // ───────────────────────────────────────────────────────────────────────────── // test "Simple tag interpolation" { // try expectOutput( // "p This is #[em emphasized] text.", // .{}, // "

This is emphasized text.

", // ); // } // test "Tag interpolation with strong" { // try expectOutput( // "p This is #[strong important] text.", // .{}, // "

This is important text.

", // ); // } // test "Tag interpolation with link" { // try expectOutput( // "p Click #[a(href='/') here] to continue.", // .{}, // "

Click here to continue.

", // ); // } // test "Tag interpolation with class" { // try expectOutput( // "p This is #[span.highlight highlighted] text.", // .{}, // "

This is highlighted text.

", // ); // } // test "Tag interpolation with id" { // try expectOutput( // "p See #[span#note this note] for details.", // .{}, // "

See this note for details.

", // ); // } // test "Tag interpolation with class and id" { // try expectOutput( // "p Check #[span#info.tooltip the tooltip] here.", // .{}, // "

Check the tooltip here.

", // ); // } // test "Multiple tag interpolations" { // try expectOutput( // "p This has #[em emphasis] and #[strong strength].", // .{}, // "

This has emphasis and strength.

", // ); // } // test "Tag interpolation with multiple classes" { // try expectOutput( // "p Text with #[span.red.bold styled content] here.", // .{}, // "

Text with styled content here.

", // ); // } // ───────────────────────────────────────────────────────────────────────────── // Iteration Tests // TODO: Each loop requires runtime iteration support with item context // ───────────────────────────────────────────────────────────────────────────── // test "each loop with array" { // try expectOutput( // \\ul // \\ each item in items // \\ li= item // , .{ .items = &[_][]const u8{ "apple", "banana", "cherry" } }, // \\ // ); // } // test "for loop as alias for each" { // try expectOutput( // \\ul // \\ for item in items // \\ li= item // , .{ .items = &[_][]const u8{ "one", "two", "three" } }, // \\ // ); // } // test "each loop with index" { // try expectOutput( // \\ul // \\ each item, idx in items // \\ li #{idx}: #{item} // , .{ .items = &[_][]const u8{ "a", "b", "c" } }, // \\ // ); // } // test "each loop with else block" { // try expectOutput( // \\ul // \\ each item in items // \\ li= item // \\ else // \\ li No items found // , .{ .items = &[_][]const u8{} }, // \\ // ); // } // ───────────────────────────────────────────────────────────────────────────── // Mixin Tests // TODO: Mixins require full runtime mixin resolution and argument binding // ───────────────────────────────────────────────────────────────────────────── // test "Basic mixin declaration and call" { // try expectOutput( // \\mixin list // \\ ul // \\ li foo // \\ li bar // \\+list // , .{}, // \\ // ); // } // test "Mixin with arguments" { // try expectOutput( // \\mixin pet(name) // \\ li.pet= name // \\ul // \\ +pet('cat') // \\ +pet('dog') // , .{}, // \\ // ); // } // test "Mixin with default argument" { // try expectOutput( // \\mixin greet(name='World') // \\ p Hello, #{name}! // \\+greet // \\+greet('Zig') // , .{}, // \\

Hello, World!

// \\

Hello, Zig!

// ); // } // test "Mixin with block content" { // try expectOutput( // \\mixin article(title) // \\ .article // \\ h1= title // \\ block // \\+article('Hello') // \\ p This is content // \\ p More content // , .{}, // \\
// \\

Hello

// \\

This is content

// \\

More content

// \\
// ); // } // test "Mixin with block and no content passed" { // try expectOutput( // \\mixin box // \\ .box // \\ block // \\+box // , .{}, // \\
// \\
// ); // } // test "Mixin with attributes" { // try expectOutput( // \\mixin link(href, name) // \\ a(href=href)&attributes(attributes)= name // \\+link('/foo', 'foo')(class="btn") // , .{}, // \\foo // ); // } // test "Mixin with rest arguments" { // try expectOutput( // \\mixin list(id, ...items) // \\ ul(id=id) // \\ each item in items // \\ li= item // \\+list('my-list', 'one', 'two', 'three') // , .{}, // \\ // ); // } // test "Mixin with rest arguments empty" { // try expectOutput( // \\mixin list(id, ...items) // \\ ul(id=id) // \\ each item in items // \\ li= item // \\+list('my-list') // , .{}, // \\ // ); // } // ───────────────────────────────────────────────────────────────────────────── // Plain Text Tests // ───────────────────────────────────────────────────────────────────────────── test "Inline text in tag" { try expectOutput( \\p This is plain old text content. , .{}, \\

This is plain old text content.

); } // TODO: Piped text whitespace handling needs lexer fix // test "Piped text basic" { // try expectOutput( // \\p // \\ | The pipe always goes at the beginning of its own line, // \\ | not counting indentation. // , .{}, // \\

// \\ The pipe always goes at the beginning of its own line, // \\ not counting indentation. // \\

// ); // } // test "Piped text with inline tags" { // try expectOutput( // \\| You put the em // \\em pha // \\| sis on the wrong syl // \\em la // \\| ble. // , .{}, // \\You put the em // \\phasis on the wrong syl // \\lable. // ); // } test "Block text with dot" { // Multi-line content in whitespace-preserving elements gets leading newline and preserved indentation try expectOutput( \\script. \\ if (usingPug) \\ console.log('you are awesome') , .{}, \\ ); } // TODO: Block text whitespace preservation needs lexer fix // test "Block text with dot and attributes" { // // Multi-line content in whitespace-preserving elements gets leading newline and preserved indentation // try expectOutput( // \\style(type='text/css'). // \\ body { // \\ color: red; // \\ } // , .{}, // \\ // ); // } // TODO: Literal HTML passthrough needs proper whitespace handling // test "Literal HTML passthrough" { // try expectOutput( // \\ // \\p Hello from Pug // \\ // , .{}, // \\ // \\

Hello from Pug

// \\ // ); // } // test "Literal HTML mixed with Pug" { // try expectOutput( // \\div // \\ Literal HTML // \\ p Pug paragraph // , .{}, // \\
// \\Literal HTML // \\

Pug paragraph

// \\
// ); // } // ───────────────────────────────────────────────────────────────────────────── // Tag Tests // ───────────────────────────────────────────────────────────────────────────── test "Nested tags with indentation" { try expectOutput( \\ul \\ li Item A \\ li Item B \\ li Item C , .{}, \\ ); } test "Self-closing void elements" { // HTML5 terse mode: void elements without /> try expectOutput( \\img \\br \\input , .{}, \\ \\
\\ ); } test "Block expansion with colon" { // HTML5 terse mode: void elements without /> try expectOutput( \\a: img , .{}, \\ ); } test "Block expansion nested" { // Block expansion renders children inline (on same line) try expectOutput( \\ul \\ li: a(href='/') Home \\ li: a(href='/about') About , .{}, \\ ); } test "Explicit self-closing tag" { try expectOutput( \\foo/ , .{}, \\ ); } test "Explicit self-closing tag with attributes" { try expectOutput( \\foo(bar='baz')/ , .{}, \\ ); } // ───────────────────────────────────────────────────────────────────────────── // String concatenation in attributes // TODO: String concatenation requires JS expression evaluation // ───────────────────────────────────────────────────────────────────────────── // test "Attribute with string concatenation" { // try expectOutput( // \\button(class="btn btn-" + btnType) Click // , .{ .btnType = "secondary" }, // \\ // ); // } // test "Mixin with string concatenation in class" { // try expectOutput( // \\mixin btn(text, btnType="primary") // \\ button(class="btn btn-" + btnType)= text // \\+btn("Click me", "secondary") // , .{}, // \\ // ); // }