//! 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" }, "\")",
.{},
"",
);
}
test "Unescaped attribute value" {
try expectOutput(
"div(unescaped!=\"\")",
.{},
"\">",
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Test Case 6: Boolean attributes with true/false values
// ─────────────────────────────────────────────────────────────────────────────
test "Checkbox with checked (no value)" {
try expectOutput(
"input(type='checkbox' checked)",
.{},
"",
);
}
test "Checkbox with checked=true" {
try expectOutput(
"input(type='checkbox' checked=true)",
.{},
"",
);
}
test "Checkbox with checked=false (omitted)" {
try expectOutput(
"input(type='checkbox' checked=false)",
.{},
"",
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Test Case 7: Object literal as style attribute
// ─────────────────────────────────────────────────────────────────────────────
test "Style object literal" {
try expectOutput(
"a(style={color: 'red', background: 'green'})",
.{},
"",
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Test Case 8: Array literals for class attribute
// ─────────────────────────────────────────────────────────────────────────────
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
// ─────────────────────────────────────────────────────────────────────────────
test "Attributes spread with &attributes" {
try expectOutput(
"div#foo(data-bar=\"foo\")&attributes({'data-foo': 'bar'})",
.{},
"",
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Test Case 11: case/when/default
// ─────────────────────────────────────────────────────────────────────────────
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)
// ─────────────────────────────────────────────────────────────────────────────
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
// ─────────────────────────────────────────────────────────────────────────────
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
// ─────────────────────────────────────────────────────────────────────────────
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
// ─────────────────────────────────────────────────────────────────────────────
test "each loop with array" {
try expectOutput(
\\ul
\\ each item in items
\\ li= item
, .{ .items = &[_][]const u8{ "apple", "banana", "cherry" } },
\\
\\ - 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" } },
\\
\\ - 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" } },
\\
\\ - 0: a
\\ - 1: b
\\ - 2: c
\\
);
}
test "each loop with else block" {
try expectOutput(
\\ul
\\ each item in items
\\ li= item
\\ else
\\ li No items found
, .{ .items = &[_][]const u8{} },
\\
\\ - No items found
\\
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Mixin Tests
// ─────────────────────────────────────────────────────────────────────────────
test "Basic mixin declaration and call" {
try expectOutput(
\\mixin list
\\ ul
\\ li foo
\\ li bar
\\+list
, .{},
\\
\\ - foo
\\ - bar
\\
);
}
test "Mixin with arguments" {
try expectOutput(
\\mixin pet(name)
\\ li.pet= name
\\ul
\\ +pet('cat')
\\ +pet('dog')
, .{},
\\
\\ - cat
\\ - 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')
, .{},
\\
\\ - 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.
);
}
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')
, .{},
\\
);
}
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;
\\ }
, .{},
\\
);
}
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
, .{},
\\
\\ - Item A
\\ - Item B
\\ - Item C
\\
);
}
test "Self-closing void elements" {
try expectOutput(
\\img
\\br
\\input
, .{},
\\
\\
\\
);
}
test "Block expansion with colon" {
// Block expansion renders children inline (on same line)
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
// ─────────────────────────────────────────────────────────────────────────────
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")
, .{},
\\
);
}