@@ -284,9 +284,7 @@ fn error_else_without_if() {
284284fn error_each_bad_syntax() {
285285 let diags = parse_diagnostics("@each $x");
286286 assert!(
287− diags
288− .iter()
289− .any(|d| d.message.contains("@each requires")),
287+ diags.iter().any(|d| d.message.contains("@each requires")),
290288 "expected @each syntax error, got: {:?}",
291289 diags
292290 );
@@ -308,9 +306,7 @@ fn error_numeric_validation() {
308306fn error_opacity_range() {
309307 let diags = parse_diagnostics("@el [opacity 2.0]");
310308 assert!(
311− diags
312− .iter()
313− .any(|d| d.message.contains("between 0 and 1")),
309+ diags.iter().any(|d| d.message.contains("between 0 and 1")),
314310 "expected opacity range warning, got: {:?}",
315311 diags
316312 );
@@ -342,7 +338,9 @@ fn error_else_if_without_if() {
342338
343339#[test]
344340fn else_if_chain() {
345− let output = compile("@let x 2\n@if $x == 1\n @text one\n@else if $x == 2\n @text two\n@else\n @text other");
341+ let output = compile(
342+ "@let x 2\n@if $x == 1\n @text one\n@else if $x == 2\n @text two\n@else\n @text other",
343+ );
346344 assert!(output.contains("two"));
347345 assert!(!output.contains("one"));
348346 assert!(!output.contains("other"));
@@ -410,7 +408,9 @@ fn each_loop_expansion() {
410408fn aria_data_attrs_accepted() {
411409 let diags = parse_diagnostics("@el [aria-label Test, data-id 42]");
412410 assert!(
413− !diags.iter().any(|d| d.message.contains("unknown attribute")),
411+ !diags
412+ .iter()
413+ .any(|d| d.message.contains("unknown attribute")),
414414 "aria-*/data-* should not produce warnings, got: {:?}",
415415 diags
416416 );
@@ -426,7 +426,9 @@ fn aria_data_attrs_accepted() {
426426fn warning_unused_variable() {
427427 let diags = parse_diagnostics("@let color red\n@el [padding 10]");
428428 assert!(
429− diags.iter().any(|d| d.message.contains("unused variable") && d.message.contains("color")),
429+ diags
430+ .iter()
431+ .any(|d| d.message.contains("unused variable") && d.message.contains("color")),
430432 "expected unused variable warning, got: {:?}",
431433 diags
432434 );
@@ -446,7 +448,9 @@ fn no_warning_used_variable() {
446448fn warning_unused_function() {
447449 let diags = parse_diagnostics("@fn card\n @el [padding 10]\n@el");
448450 assert!(
449− diags.iter().any(|d| d.message.contains("unused function") && d.message.contains("card")),
451+ diags
452+ .iter()
453+ .any(|d| d.message.contains("unused function") && d.message.contains("card")),
450454 "expected unused function warning, got: {:?}",
451455 diags
452456 );
@@ -488,7 +492,9 @@ fn no_warning_used_define() {
488492fn warning_spacing_on_text() {
489493 let diags = parse_diagnostics("@text [spacing 10] hello");
490494 assert!(
491− diags.iter().any(|d| d.message.contains("spacing") && d.message.contains("no effect")),
495+ diags
496+ .iter()
497+ .any(|d| d.message.contains("spacing") && d.message.contains("no effect")),
492498 "expected spacing on @text warning, got: {:?}",
493499 diags
494500 );
@@ -498,7 +504,9 @@ fn warning_spacing_on_text() {
498504fn no_warning_spacing_on_row() {
499505 let diags = parse_diagnostics("@row [spacing 10]");
500506 assert!(
501− !diags.iter().any(|d| d.message.contains("spacing") && d.message.contains("no effect")),
507+ !diags
508+ .iter()
509+ .any(|d| d.message.contains("spacing") && d.message.contains("no effect")),
502510 "should not warn about spacing on @row, got: {:?}",
503511 diags
504512 );
@@ -508,7 +516,9 @@ fn no_warning_spacing_on_row() {
508516fn warning_placeholder_on_row() {
509517 let diags = parse_diagnostics("@row [placeholder test]");
510518 assert!(
511− diags.iter().any(|d| d.message.contains("placeholder") && d.message.contains("no effect")),
519+ diags
520+ .iter()
521+ .any(|d| d.message.contains("placeholder") && d.message.contains("no effect")),
512522 "expected placeholder on @row warning, got: {:?}",
513523 diags
514524 );
@@ -518,7 +528,9 @@ fn warning_placeholder_on_row() {
518528fn warning_for_on_non_label() {
519529 let diags = parse_diagnostics("@el [for email]");
520530 assert!(
521− diags.iter().any(|d| d.message.contains("'for'") && d.message.contains("@label")),
531+ diags
532+ .iter()
533+ .any(|d| d.message.contains("'for'") && d.message.contains("@label")),
522534 "expected 'for' on non-label warning, got: {:?}",
523535 diags
524536 );
@@ -546,14 +558,18 @@ fn each_range_with_index() {
546558
547559#[test]
548560fn named_slot_basic() {
549− let output = compile("@fn card\n @el\n @slot header\n @children\n@card\n @slot header\n @text Title\n @text Body");
561+ let output = compile(
562+ "@fn card\n @el\n @slot header\n @children\n@card\n @slot header\n @text Title\n @text Body",
563+ );
550564 assert!(output.contains("Title"));
551565 assert!(output.contains("Body"));
552566}
553567
554568#[test]
555569fn named_slot_default_content() {
556− let output = compile("@fn card\n @el\n @slot header\n @text Default\n @children\n@card\n @text Body");
570+ let output = compile(
571+ "@fn card\n @el\n @slot header\n @text Default\n @children\n@card\n @text Body",
572+ );
557573 assert!(output.contains("Default"));
558574 assert!(output.contains("Body"));
559575}
@@ -562,7 +578,9 @@ fn named_slot_default_content() {
562578
563579#[test]
564580fn style_block_output() {
565− let output = compile("@page Test\n@style\n .custom { color: red; }\n@el [class custom]\n @text styled");
581+ let output = compile(
582+ "@page Test\n@style\n .custom { color: red; }\n@el [class custom]\n @text styled",
583+ );
566584 assert!(output.contains(".custom{color:red;}") || output.contains(".custom { color: red; }"));
567585 assert!(output.contains("styled"));
568586}
@@ -721,7 +739,9 @@ fn item_alias_li() {
721739
722740#[test]
723741fn table_renders_proper_tags() {
724− let output = compile("@page T\n@table\n @thead\n @tr\n @th Header\n @tbody\n @tr\n @td Cell");
742+ let output = compile(
743+ "@page T\n@table\n @thead\n @tr\n @th Header\n @tbody\n @tr\n @td Cell",
744+ );
725745 assert!(output.contains("<table"));
726746 assert!(output.contains("<thead"));
727747 assert!(output.contains("<tbody"));
@@ -768,7 +788,9 @@ fn video_with_multiple_attrs() {
768788
769789#[test]
770790fn match_selects_correct_case() {
771− let output = compile("@let x b\n@match $x\n @case a\n @text A\n @case b\n @text B\n @default\n @text D");
791+ let output = compile(
792+ "@let x b\n@match $x\n @case a\n @text A\n @case b\n @text B\n @default\n @text D",
793+ );
772794 assert!(output.contains("B"));
773795 assert!(!output.contains(">A<"));
774796 assert!(!output.contains(">D<"));
@@ -776,7 +798,8 @@ fn match_selects_correct_case() {
776798
777799#[test]
778800fn match_falls_to_default() {
779− let output = compile("@let x z\n@match $x\n @case a\n @text A\n @default\n @text Default");
801+ let output =
802+ compile("@let x z\n@match $x\n @case a\n @text A\n @default\n @text Default");
780803 assert!(output.contains("Default"));
781804 assert!(!output.contains(">A<"));
782805}
@@ -865,7 +888,9 @@ fn warn_produces_diagnostic() {
865888fn warn_substitutes_variables() {
866889 let diags = parse_diagnostics("@let name test\n@warn Missing $name value");
867890 assert!(
868− diags.iter().any(|d| d.message.contains("Missing test value")),
891+ diags
892+ .iter()
893+ .any(|d| d.message.contains("Missing test value")),
869894 "expected substituted @warn, got: {:?}",
870895 diags
871896 );
@@ -878,7 +903,9 @@ fn warn_substitutes_variables() {
878903#[test]
879904fn image_auto_lazy_loading() {
880905 // First 3 images get fetchpriority="high" (above-the-fold), subsequent get loading="lazy"
881− let output = compile("@page T\n@image https://example.com/1.jpg\n@image https://example.com/2.jpg\n@image https://example.com/3.jpg\n@image https://example.com/4.jpg");
906+ let output = compile(
907+ "@page T\n@image https://example.com/1.jpg\n@image https://example.com/2.jpg\n@image https://example.com/3.jpg\n@image https://example.com/4.jpg",
908+ );
882909 // First 3 images: fetchpriority="high", no lazy loading
883910 assert!(output.contains("fetchpriority=\"high\""));
884911 // 4th image: loading="lazy" + decoding="async"
@@ -935,13 +962,17 @@ fn comparison_operators_contains() {
935962
936963#[test]
937964fn comparison_operators_starts_with() {
938− let output = compile("@page T\n@let url https://example.com\n@if $url starts-with https\n @text secure");
965+ let output = compile(
966+ "@page T\n@let url https://example.com\n@if $url starts-with https\n @text secure",
967+ );
939968 assert!(output.contains("secure"));
940969}
941970
942971#[test]
943972fn string_concat_operator() {
944− let output = compile("@page T\n@let first Hello\n@let last World\n@let full $first ~ \" \" ~ $last\n@text $full");
973+ let output = compile(
974+ "@page T\n@let first Hello\n@let last World\n@let full $first ~ \" \" ~ $last\n@text $full",
975+ );
945976 assert!(output.contains("Hello World"), "got: {}", output);
946977}
947978
@@ -1005,7 +1036,9 @@ fn internal_link_no_noopener() {
10051036
10061037#[test]
10071038fn dns_prefetch_for_external_domains() {
1008− let output = compile("@page T\n@link https://example.com\n Link\n@image https://cdn.example.org/img.jpg");
1039+ let output = compile(
1040+ "@page T\n@link https://example.com\n Link\n@image https://cdn.example.org/img.jpg",
1041+ );
10091042 assert!(output.contains("dns-prefetch"));
10101043 assert!(output.contains("example.com"));
10111044}
@@ -1026,7 +1059,12 @@ fn aria_live_passthrough() {
10261059#[test]
10271060fn defer_directive() {
10281061 let result = htmlang::parser::parse("@page T\n@defer\n @el\n Lazy content");
1029− assert!(result.diagnostics.iter().all(|d| d.severity != htmlang::parser::Severity::Error));
1062+ assert!(
1063+ result
1064+ .diagnostics
1065+ .iter()
1066+ .all(|d| d.severity != htmlang::parser::Severity::Error)
1067+ );
10301068 let html = htmlang::codegen::generate(&result.document);
10311069 assert!(html.contains("data-hl-defer"));
10321070 assert!(html.contains("IntersectionObserver"));
@@ -1053,7 +1091,9 @@ fn comparison_ends_with() {
10531091fn warning_ordered_on_non_list() {
10541092 let diags = parse_diagnostics("@el [ordered]");
10551093 assert!(
1056− diags.iter().any(|d| d.message.contains("ordered") && d.message.contains("@list")),
1094+ diags
1095+ .iter()
1096+ .any(|d| d.message.contains("ordered") && d.message.contains("@list")),
10571097 "expected ordered on non-list warning, got: {:?}",
10581098 diags
10591099 );
@@ -1063,7 +1103,9 @@ fn warning_ordered_on_non_list() {
10631103fn no_warning_ordered_on_list() {
10641104 let diags = parse_diagnostics("@list [ordered]\n @item x");
10651105 assert!(
1066− !diags.iter().any(|d| d.message.contains("ordered") && d.message.contains("no effect")),
1106+ !diags
1107+ .iter()
1108+ .any(|d| d.message.contains("ordered") && d.message.contains("no effect")),
10671109 "should not warn about ordered on @list, got: {:?}",
10681110 diags
10691111 );
@@ -1073,7 +1115,9 @@ fn no_warning_ordered_on_list() {
10731115fn warning_controls_on_non_media() {
10741116 let diags = parse_diagnostics("@el [controls]");
10751117 assert!(
1076− diags.iter().any(|d| d.message.contains("controls") && d.message.contains("@video")),
1118+ diags
1119+ .iter()
1120+ .any(|d| d.message.contains("controls") && d.message.contains("@video")),
10771121 "expected controls on non-media warning, got: {:?}",
10781122 diags
10791123 );
@@ -1087,7 +1131,10 @@ fn warning_controls_on_non_media() {
10871131fn fmt_normalizes_indentation() {
10881132 let input = "@row\n @col\n @text hello\n @col\n @text world";
10891133 let formatted = htmlang::fmt::format(input);
1090− assert_eq!(formatted, "@row\n @col\n @text hello\n @col\n @text world\n");
1134+ assert_eq!(
1135+ formatted,
1136+ "@row\n @col\n @text hello\n @col\n @text world\n"
1137+ );
10911138}
10921139
10931140#[test]
@@ -1363,7 +1410,9 @@ fn each_destructuring_pairs() {
13631410fn warning_missing_alt_on_image() {
13641411 let diags = parse_diagnostics("@image photo.jpg");
13651412 assert!(
1366− diags.iter().any(|d| d.message.contains("alt") && d.message.contains("accessibility")),
1413+ diags
1414+ .iter()
1415+ .any(|d| d.message.contains("alt") && d.message.contains("accessibility")),
13671416 "expected missing alt warning, got: {:?}",
13681417 diags
13691418 );
@@ -1373,7 +1422,9 @@ fn warning_missing_alt_on_image() {
13731422fn no_warning_with_alt_on_image() {
13741423 let diags = parse_diagnostics("@image [alt A photo] photo.jpg");
13751424 assert!(
1376− !diags.iter().any(|d| d.message.contains("missing") && d.message.contains("alt")),
1425+ !diags
1426+ .iter()
1427+ .any(|d| d.message.contains("missing") && d.message.contains("alt")),
13771428 "should not warn when alt is present, got: {:?}",
13781429 diags
13791430 );
@@ -1383,7 +1434,9 @@ fn no_warning_with_alt_on_image() {
13831434fn warning_invalid_hex_color() {
13841435 let diags = parse_diagnostics("@el [color #ggg]");
13851436 assert!(
1386− diags.iter().any(|d| d.message.contains("invalid hex color")),
1437+ diags
1438+ .iter()
1439+ .any(|d| d.message.contains("invalid hex color")),
13871440 "expected invalid hex color warning, got: {:?}",
13881441 diags
13891442 );
@@ -1393,7 +1446,9 @@ fn warning_invalid_hex_color() {
13931446fn no_warning_valid_hex_color() {
13941447 let diags = parse_diagnostics("@el [color #ff0000]");
13951448 assert!(
1396− !diags.iter().any(|d| d.message.contains("invalid hex color")),
1449+ !diags
1450+ .iter()
1451+ .any(|d| d.message.contains("invalid hex color")),
13971452 "should not warn on valid hex color, got: {:?}",
13981453 diags
13991454 );
@@ -1403,7 +1458,9 @@ fn no_warning_valid_hex_color() {
14031458fn warning_duplicate_attribute() {
14041459 let diags = parse_diagnostics("@el [padding 10, padding 20]");
14051460 assert!(
1406− diags.iter().any(|d| d.message.contains("duplicate attribute")),
1461+ diags
1462+ .iter()
1463+ .any(|d| d.message.contains("duplicate attribute")),
14071464 "expected duplicate attribute warning, got: {:?}",
14081465 diags
14091466 );
@@ -1594,7 +1651,9 @@ fn css_border_collapse() {
15941651
15951652#[test]
15961653fn css_text_decoration_full() {
1597− let output = compile("@page T\n@text [text-decoration underline, text-decoration-color red, text-decoration-style wavy] Hello");
1654+ let output = compile(
1655+ "@page T\n@text [text-decoration underline, text-decoration-color red, text-decoration-style wavy] Hello",
1656+ );
15981657 assert!(output.contains("text-decoration:underline"));
15991658 assert!(output.contains("text-decoration-color:red"));
16001659 assert!(output.contains("text-decoration-style:wavy"));
@@ -1652,9 +1711,13 @@ fn favicon_fallback_href() {
16521711
16531712#[test]
16541713fn no_warning_new_pseudo_prefixes() {
1655− let diags = parse_diagnostics("@el [focus-visible:border 2 blue, disabled:opacity 0.5, checked:background green]");
1714+ let diags = parse_diagnostics(
1715+ "@el [focus-visible:border 2 blue, disabled:opacity 0.5, checked:background green]",
1716+ );
16561717 assert!(
1657− !diags.iter().any(|d| d.message.contains("unknown attribute")),
1718+ !diags
1719+ .iter()
1720+ .any(|d| d.message.contains("unknown attribute")),
16581721 "new pseudo-state prefixes should be recognized, got: {:?}",
16591722 diags
16601723 );
@@ -1662,9 +1725,13 @@ fn no_warning_new_pseudo_prefixes() {
16621725
16631726#[test]
16641727fn no_warning_child_selectors() {
1665− let diags = parse_diagnostics("@el [first:padding 0, last:padding 0, odd:background #eee, even:background white]");
1728+ let diags = parse_diagnostics(
1729+ "@el [first:padding 0, last:padding 0, odd:background #eee, even:background white]",
1730+ );
16661731 assert!(
1667− !diags.iter().any(|d| d.message.contains("unknown attribute")),
1732+ !diags
1733+ .iter()
1734+ .any(|d| d.message.contains("unknown attribute")),
16681735 "child selectors should be recognized, got: {:?}",
16691736 diags
16701737 );
@@ -1672,9 +1739,13 @@ fn no_warning_child_selectors() {
16721739
16731740#[test]
16741741fn no_warning_new_css_attrs() {
1675− let diags = parse_diagnostics("@el [overflow-x hidden, overflow-y auto, inset 0, accent-color blue, hidden]");
1742+ let diags = parse_diagnostics(
1743+ "@el [overflow-x hidden, overflow-y auto, inset 0, accent-color blue, hidden]",
1744+ );
16761745 assert!(
1677− !diags.iter().any(|d| d.message.contains("unknown attribute")),
1746+ !diags
1747+ .iter()
1748+ .any(|d| d.message.contains("unknown attribute")),
16781749 "new CSS attrs should be recognized, got: {:?}",
16791750 diags
16801751 );
@@ -1766,7 +1837,9 @@ fn element_fieldset_legend() {
17661837
17671838#[test]
17681839fn element_picture_source() {
1769− let output = compile("@page T\n@picture\n @source [srcset wide.jpg, media (min-width: 800px)]\n @image [alt Photo] photo.jpg");
1840+ let output = compile(
1841+ "@page T\n@picture\n @source [srcset wide.jpg, media (min-width: 800px)]\n @image [alt Photo] photo.jpg",
1842+ );
17701843 assert!(output.contains("<picture"));
17711844 assert!(output.contains("<source"));
17721845 assert!(output.contains("srcset=\"wide.jpg\""));
@@ -1960,7 +2033,9 @@ fn unless_true_hides_content() {
19602033
19612034#[test]
19622035fn og_tags_in_output() {
1963− let output = compile("@page T\n@og title \"My Page\"\n@og image \"https://example.com/img.png\"\n@text Hello");
2036+ let output = compile(
2037+ "@page T\n@og title \"My Page\"\n@og image \"https://example.com/img.png\"\n@text Hello",
2038+ );
19642039 assert!(output.contains("og:title"));
19652040 assert!(output.contains("My Page"));
19662041 assert!(output.contains("og:image"));
@@ -2030,10 +2105,12 @@ fn no_warning_link_with_text() {
20302105#[test]
20312106fn no_warning_new_css_properties_2() {
20322107 let diags = parse_diagnostics(
2033− "@el [clip-path circle(50%), mix-blend-mode multiply, writing-mode vertical-rl, isolation isolate]"
2108+ "@el [clip-path circle(50%), mix-blend-mode multiply, writing-mode vertical-rl, isolation isolate]",
20342109 );
20352110 assert!(
2036− !diags.iter().any(|d| d.message.contains("unknown attribute")),
2111+ !diags
2112+ .iter()
2113+ .any(|d| d.message.contains("unknown attribute")),
20372114 "new CSS properties should be recognized, got: {:?}",
20382115 diags
20392116 );
@@ -2042,10 +2119,12 @@ fn no_warning_new_css_properties_2() {
20422119#[test]
20432120fn no_warning_new_css_properties_3() {
20442121 let diags = parse_diagnostics(
2045− "@el [column-count 3, column-gap 20, text-indent 2em, hyphens auto, flex-grow 1, flex-shrink 0, flex-basis 200, place-content center, background-image url(x)]"
2122+ "@el [column-count 3, column-gap 20, text-indent 2em, hyphens auto, flex-grow 1, flex-shrink 0, flex-basis 200, place-content center, background-image url(x)]",
20462123 );
20472124 assert!(
2048− !diags.iter().any(|d| d.message.contains("unknown attribute")),
2125+ !diags
2126+ .iter()
2127+ .any(|d| d.message.contains("unknown attribute")),
20492128 "new CSS properties should be recognized, got: {:?}",
20502129 diags
20512130 );
@@ -2054,10 +2133,12 @@ fn no_warning_new_css_properties_3() {
20542133#[test]
20552134fn no_warning_new_media_prefixes() {
20562135 let diags = parse_diagnostics(
2057− "@el [2xl:padding 40, motion-safe:animation none, motion-reduce:transition none, landscape:width 100%, portrait:padding 20]"
2136+ "@el [2xl:padding 40, motion-safe:animation none, motion-reduce:transition none, landscape:width 100%, portrait:padding 20]",
20582137 );
20592138 assert!(
2060− !diags.iter().any(|d| d.message.contains("unknown attribute")),
2139+ !diags
2140+ .iter()
2141+ .any(|d| d.message.contains("unknown attribute")),
20612142 "new media prefixes should be recognized, got: {:?}",
20622143 diags
20632144 );
@@ -2075,27 +2156,43 @@ fn snapshot_if_expr() {
20752156#[test]
20762157fn if_expr_true_branch() {
20772158 let output = compile("@let x true\n@el [background if($x, blue, gray)]\n test");
2078− assert!(output.contains("blue"), "should use true branch, got: {}", output);
2159+ assert!(
2160+ output.contains("blue"),
2161+ "should use true branch, got: {}",
2162+ output
2163+ );
20792164 assert!(!output.contains("gray"), "should not contain false branch");
20802165}
20812166
20822167#[test]
20832168fn if_expr_false_branch() {
20842169 let output = compile("@let x false\n@el [background if($x, blue, gray)]\n test");
2085− assert!(output.contains("gray"), "should use false branch, got: {}", output);
2170+ assert!(
2171+ output.contains("gray"),
2172+ "should use false branch, got: {}",
2173+ output
2174+ );
20862175 assert!(!output.contains("blue"), "should not contain true branch");
20872176}
20882177
20892178#[test]
20902179fn if_expr_equality_condition() {
20912180 let output = compile("@let theme dark\n@el [color if($theme == dark, white, black)]\n test");
2092− assert!(output.contains("white"), "should match equality, got: {}", output);
2181+ assert!(
2182+ output.contains("white"),
2183+ "should match equality, got: {}",
2184+ output
2185+ );
20932186}
20942187
20952188#[test]
20962189fn if_expr_inequality_condition() {
20972190 let output = compile("@let mode light\n@el [color if($mode != dark, green, red)]\n test");
2098− assert!(output.contains("green"), "should match inequality, got: {}", output);
2191+ assert!(
2192+ output.contains("green"),
2193+ "should match inequality, got: {}",
2194+ output
2195+ );
20992196}
21002197
21012198#[test]
@@ -2119,7 +2216,11 @@ fn convert_basic_div() {
21192216#[test]
21202217fn convert_paragraph() {
21212218 let hl = htmlang::convert::convert("<p>Some text</p>");
2122− assert!(hl.contains("@paragraph"), "p should become @paragraph: {}", hl);
2219+ assert!(
2220+ hl.contains("@paragraph"),
2221+ "p should become @paragraph: {}",
2222+ hl
2223+ );
21232224}
21242225
21252226#[test]
@@ -2162,7 +2263,11 @@ fn convert_list() {
21622263#[test]
21632264fn convert_ordered_list() {
21642265 let hl = htmlang::convert::convert("<ol><li>First</li></ol>");
2165− assert!(hl.contains("@list [ordered]"), "ol becomes @list [ordered]: {}", hl);
2266+ assert!(
2267+ hl.contains("@list [ordered]"),
2268+ "ol becomes @list [ordered]: {}",
2269+ hl
2270+ );
21662271}
21672272
21682273#[test]
@@ -2183,8 +2288,14 @@ fn convert_table() {
21832288
21842289#[test]
21852290fn convert_strips_html_boilerplate() {
2186− let hl = htmlang::convert::convert("<!DOCTYPE html><html><head><title>T</title></head><body><p>Hi</p></body></html>");
2187− assert!(hl.contains("@paragraph"), "should extract body content: {}", hl);
2291+ let hl = htmlang::convert::convert(
2292+ "<!DOCTYPE html><html><head><title>T</title></head><body><p>Hi</p></body></html>",
2293+ );
2294+ assert!(
2295+ hl.contains("@paragraph"),
2296+ "should extract body content: {}",
2297+ hl
2298+ );
21882299 assert!(!hl.contains("DOCTYPE"), "should strip doctype: {}", hl);
21892300}
21902301
@@ -2192,7 +2303,11 @@ fn convert_strips_html_boilerplate() {
21922303fn convert_inline_style() {
21932304 let hl = htmlang::convert::convert("<div style=\"padding: 20px; background: red;\">X</div>");
21942305 assert!(hl.contains("padding 20"), "padding converted: {}", hl);
2195− assert!(hl.contains("background red"), "background converted: {}", hl);
2306+ assert!(
2307+ hl.contains("background red"),
2308+ "background converted: {}",
2309+ hl
2310+ );
21962311}
21972312
21982313#[test]
@@ -2228,8 +2343,13 @@ fn snapshot_each_else() {
22282343#[test]
22292344fn each_else_empty_list() {
22302345 // Variable that resolves to empty string: @each produces no items → @else fires
2231− let output = compile("@let empty \"\"\n@each $item in $empty\n @text $item\n@else\n @text fallback");
2232− assert!(output.contains("fallback"), "should render @else block when list is empty: {}", output);
2346+ let output =
2347+ compile("@let empty \"\"\n@each $item in $empty\n @text $item\n@else\n @text fallback");
2348+ assert!(
2349+ output.contains("fallback"),
2350+ "should render @else block when list is empty: {}",
2351+ output
2352+ );
22332353}
22342354
22352355#[test]
@@ -2237,72 +2357,136 @@ fn each_else_non_empty_list() {
22372357 let output = compile("@each $x in a,b\n @text $x\n@else\n @text empty");
22382358 assert!(output.contains("a"), "should render loop items: {}", output);
22392359 assert!(output.contains("b"), "should render loop items: {}", output);
2240− assert!(!output.contains("empty"), "should not render @else block when list is non-empty: {}", output);
2360+ assert!(
2361+ !output.contains("empty"),
2362+ "should not render @else block when list is non-empty: {}",
2363+ output
2364+ );
22412365}
22422366
22432367#[test]
22442368fn pseudo_element_before_content() {
22452369 let output = compile("@el [before:content arrow, before:color red]\n Hello");
2246− assert!(output.contains("::before"), "should generate ::before CSS: {}", output);
2247− assert!(output.contains("content:\"arrow\""), "should generate content property: {}", output);
2248− assert!(output.contains("color:red"), "should generate color in ::before: {}", output);
2370+ assert!(
2371+ output.contains("::before"),
2372+ "should generate ::before CSS: {}",
2373+ output
2374+ );
2375+ assert!(
2376+ output.contains("content:\"arrow\""),
2377+ "should generate content property: {}",
2378+ output
2379+ );
2380+ assert!(
2381+ output.contains("color:red"),
2382+ "should generate color in ::before: {}",
2383+ output
2384+ );
22492385}
22502386
22512387#[test]
22522388fn pseudo_element_after_content() {
22532389 let output = compile("@el [after:content ✓]\n Done");
2254− assert!(output.contains("::after"), "should generate ::after CSS: {}", output);
2255− assert!(output.contains("content:\"✓\""), "should generate content: {}", output);
2390+ assert!(
2391+ output.contains("::after"),
2392+ "should generate ::after CSS: {}",
2393+ output
2394+ );
2395+ assert!(
2396+ output.contains("content:\"✓\""),
2397+ "should generate content: {}",
2398+ output
2399+ );
22562400}
22572401
22582402#[test]
22592403fn css_font_weight_numeric() {
22602404 let output = compile("@text [font-weight 300] Light text");
2261− assert!(output.contains("font-weight:300"), "should generate font-weight CSS: {}", output);
2405+ assert!(
2406+ output.contains("font-weight:300"),
2407+ "should generate font-weight CSS: {}",
2408+ output
2409+ );
22622410}
22632411
22642412#[test]
22652413fn css_text_wrap_balance() {
22662414 let output = compile("@text [text-wrap balance] Balanced");
2267− assert!(output.contains("text-wrap:balance"), "should generate text-wrap CSS: {}", output);
2415+ assert!(
2416+ output.contains("text-wrap:balance"),
2417+ "should generate text-wrap CSS: {}",
2418+ output
2419+ );
22682420}
22692421
22702422#[test]
22712423fn css_touch_action() {
22722424 let output = compile("@el [touch-action none]\n No touch");
2273− assert!(output.contains("touch-action:none"), "should generate touch-action CSS: {}", output);
2425+ assert!(
2426+ output.contains("touch-action:none"),
2427+ "should generate touch-action CSS: {}",
2428+ output
2429+ );
22742430}
22752431
22762432#[test]
22772433fn css_content_visibility() {
22782434 let output = compile("@el [content-visibility auto]\n Lazy");
2279− assert!(output.contains("content-visibility:auto"), "should generate content-visibility CSS: {}", output);
2435+ assert!(
2436+ output.contains("content-visibility:auto"),
2437+ "should generate content-visibility CSS: {}",
2438+ output
2439+ );
22802440}
22812441
22822442#[test]
22832443fn css_scroll_margin() {
22842444 let output = compile("@el [scroll-margin-top 80]\n Offset");
2285− assert!(output.contains("scroll-margin-top:80px"), "should generate scroll-margin-top CSS: {}", output);
2445+ assert!(
2446+ output.contains("scroll-margin-top:80px"),
2447+ "should generate scroll-margin-top CSS: {}",
2448+ output
2449+ );
22862450}
22872451
22882452#[test]
22892453fn element_iframe() {
22902454 let output = compile("@iframe [width fill, height 400] https://example.com");
2291− assert!(output.contains("<iframe"), "should generate iframe tag: {}", output);
2292− assert!(output.contains("src=\"https://example.com\""), "should have src: {}", output);
2455+ assert!(
2456+ output.contains("<iframe"),
2457+ "should generate iframe tag: {}",
2458+ output
2459+ );
2460+ assert!(
2461+ output.contains("src=\"https://example.com\""),
2462+ "should have src: {}",
2463+ output
2464+ );
22932465}
22942466
22952467#[test]
22962468fn element_canvas() {
22972469 let output = compile("@canvas [width 400, height 300, id myCanvas]");
2298− assert!(output.contains("<canvas"), "should generate canvas tag: {}", output);
2299− assert!(output.contains("id=\"myCanvas\""), "should have id: {}", output);
2470+ assert!(
2471+ output.contains("<canvas"),
2472+ "should generate canvas tag: {}",
2473+ output
2474+ );
2475+ assert!(
2476+ output.contains("id=\"myCanvas\""),
2477+ "should have id: {}",
2478+ output
2479+ );
23002480}
23012481
23022482#[test]
23032483fn element_output() {
23042484 let output = compile("@output [for a b]\n 42");
2305− assert!(output.contains("<output"), "should generate output tag: {}", output);
2485+ assert!(
2486+ output.contains("<output"),
2487+ "should generate output tag: {}",
2488+ output
2489+ );
23062490}
23072491
23082492// =========================================================================
@@ -2339,8 +2523,16 @@ fn snapshot_direction_attr() {
23392523#[test]
23402524fn element_grid() {
23412525 let output = compile("@page T\n@grid [grid-cols 3, gap 16]\n @el\n @text A");
2342− assert!(output.contains("display:grid"), "grid should have display:grid: {}", output);
2343− assert!(output.contains("grid-template-columns:repeat(3,1fr)"), "should have 3 cols: {}", output);
2526+ assert!(
2527+ output.contains("display:grid"),
2528+ "grid should have display:grid: {}",
2529+ output
2530+ );
2531+ assert!(
2532+ output.contains("grid-template-columns:repeat(3,1fr)"),
2533+ "should have 3 cols: {}",
2534+ output
2535+ );
23442536}
23452537
23462538// --- Stack element ---
@@ -2348,7 +2540,11 @@ fn element_grid() {
23482540#[test]
23492541fn element_stack() {
23502542 let output = compile("@page T\n@stack [width 200, height 200]\n @el\n @text Layer");
2351− assert!(output.contains("position:relative"), "stack should have position:relative: {}", output);
2543+ assert!(
2544+ output.contains("position:relative"),
2545+ "stack should have position:relative: {}",
2546+ output
2547+ );
23522548}
23532549
23542550// --- Spacer element ---
@@ -2356,7 +2552,11 @@ fn element_stack() {
23562552#[test]
23572553fn element_spacer() {
23582554 let output = compile("@page T\n@row\n @text Left\n @spacer\n @text Right");
2359− assert!(output.contains("flex:1"), "spacer should have flex:1: {}", output);
2555+ assert!(
2556+ output.contains("flex:1"),
2557+ "spacer should have flex:1: {}",
2558+ output
2559+ );
23602560}
23612561
23622562// --- Badge element ---
@@ -2364,8 +2564,16 @@ fn element_spacer() {
23642564#[test]
23652565fn element_badge() {
23662566 let output = compile("@page T\n@badge [background red, color white] 3");
2367− assert!(output.contains("<span"), "badge renders as span: {}", output);
2368− assert!(output.contains("border-radius:9999px"), "badge should be pill-shaped: {}", output);
2567+ assert!(
2568+ output.contains("<span"),
2569+ "badge renders as span: {}",
2570+ output
2571+ );
2572+ assert!(
2573+ output.contains("border-radius:9999px"),
2574+ "badge should be pill-shaped: {}",
2575+ output
2576+ );
23692577 assert!(output.contains("3"), "badge content: {}", output);
23702578}
23712579
@@ -2374,8 +2582,16 @@ fn element_badge() {
23742582#[test]
23752583fn element_tooltip() {
23762584 let output = compile("@page T\n@tooltip Hover for info\n @text Help");
2377− assert!(output.contains("title=\"Hover for info\""), "tooltip should have title attr: {}", output);
2378− assert!(output.contains("cursor:help"), "tooltip should have cursor:help: {}", output);
2585+ assert!(
2586+ output.contains("title=\"Hover for info\""),
2587+ "tooltip should have title attr: {}",
2588+ output
2589+ );
2590+ assert!(
2591+ output.contains("cursor:help"),
2592+ "tooltip should have cursor:help: {}",
2593+ output
2594+ );
23792595}
23802596
23812597// --- @each step ---
@@ -2404,9 +2620,18 @@ fn each_step_reverse() {
24042620
24052621#[test]
24062622fn selection_pseudo_generates_css() {
2407− let output = compile("@page T\n@text [selection:background blue, selection:color white] Select me");
2408− assert!(output.contains("::selection"), "should generate ::selection: {}", output);
2409− assert!(output.contains("background:blue"), "should have bg: {}", output);
2623+ let output =
2624+ compile("@page T\n@text [selection:background blue, selection:color white] Select me");
2625+ assert!(
2626+ output.contains("::selection"),
2627+ "should generate ::selection: {}",
2628+ output
2629+ );
2630+ assert!(
2631+ output.contains("background:blue"),
2632+ "should have bg: {}",
2633+ output
2634+ );
24102635}
24112636
24122637// --- nth: pseudo ---
@@ -2414,14 +2639,26 @@ fn selection_pseudo_generates_css() {
24142639#[test]
24152640fn nth_pseudo_generates_css() {
24162641 let output = compile("@page T\n@el [nth:3:background red]\n @text test");
2417− assert!(output.contains(":nth-child(3)"), "should generate :nth-child(3): {}", output);
2418− assert!(output.contains("background:red"), "should have bg: {}", output);
2642+ assert!(
2643+ output.contains(":nth-child(3)"),
2644+ "should generate :nth-child(3): {}",
2645+ output
2646+ );
2647+ assert!(
2648+ output.contains("background:red"),
2649+ "should have bg: {}",
2650+ output
2651+ );
24192652}
24202653
24212654#[test]
24222655fn nth_pseudo_formula() {
24232656 let output = compile("@page T\n@el [nth:2n:background #eee]\n @text test");
2424− assert!(output.contains(":nth-child(2n)"), "should generate :nth-child(2n): {}", output);
2657+ assert!(
2658+ output.contains(":nth-child(2n)"),
2659+ "should generate :nth-child(2n): {}",
2660+ output
2661+ );
24252662}
24262663
24272664// --- container query prefix ---
@@ -2429,8 +2666,16 @@ fn nth_pseudo_formula() {
24292666#[test]
24302667fn container_query_generates_css() {
24312668 let output = compile("@page T\n@el [container]\n @el [cq-sm:padding 20]\n @text test");
2432− assert!(output.contains("@container(min-width:640px)"), "should generate container query: {}", output);
2433− assert!(output.contains("padding:20px"), "should have padding: {}", output);
2669+ assert!(
2670+ output.contains("@container(min-width:640px)"),
2671+ "should generate container query: {}",
2672+ output
2673+ );
2674+ assert!(
2675+ output.contains("padding:20px"),
2676+ "should have padding: {}",
2677+ output
2678+ );
24342679}
24352680
24362681// --- direction attribute ---
@@ -2438,7 +2683,11 @@ fn container_query_generates_css() {
24382683#[test]
24392684fn direction_rtl() {
24402685 let output = compile("@page T\n@el [direction rtl]\n @text RTL text");
2441− assert!(output.contains("direction:rtl"), "should generate direction:rtl: {}", output);
2686+ assert!(
2687+ output.contains("direction:rtl"),
2688+ "should generate direction:rtl: {}",
2689+ output
2690+ );
24422691}
24432692
24442693// --- contrast checker ---
@@ -2447,7 +2696,9 @@ fn direction_rtl() {
24472696fn warning_low_contrast() {
24482697 let diags = parse_diagnostics("@el [background #ffffff, color #cccccc]\n @text test");
24492698 assert!(
2450− diags.iter().any(|d| d.message.contains("low contrast ratio")),
2699+ diags
2700+ .iter()
2701+ .any(|d| d.message.contains("low contrast ratio")),
24512702 "should warn about low contrast, got: {:?}",
24522703 diags
24532704 );
@@ -2457,7 +2708,9 @@ fn warning_low_contrast() {
24572708fn no_warning_good_contrast() {
24582709 let diags = parse_diagnostics("@el [background #ffffff, color #000000]\n @text test");
24592710 assert!(
2460− !diags.iter().any(|d| d.message.contains("low contrast ratio")),
2711+ !diags
2712+ .iter()
2713+ .any(|d| d.message.contains("low contrast ratio")),
24612714 "should not warn about good contrast, got: {:?}",
24622715 diags
24632716 );
@@ -2469,7 +2722,9 @@ fn no_warning_good_contrast() {
24692722fn no_warning_selection_prefix() {
24702723 let diags = parse_diagnostics("@el [selection:background blue]");
24712724 assert!(
2472− !diags.iter().any(|d| d.message.contains("unknown attribute")),
2725+ !diags
2726+ .iter()
2727+ .any(|d| d.message.contains("unknown attribute")),
24732728 "selection: prefix should be recognized, got: {:?}",
24742729 diags
24752730 );
@@ -2479,7 +2734,9 @@ fn no_warning_selection_prefix() {
24792734fn no_warning_nth_prefix() {
24802735 let diags = parse_diagnostics("@el [nth:3:background red]");
24812736 assert!(
2482− !diags.iter().any(|d| d.message.contains("unknown attribute")),
2737+ !diags
2738+ .iter()
2739+ .any(|d| d.message.contains("unknown attribute")),
24832740 "nth: prefix should be recognized, got: {:?}",
24842741 diags
24852742 );
@@ -2489,7 +2746,9 @@ fn no_warning_nth_prefix() {
24892746fn no_warning_cq_prefix() {
24902747 let diags = parse_diagnostics("@el [cq-sm:padding 20]");
24912748 assert!(
2492− !diags.iter().any(|d| d.message.contains("unknown attribute")),
2749+ !diags
2750+ .iter()
2751+ .any(|d| d.message.contains("unknown attribute")),
24932752 "cq- prefix should be recognized, got: {:?}",
24942753 diags
24952754 );
@@ -2499,7 +2758,9 @@ fn no_warning_cq_prefix() {
24992758fn no_warning_direction_attr() {
25002759 let diags = parse_diagnostics("@el [direction rtl]");
25012760 assert!(
2502− !diags.iter().any(|d| d.message.contains("unknown attribute")),
2761+ !diags
2762+ .iter()
2763+ .any(|d| d.message.contains("unknown attribute")),
25032764 "direction should be recognized, got: {:?}",
25042765 diags
25052766 );
@@ -2510,31 +2771,41 @@ fn no_warning_new_elements() {
25102771 // Grid, stack, spacer, badge, tooltip should all parse without errors
25112772 let diags = parse_diagnostics("@grid\n @text A");
25122773 assert!(
2513− !diags.iter().any(|d| d.severity == htmlang::parser::Severity::Error),
2774+ !diags
2775+ .iter()
2776+ .any(|d| d.severity == htmlang::parser::Severity::Error),
25142777 "grid should parse, got: {:?}",
25152778 diags
25162779 );
25172780 let diags = parse_diagnostics("@stack\n @text A");
25182781 assert!(
2519− !diags.iter().any(|d| d.severity == htmlang::parser::Severity::Error),
2782+ !diags
2783+ .iter()
2784+ .any(|d| d.severity == htmlang::parser::Severity::Error),
25202785 "stack should parse, got: {:?}",
25212786 diags
25222787 );
25232788 let diags = parse_diagnostics("@row\n @spacer");
25242789 assert!(
2525− !diags.iter().any(|d| d.severity == htmlang::parser::Severity::Error),
2790+ !diags
2791+ .iter()
2792+ .any(|d| d.severity == htmlang::parser::Severity::Error),
25262793 "spacer should parse, got: {:?}",
25272794 diags
25282795 );
25292796 let diags = parse_diagnostics("@badge [background red] 5");
25302797 assert!(
2531− !diags.iter().any(|d| d.severity == htmlang::parser::Severity::Error),
2798+ !diags
2799+ .iter()
2800+ .any(|d| d.severity == htmlang::parser::Severity::Error),
25322801 "badge should parse, got: {:?}",
25332802 diags
25342803 );
25352804 let diags = parse_diagnostics("@tooltip Help text\n @text Hover");
25362805 assert!(
2537− !diags.iter().any(|d| d.severity == htmlang::parser::Severity::Error),
2806+ !diags
2807+ .iter()
2808+ .any(|d| d.severity == htmlang::parser::Severity::Error),
25382809 "tooltip should parse, got: {:?}",
25392810 diags
25402811 );
@@ -2560,92 +2831,188 @@ fn snapshot_css_shorthands() {
25602831#[test]
25612832fn test_variable_filters() {
25622833 let result = htmlang::parser::parse("@let name hello\n@text $name|uppercase");
2563− assert!(result.diagnostics.iter().all(|d| d.severity != htmlang::parser::Severity::Error));
2834+ assert!(
2835+ result
2836+ .diagnostics
2837+ .iter()
2838+ .all(|d| d.severity != htmlang::parser::Severity::Error)
2839+ );
25642840 let html = htmlang::codegen::generate(&result.document);
2565− assert!(html.contains("HELLO"), "uppercase filter should work, got: {}", html);
2841+ assert!(
2842+ html.contains("HELLO"),
2843+ "uppercase filter should work, got: {}",
2844+ html
2845+ );
25662846
25672847 let result = htmlang::parser::parse("@let name HELLO\n@text $name|lowercase");
25682848 let html = htmlang::codegen::generate(&result.document);
2569− assert!(html.contains("hello"), "lowercase filter should work, got: {}", html);
2849+ assert!(
2850+ html.contains("hello"),
2851+ "lowercase filter should work, got: {}",
2852+ html
2853+ );
25702854
25712855 let result = htmlang::parser::parse("@let name hello\n@text $name|capitalize");
25722856 let html = htmlang::codegen::generate(&result.document);
2573− assert!(html.contains("Hello"), "capitalize filter should work, got: {}", html);
2857+ assert!(
2858+ html.contains("Hello"),
2859+ "capitalize filter should work, got: {}",
2860+ html
2861+ );
25742862
25752863 let result = htmlang::parser::parse("@let name hello\n@text $name|length");
25762864 let html = htmlang::codegen::generate(&result.document);
2577− assert!(html.contains("5"), "length filter should work, got: {}", html);
2865+ assert!(
2866+ html.contains("5"),
2867+ "length filter should work, got: {}",
2868+ html
2869+ );
25782870
25792871 let result = htmlang::parser::parse("@let name hello\n@text $name|reverse");
25802872 let html = htmlang::codegen::generate(&result.document);
2581− assert!(html.contains("olleh"), "reverse filter should work, got: {}", html);
2873+ assert!(
2874+ html.contains("olleh"),
2875+ "reverse filter should work, got: {}",
2876+ html
2877+ );
25822878
25832879 let result = htmlang::parser::parse("@let name hello world\n@text $name|truncate:5");
25842880 let html = htmlang::codegen::generate(&result.document);
2585− assert!(html.contains("hello..."), "truncate filter should work, got: {}", html);
2881+ assert!(
2882+ html.contains("hello..."),
2883+ "truncate filter should work, got: {}",
2884+ html
2885+ );
25862886}
25872887
25882888#[test]
25892889fn test_new_elements_parse() {
25902890 // Avatar
25912891 let diags = parse_diagnostics("@avatar [width 48, height 48]\n @text AB");
2592− assert!(!diags.iter().any(|d| d.severity == htmlang::parser::Severity::Error), "avatar: {:?}", diags);
2892+ assert!(
2893+ !diags
2894+ .iter()
2895+ .any(|d| d.severity == htmlang::parser::Severity::Error),
2896+ "avatar: {:?}",
2897+ diags
2898+ );
25932899
25942900 // Carousel
25952901 let diags = parse_diagnostics("@carousel [gap 16]\n @el Slide 1");
2596− assert!(!diags.iter().any(|d| d.severity == htmlang::parser::Severity::Error), "carousel: {:?}", diags);
2902+ assert!(
2903+ !diags
2904+ .iter()
2905+ .any(|d| d.severity == htmlang::parser::Severity::Error),
2906+ "carousel: {:?}",
2907+ diags
2908+ );
25972909
25982910 // Chip
25992911 let diags = parse_diagnostics("@chip [background #eee] Tag");
2600− assert!(!diags.iter().any(|d| d.severity == htmlang::parser::Severity::Error), "chip: {:?}", diags);
2912+ assert!(
2913+ !diags
2914+ .iter()
2915+ .any(|d| d.severity == htmlang::parser::Severity::Error),
2916+ "chip: {:?}",
2917+ diags
2918+ );
26012919
26022920 // Tag
26032921 let diags = parse_diagnostics("@tag [color blue] v1.0");
2604− assert!(!diags.iter().any(|d| d.severity == htmlang::parser::Severity::Error), "tag: {:?}", diags);
2922+ assert!(
2923+ !diags
2924+ .iter()
2925+ .any(|d| d.severity == htmlang::parser::Severity::Error),
2926+ "tag: {:?}",
2927+ diags
2928+ );
26052929}
26062930
26072931#[test]
26082932fn test_css_shorthands_output() {
26092933 let result = htmlang::parser::parse("@text [truncate] Hello");
26102934 let html = htmlang::codegen::generate(&result.document);
2611− assert!(html.contains("text-overflow:ellipsis"), "truncate should add ellipsis, got: {}", html);
2612− assert!(html.contains("white-space:nowrap"), "truncate should add nowrap");
2935+ assert!(
2936+ html.contains("text-overflow:ellipsis"),
2937+ "truncate should add ellipsis, got: {}",
2938+ html
2939+ );
2940+ assert!(
2941+ html.contains("white-space:nowrap"),
2942+ "truncate should add nowrap"
2943+ );
26132944
26142945 let result = htmlang::parser::parse("@paragraph [line-clamp 3] Text");
26152946 let html = htmlang::codegen::generate(&result.document);
2616− assert!(html.contains("-webkit-line-clamp:3"), "line-clamp should work, got: {}", html);
2947+ assert!(
2948+ html.contains("-webkit-line-clamp:3"),
2949+ "line-clamp should work, got: {}",
2950+ html
2951+ );
26172952
26182953 let result = htmlang::parser::parse("@el [blur 4] Content");
26192954 let html = htmlang::codegen::generate(&result.document);
2620− assert!(html.contains("filter:blur(4px)"), "blur should work, got: {}", html);
2955+ assert!(
2956+ html.contains("filter:blur(4px)"),
2957+ "blur should work, got: {}",
2958+ html
2959+ );
26212960
26222961 let result = htmlang::parser::parse("@el [backdrop-blur 10] Content");
26232962 let html = htmlang::codegen::generate(&result.document);
2624− assert!(html.contains("backdrop-filter:blur(10px)"), "backdrop-blur should work, got: {}", html);
2963+ assert!(
2964+ html.contains("backdrop-filter:blur(10px)"),
2965+ "backdrop-blur should work, got: {}",
2966+ html
2967+ );
26252968
26262969 let result = htmlang::parser::parse("@el [no-scrollbar] Content");
26272970 let html = htmlang::codegen::generate(&result.document);
2628− assert!(html.contains("scrollbar-width:none"), "no-scrollbar should work, got: {}", html);
2971+ assert!(
2972+ html.contains("scrollbar-width:none"),
2973+ "no-scrollbar should work, got: {}",
2974+ html
2975+ );
26292976
26302977 let result = htmlang::parser::parse("@el [skeleton, width 100, height 20] Content");
26312978 let html = htmlang::codegen::generate(&result.document);
2632− assert!(html.contains("hl-skeleton"), "skeleton should add animation, got: {}", html);
2633− assert!(html.contains("@keyframes hl-skeleton"), "skeleton should add keyframes, got: {}", html);
2979+ assert!(
2980+ html.contains("hl-skeleton"),
2981+ "skeleton should add animation, got: {}",
2982+ html
2983+ );
2984+ assert!(
2985+ html.contains("@keyframes hl-skeleton"),
2986+ "skeleton should add keyframes, got: {}",
2987+ html
2988+ );
26342989
26352990 let result = htmlang::parser::parse("@el [gradient #fff #000] Content");
26362991 let html = htmlang::codegen::generate(&result.document);
2637− assert!(html.contains("linear-gradient(#fff,#000)"), "gradient should work, got: {}", html);
2992+ assert!(
2993+ html.contains("linear-gradient(#fff,#000)"),
2994+ "gradient should work, got: {}",
2995+ html
2996+ );
26382997
26392998 let result = htmlang::parser::parse("@el [gradient #fff #000 45deg] Content");
26402999 let html = htmlang::codegen::generate(&result.document);
2641− assert!(html.contains("linear-gradient(45deg,#fff,#000)"), "gradient with angle should work, got: {}", html);
3000+ assert!(
3001+ html.contains("linear-gradient(45deg,#fff,#000)"),
3002+ "gradient with angle should work, got: {}",
3003+ html
3004+ );
26423005}
26433006
26443007#[test]
26453008fn test_carousel_children_snap() {
26463009 let result = htmlang::parser::parse("@carousel\n @el A\n @el B");
26473010 let html = htmlang::codegen::generate(&result.document);
2648− assert!(html.contains("scroll-snap-align:start"), "carousel children should have snap-align, got: {}", html);
3011+ assert!(
3012+ html.contains("scroll-snap-align:start"),
3013+ "carousel children should have snap-align, got: {}",
3014+ html
3015+ );
26493016}
26503017
26513018#[test]
@@ -2653,110 +3020,206 @@ fn test_use_directive() {
26533020 // We can't test @use with actual files in unit tests easily, but we can verify
26543021 // the parser recognizes the directive without errors when it can't find the file
26553022 let result = htmlang::parser::parse("@use nonexistent.hl card");
2656− let has_use_error = result.diagnostics.iter().any(|d|
3023+ let has_use_error = result.diagnostics.iter().any(|d| {
26573024 d.message.contains("cannot use") && d.severity == htmlang::parser::Severity::Error
3025+ });
3026+ assert!(
3027+ has_use_error,
3028+ "@use should report error for missing file, got: {:?}",
3029+ result.diagnostics
26583030 );
2659− assert!(has_use_error, "@use should report error for missing file, got: {:?}", result.diagnostics);
26603031}
26613032
26623033#[test]
26633034fn test_enhanced_keyframes() {
2664− let result = htmlang::parser::parse("@keyframes fade-in\n from [opacity 0]\n to [opacity 1]\n@el [animation fade-in 0.3s] Content");
3035+ let result = htmlang::parser::parse(
3036+ "@keyframes fade-in\n from [opacity 0]\n to [opacity 1]\n@el [animation fade-in 0.3s] Content",
3037+ );
26653038 let html = htmlang::codegen::generate(&result.document);
2666− assert!(html.contains("@keyframes fade-in{from{opacity:0;}to{opacity:1;}}"), "keyframes should parse htmlang syntax, got: {}", html);
3039+ assert!(
3040+ html.contains("@keyframes fade-in{from{opacity:0;}to{opacity:1;}}"),
3041+ "keyframes should parse htmlang syntax, got: {}",
3042+ html
3043+ );
26673044}
26683045
26693046#[test]
26703047fn test_keyframes_percentage() {
2671− let result = htmlang::parser::parse("@keyframes slide\n 0% [transform translateX(-100%)]\n 100% [transform translateX(0)]");
3048+ let result = htmlang::parser::parse(
3049+ "@keyframes slide\n 0% [transform translateX(-100%)]\n 100% [transform translateX(0)]",
3050+ );
26723051 let html = htmlang::codegen::generate(&result.document);
2673− assert!(html.contains("0%{transform:translateX(-100%);}"), "keyframe percentage should work, got: {}", html);
3052+ assert!(
3053+ html.contains("0%{transform:translateX(-100%);}"),
3054+ "keyframe percentage should work, got: {}",
3055+ html
3056+ );
26743057}
26753058
26763059#[test]
26773060fn test_theme_directive() {
2678− let result = htmlang::parser::parse("@theme\n primary #3b82f6\n spacing-md 16\n\n@el [background $primary, padding $spacing-md] Content");
3061+ let result = htmlang::parser::parse(
3062+ "@theme\n primary #3b82f6\n spacing-md 16\n\n@el [background $primary, padding $spacing-md] Content",
3063+ );
26793064 let diags = &result.diagnostics;
2680− let errors: Vec<_> = diags.iter().filter(|d| d.severity == htmlang::parser::Severity::Error).collect();
2681− assert!(errors.is_empty(), "theme should not cause errors: {:?}", errors);
3065+ let errors: Vec<_> = diags
3066+ .iter()
3067+ .filter(|d| d.severity == htmlang::parser::Severity::Error)
3068+ .collect();
3069+ assert!(
3070+ errors.is_empty(),
3071+ "theme should not cause errors: {:?}",
3072+ errors
3073+ );
26823074 let html = htmlang::codegen::generate(&result.document);
2683− assert!(html.contains("--primary:#3b82f6"), "theme should emit CSS vars, got: {}", html);
2684− assert!(html.contains("--spacing-md:16"), "theme should emit spacing var, got: {}", html);
2685− assert!(html.contains("background:#3b82f6"), "theme var should resolve in attrs, got: {}", html);
3075+ assert!(
3076+ html.contains("--primary:#3b82f6"),
3077+ "theme should emit CSS vars, got: {}",
3078+ html
3079+ );
3080+ assert!(
3081+ html.contains("--spacing-md:16"),
3082+ "theme should emit spacing var, got: {}",
3083+ html
3084+ );
3085+ assert!(
3086+ html.contains("background:#3b82f6"),
3087+ "theme var should resolve in attrs, got: {}",
3088+ html
3089+ );
26863090}
26873091
26883092#[test]
26893093fn test_deprecated_fn() {
2690− let result = htmlang::parser::parse("@deprecated Use @new-card instead\n@fn old-card $title\n @text $title\n\n@old-card [title Hello]");
2691− let warnings: Vec<_> = result.diagnostics.iter()
3094+ let result = htmlang::parser::parse(
3095+ "@deprecated Use @new-card instead\n@fn old-card $title\n @text $title\n\n@old-card [title Hello]",
3096+ );
3097+ let warnings: Vec<_> = result
3098+ .diagnostics
3099+ .iter()
26923100 .filter(|d| d.message.contains("deprecated"))
26933101 .collect();
2694− assert!(!warnings.is_empty(), "calling deprecated fn should warn, got: {:?}", result.diagnostics);
2695− assert!(warnings[0].message.contains("Use @new-card instead"), "deprecation message should be included");
3102+ assert!(
3103+ !warnings.is_empty(),
3104+ "calling deprecated fn should warn, got: {:?}",
3105+ result.diagnostics
3106+ );
3107+ assert!(
3108+ warnings[0].message.contains("Use @new-card instead"),
3109+ "deprecation message should be included"
3110+ );
26963111}
26973112
26983113#[test]
26993114fn test_extends_directive() {
27003115 // Can't test with actual files, but verify parse error for missing file
27013116 let result = htmlang::parser::parse("@extends nonexistent.hl\n@slot content\n Hello");
2702− let has_error = result.diagnostics.iter().any(|d|
3117+ let has_error = result.diagnostics.iter().any(|d| {
27033118 d.message.contains("cannot extend") && d.severity == htmlang::parser::Severity::Error
3119+ });
3120+ assert!(
3121+ has_error,
3122+ "@extends should report error for missing file, got: {:?}",
3123+ result.diagnostics
27043124 );
2705− assert!(has_error, "@extends should report error for missing file, got: {:?}", result.diagnostics);
27063125}
27073126
27083127#[test]
27093128fn test_color_filter_lighten() {
2710− let result = htmlang::parser::parse("@let primary #3b82f6\n@el [background $primary|lighten:20] Content");
3129+ let result = htmlang::parser::parse(
3130+ "@let primary #3b82f6\n@el [background $primary|lighten:20] Content",
3131+ );
27113132 let html = htmlang::codegen::generate(&result.document);
27123133 // Lighten #3b82f6 by 20% should produce a lighter blue
2713− assert!(html.contains("background:#"), "lighten filter should produce hex color, got: {}", html);
3134+ assert!(
3135+ html.contains("background:#"),
3136+ "lighten filter should produce hex color, got: {}",
3137+ html
3138+ );
27143139 // Verify it's not the original color
2715− assert!(!html.contains("background:#3b82f6"), "lighten should change the color");
3140+ assert!(
3141+ !html.contains("background:#3b82f6"),
3142+ "lighten should change the color"
3143+ );
27163144}
27173145
27183146#[test]
27193147fn test_color_filter_darken() {
2720− let result = htmlang::parser::parse("@let primary #ffffff\n@el [background $primary|darken:50] Content");
3148+ let result =
3149+ htmlang::parser::parse("@let primary #ffffff\n@el [background $primary|darken:50] Content");
27213150 let html = htmlang::codegen::generate(&result.document);
27223151 // Darken white by 50% should produce gray (#808080 approximately)
2723− assert!(html.contains("background:#"), "darken filter should produce hex color, got: {}", html);
2724− assert!(!html.contains("background:#ffffff"), "darken should change the color");
3152+ assert!(
3153+ html.contains("background:#"),
3154+ "darken filter should produce hex color, got: {}",
3155+ html
3156+ );
3157+ assert!(
3158+ !html.contains("background:#ffffff"),
3159+ "darken should change the color"
3160+ );
27253161}
27263162
27273163#[test]
27283164fn test_color_filter_alpha() {
2729− let result = htmlang::parser::parse("@let primary #3b82f6\n@el [background $primary|alpha:0.5] Content");
3165+ let result =
3166+ htmlang::parser::parse("@let primary #3b82f6\n@el [background $primary|alpha:0.5] Content");
27303167 let html = htmlang::codegen::generate(&result.document);
27313168 // Should produce 8-digit hex with alpha
2732− assert!(html.contains("background:#3b82f67f"), "alpha filter should add alpha channel, got: {}", html);
3169+ assert!(
3170+ html.contains("background:#3b82f67f"),
3171+ "alpha filter should add alpha channel, got: {}",
3172+ html
3173+ );
27333174}
27343175
27353176#[test]
27363177fn test_color_filter_mix() {
2737− let result = htmlang::parser::parse("@let primary #000000\n@el [background $primary|mix:#ffffff:50] Content");
3178+ let result = htmlang::parser::parse(
3179+ "@let primary #000000\n@el [background $primary|mix:#ffffff:50] Content",
3180+ );
27383181 let html = htmlang::codegen::generate(&result.document);
27393182 // Mix black and white at 50% should produce gray
2740− assert!(html.contains("background:#808080") || html.contains("background:#7f7f7f"), "mix filter should blend colors, got: {}", html);
2741−}
3183+ assert!(
3184+ html.contains("background:#808080") || html.contains("background:#7f7f7f"),
3185+ "mix filter should blend colors, got: {}",
3186+ html
3187+ );
3188+}
27423189
27433190#[test]
27443191fn test_autofocus_attribute() {
27453192 let result = htmlang::parser::parse("@input [type text, autofocus]");
27463193 let html = htmlang::codegen::generate(&result.document);
2747− assert!(html.contains("autofocus"), "autofocus should be in output, got: {}", html);
3194+ assert!(
3195+ html.contains("autofocus"),
3196+ "autofocus should be in output, got: {}",
3197+ html
3198+ );
27483199 // Should not produce unknown attribute warning
2749− let unknown_warnings: Vec<_> = result.diagnostics.iter()
3200+ let unknown_warnings: Vec<_> = result
3201+ .diagnostics
3202+ .iter()
27503203 .filter(|d| d.message.contains("unknown attribute") && d.message.contains("autofocus"))
27513204 .collect();
2752− assert!(unknown_warnings.is_empty(), "autofocus should not warn as unknown");
3205+ assert!(
3206+ unknown_warnings.is_empty(),
3207+ "autofocus should not warn as unknown"
3208+ );
27533209}
27543210
27553211#[test]
27563212fn test_repl_components_feed_subcommands_recognized() {
27573213 // Just verify that the parser and codegen work for content that these commands would process
2758− let result = htmlang::parser::parse("@page Test Site\n@meta description A test\n@fn card $title\n @text $title");
2759− assert!(!result.diagnostics.iter().any(|d| d.severity == htmlang::parser::Severity::Error));
3214+ let result = htmlang::parser::parse(
3215+ "@page Test Site\n@meta description A test\n@fn card $title\n @text $title",
3216+ );
3217+ assert!(
3218+ !result
3219+ .diagnostics
3220+ .iter()
3221+ .any(|d| d.severity == htmlang::parser::Severity::Error)
3222+ );
27603223}
27613224
27623225// =========================================================================
@@ -2798,9 +3261,18 @@ fn snapshot_layer_wrapping() {
27983261
27993262#[test]
28003263fn grid_template_areas_passthrough() {
2801− let output = compile("@page T\n@el [grid, grid-template-areas \"a b\"]\n @el [grid-area a]\n A");
2802− assert!(output.contains("grid-template-areas:\"a b\""), "grid-template-areas should pass through: {}", output);
2803− assert!(output.contains("grid-area:a"), "grid-area should pass through: {}", output);
3264+ let output =
3265+ compile("@page T\n@el [grid, grid-template-areas \"a b\"]\n @el [grid-area a]\n A");
3266+ assert!(
3267+ output.contains("grid-template-areas:\"a b\""),
3268+ "grid-template-areas should pass through: {}",
3269+ output
3270+ );
3271+ assert!(
3272+ output.contains("grid-area:a"),
3273+ "grid-area should pass through: {}",
3274+ output
3275+ );
28043276}
28053277
28063278// --- View transition assertions ---
@@ -2808,15 +3280,25 @@ fn grid_template_areas_passthrough() {
28083280#[test]
28093281fn view_transition_name_passthrough() {
28103282 let output = compile("@page T\n@el [view-transition-name hero]\n Content");
2811− assert!(output.contains("view-transition-name:hero"), "view-transition-name should pass through: {}", output);
3283+ assert!(
3284+ output.contains("view-transition-name:hero"),
3285+ "view-transition-name should pass through: {}",
3286+ output
3287+ );
28123288}
28133289
28143290// --- Animate shorthand assertions ---
28153291
28163292#[test]
28173293fn animate_generates_animation_css() {
2818− let output = compile("@page T\n@keyframes fade\n from [opacity 0]\n to [opacity 1]\n@el [animate fade 0.3s ease]\n Content");
2819− assert!(output.contains("animation:fade 0.3s ease"), "animate should generate animation CSS: {}", output);
3294+ let output = compile(
3295+ "@page T\n@keyframes fade\n from [opacity 0]\n to [opacity 1]\n@el [animate fade 0.3s ease]\n Content",
3296+ );
3297+ assert!(
3298+ output.contains("animation:fade 0.3s ease"),
3299+ "animate should generate animation CSS: {}",
3300+ output
3301+ );
28203302}
28213303
28223304// --- :has() pseudo assertions ---
@@ -2824,15 +3306,25 @@ fn animate_generates_animation_css() {
28243306#[test]
28253307fn has_pseudo_generates_css() {
28263308 let output = compile("@page T\n@el [has(.active):background blue]\n Content");
2827− assert!(output.contains(":has(.active)"), "should generate :has() selector: {}", output);
2828− assert!(output.contains("background:blue"), "should have background:blue in :has() rule: {}", output);
3309+ assert!(
3310+ output.contains(":has(.active)"),
3311+ "should generate :has() selector: {}",
3312+ output
3313+ );
3314+ assert!(
3315+ output.contains("background:blue"),
3316+ "should have background:blue in :has() rule: {}",
3317+ output
3318+ );
28293319}
28303320
28313321#[test]
28323322fn has_pseudo_no_warning() {
28333323 let diags = parse_diagnostics("@el [has(.child):background red]");
28343324 assert!(
2835− !diags.iter().any(|d| d.message.contains("unknown attribute")),
3325+ !diags
3326+ .iter()
3327+ .any(|d| d.message.contains("unknown attribute")),
28363328 "has() prefix should not produce unknown attribute warning: {:?}",
28373329 diags
28383330 );
@@ -2842,8 +3334,13 @@ fn has_pseudo_no_warning() {
28423334
28433335#[test]
28443336fn computed_let_equals_syntax() {
2845− let output = compile("@page T\n@let base 10\n@let doubled = $base * 2\n@el [width $doubled]\n test");
2846− assert!(output.contains("width:20px"), "computed @let with = should work: {}", output);
3337+ let output =
3338+ compile("@page T\n@let base 10\n@let doubled = $base * 2\n@el [width $doubled]\n test");
3339+ assert!(
3340+ output.contains("width:20px"),
3341+ "computed @let with = should work: {}",
3342+ output
3343+ );
28473344}
28483345
28493346// --- @layer wrapping assertions ---
@@ -2851,24 +3348,52 @@ fn computed_let_equals_syntax() {
28513348#[test]
28523349fn output_contains_layer_wrapping() {
28533350 let output = compile("@page T\n@el [padding 10]\n test");
2854− assert!(output.contains("@layer htmlang{"), "output should contain @layer htmlang wrapper: {}", output);
3351+ assert!(
3352+ output.contains("@layer htmlang{"),
3353+ "output should contain @layer htmlang wrapper: {}",
3354+ output
3355+ );
28553356}
28563357
28573358// --- Named slots in @fn assertions ---
28583359
28593360#[test]
28603361fn fn_named_slots() {
2861− let output = compile("@fn layout\n @column\n @slot header\n Default Header\n @slot content\n@layout\n @slot header\n Custom Header\n @slot content\n Page body");
2862− assert!(output.contains("Custom Header"), "named slot should be filled: {}", output);
2863− assert!(output.contains("Page body"), "content slot should be filled: {}", output);
2864− assert!(!output.contains("Default Header"), "default should be overridden: {}", output);
3362+ let output = compile(
3363+ "@fn layout\n @column\n @slot header\n Default Header\n @slot content\n@layout\n @slot header\n Custom Header\n @slot content\n Page body",
3364+ );
3365+ assert!(
3366+ output.contains("Custom Header"),
3367+ "named slot should be filled: {}",
3368+ output
3369+ );
3370+ assert!(
3371+ output.contains("Page body"),
3372+ "content slot should be filled: {}",
3373+ output
3374+ );
3375+ assert!(
3376+ !output.contains("Default Header"),
3377+ "default should be overridden: {}",
3378+ output
3379+ );
28653380}
28663381
28673382#[test]
28683383fn fn_named_slot_default() {
2869− let output = compile("@fn layout\n @column\n @slot header\n Default Header\n @slot content\n@layout\n @slot content\n Only content");
2870− assert!(output.contains("Default Header"), "unfilled slot should use default: {}", output);
2871− assert!(output.contains("Only content"), "filled slot should render: {}", output);
3384+ let output = compile(
3385+ "@fn layout\n @column\n @slot header\n Default Header\n @slot content\n@layout\n @slot content\n Only content",
3386+ );
3387+ assert!(
3388+ output.contains("Default Header"),
3389+ "unfilled slot should use default: {}",
3390+ output
3391+ );
3392+ assert!(
3393+ output.contains("Only content"),
3394+ "filled slot should render: {}",
3395+ output
3396+ );
28723397}
28733398
28743399// --- No warnings for new attributes ---
@@ -2876,10 +3401,12 @@ fn fn_named_slot_default() {
28763401#[test]
28773402fn no_warning_new_attrs_batch6() {
28783403 let diags = parse_diagnostics(
2879− "@el [grid-template-areas \"a b\", grid-area a, view-transition-name hero, animate fade 1s, critical]"
3404+ "@el [grid-template-areas \"a b\", grid-area a, view-transition-name hero, animate fade 1s, critical]",
28803405 );
28813406 assert!(
2882− !diags.iter().any(|d| d.message.contains("unknown attribute")),
3407+ !diags
3408+ .iter()
3409+ .any(|d| d.message.contains("unknown attribute")),
28833410 "new attributes should be recognized: {:?}",
28843411 diags
28853412 );
@@ -2922,14 +3449,22 @@ fn snapshot_new_css_properties_4() {
29223449#[test]
29233450fn script_element_with_src() {
29243451 let output = compile("@script [src app.js, defer]");
2925− assert!(output.contains("<script src=\"app.js\" defer>"), "script src: {}", output);
3452+ assert!(
3453+ output.contains("<script src=\"app.js\" defer>"),
3454+ "script src: {}",
3455+ output
3456+ );
29263457 assert!(output.contains("</script>"), "script close: {}", output);
29273458}
29283459
29293460#[test]
29303461fn script_element_inline() {
29313462 let output = compile("@script\n console.log(42);");
2932− assert!(output.contains("<script>console.log(42);</script>"), "inline script: {}", output);
3463+ assert!(
3464+ output.contains("<script>console.log(42);</script>"),
3465+ "inline script: {}",
3466+ output
3467+ );
29333468}
29343469
29353470#[test]
@@ -2954,7 +3489,11 @@ fn search_element() {
29543489#[test]
29553490fn breadcrumb_generates_nav_ol() {
29563491 let output = compile("@breadcrumb\n @text Home\n @text About");
2957− assert!(output.contains("<nav aria-label=\"breadcrumb\">"), "nav: {}", output);
3492+ assert!(
3493+ output.contains("<nav aria-label=\"breadcrumb\">"),
3494+ "nav: {}",
3495+ output
3496+ );
29583497 assert!(output.contains("<ol>"), "ol: {}", output);
29593498 assert!(output.contains("<li>"), "li: {}", output);
29603499}
@@ -2962,20 +3501,32 @@ fn breadcrumb_generates_nav_ol() {
29623501#[test]
29633502fn canonical_directive() {
29643503 let output = compile("@page T\n@canonical https://example.com/page\n@text Hello");
2965− assert!(output.contains("<link rel=\"canonical\" href=\"https://example.com/page\">"), "canonical: {}", output);
3504+ assert!(
3505+ output.contains("<link rel=\"canonical\" href=\"https://example.com/page\">"),
3506+ "canonical: {}",
3507+ output
3508+ );
29663509}
29673510
29683511#[test]
29693512fn base_directive() {
29703513 let output = compile("@page T\n@base https://example.com/\n@text Hello");
2971− assert!(output.contains("<base href=\"https://example.com/\">"), "base: {}", output);
3514+ assert!(
3515+ output.contains("<base href=\"https://example.com/\">"),
3516+ "base: {}",
3517+ output
3518+ );
29723519}
29733520
29743521#[test]
29753522fn font_face_directive() {
29763523 let output = compile("@page T\n@font-face Inter fonts/inter.woff2\n@text Hello");
29773524 assert!(output.contains("@font-face"), "font-face: {}", output);
2978− assert!(output.contains("font-family:'Inter'"), "font name: {}", output);
3525+ assert!(
3526+ output.contains("font-family:'Inter'"),
3527+ "font name: {}",
3528+ output
3529+ );
29793530 assert!(output.contains("fonts/inter.woff2"), "font url: {}", output);
29803531 assert!(output.contains("woff2"), "format hint: {}", output);
29813532}
@@ -2983,7 +3534,11 @@ fn font_face_directive() {
29833534#[test]
29843535fn json_ld_directive() {
29853536 let output = compile("@page T\n@json-ld\n {\"@type\": \"WebPage\"}\n@text Hello");
2986− assert!(output.contains("application/ld+json"), "json-ld type: {}", output);
3537+ assert!(
3538+ output.contains("application/ld+json"),
3539+ "json-ld type: {}",
3540+ output
3541+ );
29873542 assert!(output.contains("WebPage"), "json-ld content: {}", output);
29883543}
29893544
@@ -3015,28 +3570,42 @@ fn valid_invalid_pseudo() {
30153570#[test]
30163571fn text_underline_offset_property() {
30173572 let output = compile("@text [underline, text-underline-offset 4] Link");
3018− assert!(output.contains("text-underline-offset:4px"), "text-underline-offset: {}", output);
3573+ assert!(
3574+ output.contains("text-underline-offset:4px"),
3575+ "text-underline-offset: {}",
3576+ output
3577+ );
30193578}
30203579
30213580#[test]
30223581fn column_width_property() {
30233582 let output = compile("@el [column-width 200]\n Content");
3024− assert!(output.contains("column-width:200px"), "column-width: {}", output);
3583+ assert!(
3584+ output.contains("column-width:200px"),
3585+ "column-width: {}",
3586+ output
3587+ );
30253588}
30263589
30273590#[test]
30283591fn column_rule_property() {
30293592 let output = compile("@el [column-rule 1px solid #ccc]\n Content");
3030− assert!(output.contains("column-rule:1px solid #ccc"), "column-rule: {}", output);
3593+ assert!(
3594+ output.contains("column-rule:1px solid #ccc"),
3595+ "column-rule: {}",
3596+ output
3597+ );
30313598}
30323599
30333600#[test]
30343601fn no_warning_new_attrs_batch7() {
30353602 let diags = parse_diagnostics(
3036− "@el [text-underline-offset 4, column-width 200, column-rule 1px solid gray]"
3603+ "@el [text-underline-offset 4, column-width 200, column-rule 1px solid gray]",
30373604 );
30383605 assert!(
3039− !diags.iter().any(|d| d.message.contains("unknown attribute")),
3606+ !diags
3607+ .iter()
3608+ .any(|d| d.message.contains("unknown attribute")),
30403609 "new CSS properties should be recognized: {:?}",
30413610 diags
30423611 );
@@ -3045,10 +3614,12 @@ fn no_warning_new_attrs_batch7() {
30453614#[test]
30463615fn no_warning_script_attrs() {
30473616 let diags = parse_diagnostics(
3048− "@script [src app.js, defer, async, crossorigin anonymous, integrity sha384-abc, nomodule]"
3617+ "@script [src app.js, defer, async, crossorigin anonymous, integrity sha384-abc, nomodule]",
30493618 );
30503619 assert!(
3051− !diags.iter().any(|d| d.message.contains("unknown attribute")),
3620+ !diags
3621+ .iter()
3622+ .any(|d| d.message.contains("unknown attribute")),
30523623 "script attributes should be recognized: {:?}",
30533624 diags
30543625 );
@@ -3066,21 +3637,41 @@ fn snapshot_mixin_spread() {
30663637#[test]
30673638fn mixin_expands_in_attrs() {
30683639 let output = compile("@mixin card [padding 20, rounded 8]\n@el [...$card]\n Hi");
3069− assert!(output.contains("padding:20px"), "mixin should expand padding: {}", output);
3070− assert!(output.contains("border-radius:8px"), "mixin should expand rounded: {}", output);
3640+ assert!(
3641+ output.contains("padding:20px"),
3642+ "mixin should expand padding: {}",
3643+ output
3644+ );
3645+ assert!(
3646+ output.contains("border-radius:8px"),
3647+ "mixin should expand rounded: {}",
3648+ output
3649+ );
30713650}
30723651
30733652#[test]
30743653fn mixin_with_dollar_syntax() {
30753654 let output = compile("@mixin card [padding 20, rounded 8]\n@el [$card]\n Hi");
3076− assert!(output.contains("padding:20px"), "mixin with $ syntax should expand: {}", output);
3655+ assert!(
3656+ output.contains("padding:20px"),
3657+ "mixin with $ syntax should expand: {}",
3658+ output
3659+ );
30773660}
30783661
30793662#[test]
30803663fn mixin_compose_with_extra_attrs() {
30813664 let output = compile("@mixin base [padding 10]\n@el [...$base, background red]\n Hi");
3082− assert!(output.contains("padding:10px"), "mixin should expand: {}", output);
3083− assert!(output.contains("background:red"), "extra attrs should work: {}", output);
3665+ assert!(
3666+ output.contains("padding:10px"),
3667+ "mixin should expand: {}",
3668+ output
3669+ );
3670+ assert!(
3671+ output.contains("background:red"),
3672+ "extra attrs should work: {}",
3673+ output
3674+ );
30843675}
30853676
30863677#[test]
@@ -3116,7 +3707,9 @@ fn snapshot_assert_directive() {
31163707fn assert_passes_no_error() {
31173708 let diags = parse_diagnostics("@let x hello\n@assert $x == hello\n@el [padding 10]");
31183709 assert!(
3119− !diags.iter().any(|d| d.severity == htmlang::parser::Severity::Error),
3710+ !diags
3711+ .iter()
3712+ .any(|d| d.severity == htmlang::parser::Severity::Error),
31203713 "passing assertion should produce no error, got: {:?}",
31213714 diags
31223715 );
@@ -3126,7 +3719,10 @@ fn assert_passes_no_error() {
31263719fn assert_fails_produces_error() {
31273720 let diags = parse_diagnostics("@let x hello\n@assert $x == world");
31283721 assert!(
3129− diags.iter().any(|d| d.severity == htmlang::parser::Severity::Error && d.message.contains("assertion failed")),
3722+ diags
3723+ .iter()
3724+ .any(|d| d.severity == htmlang::parser::Severity::Error
3725+ && d.message.contains("assertion failed")),
31303726 "failing assertion should produce error, got: {:?}",
31313727 diags
31323728 );
@@ -3136,7 +3732,9 @@ fn assert_fails_produces_error() {
31363732fn assert_not_equal() {
31373733 let diags = parse_diagnostics("@let x hello\n@assert $x != world\n@el");
31383734 assert!(
3139− !diags.iter().any(|d| d.severity == htmlang::parser::Severity::Error),
3735+ !diags
3736+ .iter()
3737+ .any(|d| d.severity == htmlang::parser::Severity::Error),
31403738 "!= assertion should pass when values differ, got: {:?}",
31413739 diags
31423740 );
@@ -3146,7 +3744,9 @@ fn assert_not_equal() {
31463744fn assert_truthy() {
31473745 let diags = parse_diagnostics("@let x true\n@assert $x\n@el");
31483746 assert!(
3149− !diags.iter().any(|d| d.severity == htmlang::parser::Severity::Error),
3747+ !diags
3748+ .iter()
3749+ .any(|d| d.severity == htmlang::parser::Severity::Error),
31503750 "truthy assertion should pass, got: {:?}",
31513751 diags
31523752 );
@@ -3164,19 +3764,31 @@ fn snapshot_clamp_css() {
31643764#[test]
31653765fn clamp_passthrough() {
31663766 let output = compile("@el [size clamp(16px, 2vw, 24px)]");
3167− assert!(output.contains("font-size:clamp(16px, 2vw, 24px)"), "clamp should pass through: {}", output);
3767+ assert!(
3768+ output.contains("font-size:clamp(16px, 2vw, 24px)"),
3769+ "clamp should pass through: {}",
3770+ output
3771+ );
31683772}
31693773
31703774#[test]
31713775fn min_passthrough() {
31723776 let output = compile("@el [width min(100%, 800px)]");
3173− assert!(output.contains("width:min(100%, 800px)"), "min() should pass through: {}", output);
3777+ assert!(
3778+ output.contains("width:min(100%, 800px)"),
3779+ "min() should pass through: {}",
3780+ output
3781+ );
31743782}
31753783
31763784#[test]
31773785fn max_passthrough() {
31783786 let output = compile("@el [padding max(10px, 2vw)]");
3179− assert!(output.contains("padding:max(10px, 2vw)"), "max() should pass through: {}", output);
3787+ assert!(
3788+ output.contains("padding:max(10px, 2vw)"),
3789+ "max() should pass through: {}",
3790+ output
3791+ );
31803792}
31813793
31823794// ---------------------------------------------------------------------------
@@ -3187,14 +3799,22 @@ fn max_passthrough() {
31873799fn round_trip_basic() {
31883800 let input = "@page Round Trip\n@column [padding 20, spacing 10]\n @text [bold, size 24] Hello\n @text World";
31893801 let result = htmlang::parser::parse(input);
3190− assert!(!result.diagnostics.iter().any(|d| d.severity == htmlang::parser::Severity::Error));
3802+ assert!(
3803+ !result
3804+ .diagnostics
3805+ .iter()
3806+ .any(|d| d.severity == htmlang::parser::Severity::Error)
3807+ );
31913808 let html = htmlang::codegen::generate_dev(&result.document);
31923809 // Convert back to .hl
31933810 let hl = htmlang::convert::convert(&html);
31943811 // The round-trip should produce valid .hl that parses without errors
31953812 let result2 = htmlang::parser::parse(&hl);
31963813 assert!(
3197− !result2.diagnostics.iter().any(|d| d.severity == htmlang::parser::Severity::Error),
3814+ !result2
3815+ .diagnostics
3816+ .iter()
3817+ .any(|d| d.severity == htmlang::parser::Severity::Error),
31983818 "round-trip should produce valid .hl, got errors: {:?}",
31993819 result2.diagnostics
32003820 );
@@ -3276,7 +3896,8 @@ fn component_wraps_in_scoped_div() {
32763896
32773897#[test]
32783898fn component_with_children() {
3279− let html = compile("@component box\n @el [padding 10]\n @children\n\n@box\n @text Inside\n");
3899+ let html =
3900+ compile("@component box\n @el [padding 10]\n @children\n\n@box\n @text Inside\n");
32803901 assert!(html.contains("hl-box"));
32813902 assert!(html.contains("Inside"));
32823903}
@@ -3287,23 +3908,31 @@ fn component_with_children() {
32873908
32883909#[test]
32893910fn switch_matches_case() {
3290− let html = compile("@let variant primary\n@switch $variant\n @case primary\n @text Primary\n @case danger\n @text Danger\n");
3911+ let html = compile(
3912+ "@let variant primary\n@switch $variant\n @case primary\n @text Primary\n @case danger\n @text Danger\n",
3913+ );
32913914 assert!(html.contains("Primary"));
32923915 assert!(!html.contains("Danger"));
32933916}
32943917
32953918#[test]
32963919fn switch_falls_to_default() {
3297− let html = compile("@let variant unknown\n@switch $variant\n @case primary\n @text Primary\n @default\n @text Default\n");
3920+ let html = compile(
3921+ "@let variant unknown\n@switch $variant\n @case primary\n @text Primary\n @default\n @text Default\n",
3922+ );
32983923 assert!(!html.contains("Primary"));
32993924 assert!(html.contains("Default"));
33003925}
33013926
33023927#[test]
33033928fn switch_with_attrs() {
3304− let _html = compile("@let variant primary\n@switch $variant\n @case primary [background blue, color white]\n @case danger [background red, color white]\n");
3929+ let _html = compile(
3930+ "@let variant primary\n@switch $variant\n @case primary [background blue, color white]\n @case danger [background red, color white]\n",
3931+ );
33053932 // The @switch should register matched attrs as __switch define
3306− let result = htmlang::parser::parse("@let variant primary\n@switch $variant\n @case primary [background blue, color white]\n @case danger [background red, color white]\n");
3933+ let result = htmlang::parser::parse(
3934+ "@let variant primary\n@switch $variant\n @case primary [background blue, color white]\n @case danger [background red, color white]\n",
3935+ );
33073936 assert!(result.document.defines.contains_key("__switch"));
33083937}
33093938
@@ -3317,7 +3946,12 @@ fn minified_output_is_smaller() {
33173946 let result = htmlang::parser::parse(input);
33183947 let normal = htmlang::codegen::generate(&result.document);
33193948 let minified = htmlang::codegen::generate_minified(&result.document);
3320− assert!(minified.len() <= normal.len(), "minified ({}) should be <= normal ({})", minified.len(), normal.len());
3949+ assert!(
3950+ minified.len() <= normal.len(),
3951+ "minified ({}) should be <= normal ({})",
3952+ minified.len(),
3953+ normal.len()
3954+ );
33213955 assert!(minified.contains("Hello World"));
33223956}
33233957
@@ -3349,10 +3983,13 @@ fn critical_attr_inlines_styles() {
33493983#[test]
33503984fn warning_input_without_label() {
33513985 let diags = parse_diagnostics("@input [type text]\n");
3352− let has_label_warning = diags.iter().any(|d|
3353− d.message.contains("aria-label") || d.message.contains("@label")
3986+ let has_label_warning = diags
3987+ .iter()
3988+ .any(|d| d.message.contains("aria-label") || d.message.contains("@label"));
3989+ assert!(
3990+ has_label_warning,
3991+ "should warn about input without label association"
33543992 );
3355− assert!(has_label_warning, "should warn about input without label association");
33563993}
33573994
33583995#[test]
@@ -3365,8 +4002,13 @@ fn warning_iframe_without_title() {
33654002#[test]
33664003fn warning_button_without_text() {
33674004 let diags = parse_diagnostics("@button [background red]\n");
3368− let has_warning = diags.iter().any(|d| d.message.contains("text content") || d.message.contains("aria-label"));
3369− assert!(has_warning, "should warn about button without accessible text");
4005+ let has_warning = diags
4006+ .iter()
4007+ .any(|d| d.message.contains("text content") || d.message.contains("aria-label"));
4008+ assert!(
4009+ has_warning,
4010+ "should warn about button without accessible text"
4011+ );
33704012}
33714013
33724014#[test]
@@ -3379,19 +4021,25 @@ fn warning_positive_tabindex() {
33794021#[test]
33804022fn no_warning_input_with_aria_label() {
33814023 let diags = parse_diagnostics("@input [type text, aria-label Search]\n");
3382− let has_label_warning = diags.iter().any(|d|
3383− d.message.contains("should have an") && d.message.contains("@label")
4024+ let has_label_warning = diags
4025+ .iter()
4026+ .any(|d| d.message.contains("should have an") && d.message.contains("@label"));
4027+ assert!(
4028+ !has_label_warning,
4029+ "should not warn when aria-label is present"
33844030 );
3385− assert!(!has_label_warning, "should not warn when aria-label is present");
33864031}
33874032
33884033#[test]
33894034fn no_warning_input_in_label() {
33904035 let diags = parse_diagnostics("@label\n @input [type text]\n");
3391− let has_label_warning = diags.iter().any(|d|
3392− d.message.contains("should have an") && d.message.contains("@label")
4036+ let has_label_warning = diags
4037+ .iter()
4038+ .any(|d| d.message.contains("should have an") && d.message.contains("@label"));
4039+ assert!(
4040+ !has_label_warning,
4041+ "should not warn when input is inside @label"
33934042 );
3394− assert!(!has_label_warning, "should not warn when input is inside @label");
33954043}
33964044
33974045// --- New feature tests ---
@@ -3418,42 +4066,72 @@ fn snapshot_data_directive() {
34184066
34194067#[test]
34204068fn test_popover_in_output() {
3421− let html = compile("@button [popovertarget my-pop] Open\n@el [popover, id my-pop, padding 10]\n Hello");
3422− assert!(html.contains("popovertarget=\"my-pop\""), "should have popovertarget attr");
3423− assert!(html.contains(" popover"), "should have popover boolean attr");
4069+ let html = compile(
4070+ "@button [popovertarget my-pop] Open\n@el [popover, id my-pop, padding 10]\n Hello",
4071+ );
4072+ assert!(
4073+ html.contains("popovertarget=\"my-pop\""),
4074+ "should have popovertarget attr"
4075+ );
4076+ assert!(
4077+ html.contains(" popover"),
4078+ "should have popover boolean attr"
4079+ );
34244080}
34254081
34264082#[test]
34274083fn test_color_scheme_css() {
34284084 let html = compile("@el [color-scheme light dark]\n Test");
3429− assert!(html.contains("color-scheme:light dark"), "should generate color-scheme CSS");
4085+ assert!(
4086+ html.contains("color-scheme:light dark"),
4087+ "should generate color-scheme CSS"
4088+ );
34304089}
34314090
34324091#[test]
34334092fn test_appearance_css() {
34344093 let html = compile("@input [appearance none, padding 10]");
3435− assert!(html.contains("appearance:none"), "should generate appearance CSS");
4094+ assert!(
4095+ html.contains("appearance:none"),
4096+ "should generate appearance CSS"
4097+ );
34364098}
34374099
34384100#[test]
34394101fn test_inputmode_attr() {
34404102 let html = compile("@input [type search, inputmode search]");
3441− assert!(html.contains("inputmode=\"search\""), "should pass through inputmode");
4103+ assert!(
4104+ html.contains("inputmode=\"search\""),
4105+ "should pass through inputmode"
4106+ );
34424107}
34434108
34444109#[test]
34454110fn test_fetchpriority_attr() {
34464111 let html = compile("@image [fetchpriority high, width 100] hero.jpg");
3447− assert!(html.contains("fetchpriority=\"high\""), "should pass through fetchpriority");
4112+ assert!(
4113+ html.contains("fetchpriority=\"high\""),
4114+ "should pass through fetchpriority"
4115+ );
34484116}
34494117
34504118#[test]
34514119fn test_compat_vendor_prefixes() {
3452− let result = htmlang::parser::parse("@el [backdrop-filter blur(10px), user-select none]\n Test");
4120+ let result =
4121+ htmlang::parser::parse("@el [backdrop-filter blur(10px), user-select none]\n Test");
34534122 let html = htmlang::codegen::generate_compat(&result.document);
3454− assert!(html.contains("-webkit-backdrop-filter"), "should add webkit prefix for backdrop-filter");
3455− assert!(html.contains("-webkit-user-select"), "should add webkit prefix for user-select");
3456− assert!(html.contains("-moz-user-select"), "should add moz prefix for user-select");
4123+ assert!(
4124+ html.contains("-webkit-backdrop-filter"),
4125+ "should add webkit prefix for backdrop-filter"
4126+ );
4127+ assert!(
4128+ html.contains("-webkit-user-select"),
4129+ "should add webkit prefix for user-select"
4130+ );
4131+ assert!(
4132+ html.contains("-moz-user-select"),
4133+ "should add moz prefix for user-select"
4134+ );
34574135}
34584136
34594137// ---------------------------------------------------------------------------
@@ -3473,7 +4151,9 @@ fn test_error_unknown_element() {
34734151fn test_error_unclosed_brackets() {
34744152 let diags = parse_diagnostics("@el [padding 10\n Hello");
34754153 assert!(
3476− diags.iter().any(|d| d.message.contains("unclosed '['") || d.message.contains("unclosed")),
4154+ diags
4155+ .iter()
4156+ .any(|d| d.message.contains("unclosed '['") || d.message.contains("unclosed")),
34774157 "should report unclosed bracket error"
34784158 );
34794159}
@@ -3511,7 +4191,8 @@ fn test_error_circular_include() {
35114191fn test_error_assert_failure() {
35124192 let diags = parse_diagnostics("@let x 5\n@assert $x == 10");
35134193 assert!(
3514− diags.iter().any(|d| d.message.contains("assertion failed") && d.severity == htmlang::parser::Severity::Error),
4194+ diags.iter().any(|d| d.message.contains("assertion failed")
4195+ && d.severity == htmlang::parser::Severity::Error),
35154196 "should report assertion failure as error"
35164197 );
35174198}
@@ -3520,7 +4201,9 @@ fn test_error_assert_failure() {
35204201fn test_error_duplicate_attribute() {
35214202 let diags = parse_diagnostics("@el [padding 10, padding 20]\n Hello");
35224203 assert!(
3523− diags.iter().any(|d| d.message.contains("duplicate attribute")),
4204+ diags
4205+ .iter()
4206+ .any(|d| d.message.contains("duplicate attribute")),
35244207 "should warn on duplicate attribute"
35254208 );
35264209}
@@ -3529,7 +4212,8 @@ fn test_error_duplicate_attribute() {
35294212fn test_warning_unused_variable() {
35304213 let diags = parse_diagnostics("@let unused_var hello\n@text Hello");
35314214 assert!(
3532− diags.iter().any(|d| d.message.contains("unused variable") && d.severity == htmlang::parser::Severity::Warning),
4215+ diags.iter().any(|d| d.message.contains("unused variable")
4216+ && d.severity == htmlang::parser::Severity::Warning),
35334217 "should warn about unused variable"
35344218 );
35354219}
@@ -3538,7 +4222,8 @@ fn test_warning_unused_variable() {
35384222fn test_warning_unused_function() {
35394223 let diags = parse_diagnostics("@fn unused_fn\n @text Hello\n\n@text World");
35404224 assert!(
3541− diags.iter().any(|d| d.message.contains("unused function") && d.severity == htmlang::parser::Severity::Warning),
4225+ diags.iter().any(|d| d.message.contains("unused function")
4226+ && d.severity == htmlang::parser::Severity::Warning),
35424227 "should warn about unused function"
35434228 );
35444229}
@@ -3557,15 +4242,26 @@ fn test_each_index_variable() {
35574242
35584243#[test]
35594244fn test_children_fallback_content() {
3560− let html = compile("@fn wrapper\n @el [padding 10]\n @children\n @text Default content\n\n@wrapper");
3561− assert!(html.contains("Default content"), "should use @children fallback when no children provided");
4245+ let html = compile(
4246+ "@fn wrapper\n @el [padding 10]\n @children\n @text Default content\n\n@wrapper",
4247+ );
4248+ assert!(
4249+ html.contains("Default content"),
4250+ "should use @children fallback when no children provided"
4251+ );
35624252}
35634253
35644254#[test]
35654255fn test_spread_define() {
35664256 let html = compile("@define btn [padding 12, bold]\n@el [...$btn]\n Click");
3567− assert!(html.contains("padding:12px"), "spread define should apply padding");
3568− assert!(html.contains("font-weight:bold") || html.contains("font-weight:700"), "spread define should apply bold");
4257+ assert!(
4258+ html.contains("padding:12px"),
4259+ "spread define should apply padding"
4260+ );
4261+ assert!(
4262+ html.contains("font-weight:bold") || html.contains("font-weight:700"),
4263+ "spread define should apply bold"
4264+ );
35694265}
35704266
35714267#[test]
@@ -3573,7 +4269,10 @@ fn test_log_directive() {
35734269 // @log should not produce errors and should be consumed without output nodes
35744270 let result = htmlang::parser::parse("@let x hello\n@log $x\n@text $x");
35754271 assert!(
3576− result.diagnostics.iter().all(|d| d.severity != htmlang::parser::Severity::Error),
4272+ result
4273+ .diagnostics
4274+ .iter()
4275+ .all(|d| d.severity != htmlang::parser::Severity::Error),
35774276 "@log should not produce errors"
35784277 );
35794278 let html = htmlang::codegen::generate(&result.document);
@@ -3584,8 +4283,14 @@ fn test_log_directive() {
35844283fn test_short_class_names() {
35854284 let html = compile("@el [padding 10]\n @el [padding 20]\n Hello");
35864285 // Class names should be short single letters, not _0, _1
3587− assert!(!html.contains("class=\"_0\""), "should use short class names, not _0");
3588− assert!(html.contains("class=\"a\"") || html.contains("class=\"b\""), "should use short alphabetic class names");
4286+ assert!(
4287+ !html.contains("class=\"_0\""),
4288+ "should use short class names, not _0"
4289+ );
4290+ assert!(
4291+ html.contains("class=\"a\"") || html.contains("class=\"b\""),
4292+ "should use short alphabetic class names"
4293+ );
35894294}
35904295
35914296// ---------------------------------------------------------------------------
@@ -3603,8 +4308,14 @@ fn test_round_trip() {
36034308 // May have warnings (e.g. unknown attrs from raw HTML) but should not have errors that prevent output
36044309 let html2 = htmlang::codegen::generate(&result2.document);
36054310 // Both HTML outputs should contain the same text content
3606− assert!(html2.contains("Hello"), "round-trip should preserve text content 'Hello'");
3607− assert!(html2.contains("This is a test"), "round-trip should preserve text content 'This is a test'");
4311+ assert!(
4312+ html2.contains("Hello"),
4313+ "round-trip should preserve text content 'Hello'"
4314+ );
4315+ assert!(
4316+ html2.contains("This is a test"),
4317+ "round-trip should preserve text content 'This is a test'"
4318+ );
36084319}
36094320
36104321// ---------------------------------------------------------------------------
@@ -3674,88 +4385,139 @@ fn repeat_directive_basic() {
36744385#[test]
36754386fn with_directive_rebinding() {
36764387 let output = compile("@let x hello\n@with $x as y\n @text $y");
3677− assert!(output.contains("hello"), "expected @with to rebind variable");
4388+ assert!(
4389+ output.contains("hello"),
4390+ "expected @with to rebind variable"
4391+ );
36784392}
36794393
36804394#[test]
36814395fn markdown_renders_heading() {
36824396 let output = compile("@markdown\n # Title\n Some text");
3683− assert!(output.contains("<h1>Title</h1>"), "markdown should render # as <h1>");
3684− assert!(output.contains("<p>Some text</p>"), "markdown should render paragraphs");
4397+ assert!(
4398+ output.contains("<h1>Title</h1>"),
4399+ "markdown should render # as <h1>"
4400+ );
4401+ assert!(
4402+ output.contains("<p>Some text</p>"),
4403+ "markdown should render paragraphs"
4404+ );
36854405}
36864406
36874407#[test]
36884408fn markdown_renders_bold_italic() {
36894409 let output = compile("@markdown\n This is **bold** and *italic*");
3690− assert!(output.contains("<strong>bold</strong>"), "markdown should render **bold**");
3691− assert!(output.contains("<em>italic</em>"), "markdown should render *italic*");
4410+ assert!(
4411+ output.contains("<strong>bold</strong>"),
4412+ "markdown should render **bold**"
4413+ );
4414+ assert!(
4415+ output.contains("<em>italic</em>"),
4416+ "markdown should render *italic*"
4417+ );
36924418}
36934419
36944420#[test]
36954421fn markdown_renders_code() {
36964422 let output = compile("@markdown\n Use `code` here");
3697− assert!(output.contains("<code>code</code>"), "markdown should render `code`");
4423+ assert!(
4424+ output.contains("<code>code</code>"),
4425+ "markdown should render `code`"
4426+ );
36984427}
36994428
37004429#[test]
37014430fn markdown_renders_link() {
37024431 let output = compile("@markdown\n Visit [example](https://example.com)");
3703− assert!(output.contains("<a href=\"https://example.com\">example</a>"), "markdown should render links");
4432+ assert!(
4433+ output.contains("<a href=\"https://example.com\">example</a>"),
4434+ "markdown should render links"
4435+ );
37044436}
37054437
37064438#[test]
37074439fn markdown_renders_list() {
37084440 let output = compile("@markdown\n - one\n - two\n - three");
3709− assert!(output.contains("<ul>"), "markdown should render unordered list");
3710− assert!(output.contains("<li>one</li>"), "markdown should render list items");
4441+ assert!(
4442+ output.contains("<ul>"),
4443+ "markdown should render unordered list"
4444+ );
4445+ assert!(
4446+ output.contains("<li>one</li>"),
4447+ "markdown should render list items"
4448+ );
37114449}
37124450
37134451#[test]
37144452fn scope_block_generates_css() {
37154453 let output = compile("@page Test\n@scope .card\n .title { color: red; }\n@text hello");
3716− assert!(output.contains("@scope"), "should generate @scope CSS block");
4454+ assert!(
4455+ output.contains("@scope"),
4456+ "should generate @scope CSS block"
4457+ );
37174458}
37184459
37194460#[test]
37204461fn starting_style_generates_css() {
37214462 let output = compile("@page Test\n@starting-style\n .fade { opacity: 0; }\n@text hello");
3722− assert!(output.contains("@starting-style"), "should generate @starting-style CSS block");
4463+ assert!(
4464+ output.contains("@starting-style"),
4465+ "should generate @starting-style CSS block"
4466+ );
37234467}
37244468
37254469#[test]
37264470fn manifest_generates_link() {
37274471 let output = compile("@page App\n@manifest My App\n display standalone\n@text hi");
3728− assert!(output.contains("rel=\"manifest\""), "should generate manifest link");
4472+ assert!(
4473+ output.contains("rel=\"manifest\""),
4474+ "should generate manifest link"
4475+ );
37294476}
37304477
37314478#[test]
37324479fn subgrid_support() {
37334480 let output = compile("@el [grid-template-columns subgrid]");
3734− assert!(output.contains("grid-template-columns:subgrid"), "should support CSS subgrid");
4481+ assert!(
4482+ output.contains("grid-template-columns:subgrid"),
4483+ "should support CSS subgrid"
4484+ );
37354485}
37364486
37374487#[test]
37384488fn anchor_positioning_support() {
37394489 let output = compile("@el [anchor-name --my-anchor]\n @text anchor");
3740− assert!(output.contains("anchor-name:--my-anchor"), "should support anchor-name CSS property");
4490+ assert!(
4491+ output.contains("anchor-name:--my-anchor"),
4492+ "should support anchor-name CSS property"
4493+ );
37414494}
37424495
37434496#[test]
37444497fn scroll_driven_animation_support() {
37454498 let output = compile("@el [animation-timeline scroll()]\n @text scroll");
3746− assert!(output.contains("animation-timeline:scroll()"), "should support animation-timeline CSS property");
4499+ assert!(
4500+ output.contains("animation-timeline:scroll()"),
4501+ "should support animation-timeline CSS property"
4502+ );
37474503}
37484504
37494505#[test]
37504506fn initial_letter_support() {
37514507 let output = compile("@text [initial-letter 3] O");
3752− assert!(output.contains("initial-letter:3"), "should support initial-letter CSS property");
4508+ assert!(
4509+ output.contains("initial-letter:3"),
4510+ "should support initial-letter CSS property"
4511+ );
37534512}
37544513
37554514#[test]
37564515fn position_area_support() {
37574516 let output = compile("@el [position-area top]\n @text tooltip");
3758− assert!(output.contains("position-area:top"), "should support position-area CSS property");
4517+ assert!(
4518+ output.contains("position-area:top"),
4519+ "should support position-area CSS property"
4520+ );
37594521}
37604522
37614523// --- Snapshot tests for batch 2 ---
@@ -3789,9 +4551,13 @@ fn snapshot_responsive_images() {
37894551
37904552#[test]
37914553fn translations_inject_variables() {
3792− let output = compile("@translations\n en:\n hello Hi\n world Earth\n@text $t.hello $t.world");
4554+ let output =
4555+ compile("@translations\n en:\n hello Hi\n world Earth\n@text $t.hello $t.world");
37934556 assert!(output.contains("Hi"), "should inject translation for hello");
3794− assert!(output.contains("Earth"), "should inject translation for world");
4557+ assert!(
4558+ output.contains("Earth"),
4559+ "should inject translation for world"
4560+ );
37954561}
37964562
37974563#[test]
@@ -3814,7 +4580,10 @@ fn pagination_page_2() {
38144580#[test]
38154581fn image_auto_preload() {
38164582 let output = compile("@page Test\n@image logo.png\n@image hero.jpg");
3817− assert!(output.contains("rel=\"preload\""), "should auto-preload images");
4583+ assert!(
4584+ output.contains("rel=\"preload\""),
4585+ "should auto-preload images"
4586+ );
38184587 assert!(output.contains("logo.png"), "should preload logo.png");
38194588}
38204589
@@ -3823,15 +4592,28 @@ fn source_map_generation() {
38234592 let input = "@page Test\n@text [bold] Hello";
38244593 let result = htmlang::parser::parse(input);
38254594 let map = htmlang::codegen::generate_source_map(&result.document, "test.hl");
3826− assert!(map.contains("\"version\":3"), "source map should have version 3");
3827− assert!(map.contains("test.hl"), "source map should reference source file");
4595+ assert!(
4596+ map.contains("\"version\":3"),
4597+ "source map should have version 3"
4598+ );
4599+ assert!(
4600+ map.contains("test.hl"),
4601+ "source map should reference source file"
4602+ );
38284603}
38294604
38304605#[test]
38314606fn parser_multiple_errors() {
38324607 let diags = parse_diagnostics("@unknown1\n@text hello\n@unknown2");
3833− let errors: Vec<_> = diags.iter().filter(|d| d.severity == htmlang::parser::Severity::Error).collect();
3834− assert!(errors.len() >= 2, "parser should report multiple errors, got {}", errors.len());
4608+ let errors: Vec<_> = diags
4609+ .iter()
4610+ .filter(|d| d.severity == htmlang::parser::Severity::Error)
4611+ .collect();
4612+ assert!(
4613+ errors.len() >= 2,
4614+ "parser should report multiple errors, got {}",
4615+ errors.len()
4616+ );
38354617}
38364618
38374619#[test]
@@ -3848,45 +4630,78 @@ fn repeat_with_index() {
38484630
38494631#[test]
38504632fn env_directive_with_default() {
3851− let output = compile("@env HTMLANG_TEST_NONEXISTENT fallback_value\n@text $htmlang_test_nonexistent");
3852− assert!(output.contains("fallback_value"), "@env should use default when var is not set, got: {}", output);
4633+ let output =
4634+ compile("@env HTMLANG_TEST_NONEXISTENT fallback_value\n@text $htmlang_test_nonexistent");
4635+ assert!(
4636+ output.contains("fallback_value"),
4637+ "@env should use default when var is not set, got: {}",
4638+ output
4639+ );
38534640}
38544641
38554642#[test]
38564643fn env_directive_from_environment() {
38574644 // Set an env var and check it's picked up
3858− unsafe { std::env::set_var("HTMLANG_TEST_VAR", "hello_world"); }
4645+ unsafe {
4646+ std::env::set_var("HTMLANG_TEST_VAR", "hello_world");
4647+ }
38594648 let output = compile("@env HTMLANG_TEST_VAR\n@text $htmlang_test_var");
3860− assert!(output.contains("hello_world"), "@env should read env var, got: {}", output);
3861− unsafe { std::env::remove_var("HTMLANG_TEST_VAR"); }
4649+ assert!(
4650+ output.contains("hello_world"),
4651+ "@env should read env var, got: {}",
4652+ output
4653+ );
4654+ unsafe {
4655+ std::env::remove_var("HTMLANG_TEST_VAR");
4656+ }
38624657}
38634658
38644659#[test]
38654660fn env_directive_warning_when_missing() {
38664661 let result = htmlang::parser::parse("@env HTMLANG_DEFINITELY_NOT_SET_12345");
3867− let warnings: Vec<_> = result.diagnostics.iter()
3868− .filter(|d| d.severity == htmlang::parser::Severity::Warning && d.message.contains("not set"))
4662+ let warnings: Vec<_> = result
4663+ .diagnostics
4664+ .iter()
4665+ .filter(|d| {
4666+ d.severity == htmlang::parser::Severity::Warning && d.message.contains("not set")
4667+ })
38694668 .collect();
3870− assert!(!warnings.is_empty(), "@env should warn when var is not set, got: {:?}", result.diagnostics);
4669+ assert!(
4670+ !warnings.is_empty(),
4671+ "@env should warn when var is not set, got: {:?}",
4672+ result.diagnostics
4673+ );
38714674}
38724675
38734676#[test]
38744677fn fetch_directive_error_on_https() {
38754678 // @fetch with https should produce an error (no TLS support)
38764679 let result = htmlang::parser::parse("@fetch $data https://example.com/api");
3877− let errors: Vec<_> = result.diagnostics.iter()
4680+ let errors: Vec<_> = result
4681+ .diagnostics
4682+ .iter()
38784683 .filter(|d| d.severity == htmlang::parser::Severity::Error && d.message.contains("https"))
38794684 .collect();
3880− assert!(!errors.is_empty(), "@fetch https should error, got: {:?}", result.diagnostics);
4685+ assert!(
4686+ !errors.is_empty(),
4687+ "@fetch https should error, got: {:?}",
4688+ result.diagnostics
4689+ );
38814690}
38824691
38834692#[test]
38844693fn fetch_directive_error_on_bad_url() {
38854694 let result = htmlang::parser::parse("@fetch $data http://127.0.0.1:1/nonexistent");
3886− let errors: Vec<_> = result.diagnostics.iter()
4695+ let errors: Vec<_> = result
4696+ .diagnostics
4697+ .iter()
38874698 .filter(|d| d.severity == htmlang::parser::Severity::Error)
38884699 .collect();
3889− assert!(!errors.is_empty(), "@fetch should error on unreachable URL, got: {:?}", result.diagnostics);
4700+ assert!(
4701+ !errors.is_empty(),
4702+ "@fetch should error on unreachable URL, got: {:?}",
4703+ result.diagnostics
4704+ );
38904705}
38914706
38924707#[test]
@@ -3899,11 +4714,25 @@ fn svg_directive_inline() {
38994714
39004715 let input = format!("@svg {}", svg_path.display());
39014716 let result = htmlang::parser::parse(&input);
3902− assert!(result.diagnostics.iter().all(|d| d.severity != htmlang::parser::Severity::Error),
3903− "should parse without errors: {:?}", result.diagnostics);
4717+ assert!(
4718+ result
4719+ .diagnostics
4720+ .iter()
4721+ .all(|d| d.severity != htmlang::parser::Severity::Error),
4722+ "should parse without errors: {:?}",
4723+ result.diagnostics
4724+ );
39044725 let html = htmlang::codegen::generate(&result.document);
3905− assert!(html.contains("<svg"), "should inline SVG content, got: {}", html);
3906− assert!(html.contains("<circle"), "should contain SVG elements, got: {}", html);
4726+ assert!(
4727+ html.contains("<svg"),
4728+ "should inline SVG content, got: {}",
4729+ html
4730+ );
4731+ assert!(
4732+ html.contains("<circle"),
4733+ "should contain SVG elements, got: {}",
4734+ html
4735+ );
39074736
39084737 let _ = std::fs::remove_dir_all(&dir);
39094738}
@@ -3913,13 +4742,25 @@ fn svg_directive_with_attrs() {
39134742 let dir = std::env::temp_dir().join("htmlang_test_svg_attrs");
39144743 let _ = std::fs::create_dir_all(&dir);
39154744 let svg_path = dir.join("icon.svg");
3916− std::fs::write(&svg_path, r#"<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><rect/></svg>"#).unwrap();
4745+ std::fs::write(
4746+ &svg_path,
4747+ r#"<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><rect/></svg>"#,
4748+ )
4749+ .unwrap();
39174750
39184751 let input = format!("@svg [width 24, color red] {}", svg_path.display());
39194752 let result = htmlang::parser::parse(&input);
39204753 let html = htmlang::codegen::generate(&result.document);
3921− assert!(html.contains("width=\"24\""), "should override width, got: {}", html);
3922− assert!(html.contains("fill=\"red\""), "should set fill from color attr, got: {}", html);
4754+ assert!(
4755+ html.contains("width=\"24\""),
4756+ "should override width, got: {}",
4757+ html
4758+ );
4759+ assert!(
4760+ html.contains("fill=\"red\""),
4761+ "should set fill from color attr, got: {}",
4762+ html
4763+ );
39234764
39244765 let _ = std::fs::remove_dir_all(&dir);
39254766}
@@ -3927,42 +4768,99 @@ fn svg_directive_with_attrs() {
39274768#[test]
39284769fn svg_directive_missing_file() {
39294770 let result = htmlang::parser::parse("@svg /nonexistent/missing.svg");
3930− let errors: Vec<_> = result.diagnostics.iter()
3931− .filter(|d| d.severity == htmlang::parser::Severity::Error && d.message.contains("cannot load SVG"))
4771+ let errors: Vec<_> = result
4772+ .diagnostics
4773+ .iter()
4774+ .filter(|d| {
4775+ d.severity == htmlang::parser::Severity::Error && d.message.contains("cannot load SVG")
4776+ })
39324777 .collect();
3933− assert!(!errors.is_empty(), "should error on missing SVG, got: {:?}", result.diagnostics);
4778+ assert!(
4779+ !errors.is_empty(),
4780+ "should error on missing SVG, got: {:?}",
4781+ result.diagnostics
4782+ );
39344783}
39354784
39364785#[test]
39374786fn css_property_directive() {
39384787 let input = "@css-property --my-color\n syntax \"<color>\"\n inherits true\n initial-value #000\n\n@el [background var(--my-color)] Content";
39394788 let result = htmlang::parser::parse(input);
3940− assert!(result.diagnostics.iter().all(|d| d.severity != htmlang::parser::Severity::Error),
3941− "should parse without errors: {:?}", result.diagnostics);
4789+ assert!(
4790+ result
4791+ .diagnostics
4792+ .iter()
4793+ .all(|d| d.severity != htmlang::parser::Severity::Error),
4794+ "should parse without errors: {:?}",
4795+ result.diagnostics
4796+ );
39424797 let html = htmlang::codegen::generate(&result.document);
3943− assert!(html.contains("@property --my-color"), "@css-property should emit CSS @property rule, got: {}", html);
3944− assert!(html.contains("syntax:\"<color>\""), "should include syntax, got: {}", html);
3945− assert!(html.contains("inherits:true"), "should include inherits, got: {}", html);
3946− assert!(html.contains("initial-value:#000"), "should include initial-value, got: {}", html);
4798+ assert!(
4799+ html.contains("@property --my-color"),
4800+ "@css-property should emit CSS @property rule, got: {}",
4801+ html
4802+ );
4803+ assert!(
4804+ html.contains("syntax:\"<color>\""),
4805+ "should include syntax, got: {}",
4806+ html
4807+ );
4808+ assert!(
4809+ html.contains("inherits:true"),
4810+ "should include inherits, got: {}",
4811+ html
4812+ );
4813+ assert!(
4814+ html.contains("initial-value:#000"),
4815+ "should include initial-value, got: {}",
4816+ html
4817+ );
39474818}
39484819
39494820#[test]
39504821fn partial_output() {
39514822 let result = htmlang::parser::parse("@page Test\n@el [padding 20]\n Hello");
39524823 let html = htmlang::codegen::generate_partial(&result.document);
3953− assert!(!html.contains("<!DOCTYPE"), "partial should not have doctype, got: {}", html);
3954− assert!(!html.contains("<html"), "partial should not have html tag, got: {}", html);
3955− assert!(!html.contains("<head"), "partial should not have head tag, got: {}", html);
3956− assert!(!html.contains("<body"), "partial should not have body tag, got: {}", html);
3957− assert!(html.contains("<style>"), "partial should still have CSS, got: {}", html);
3958− assert!(html.contains("Hello"), "partial should have content, got: {}", html);
4824+ assert!(
4825+ !html.contains("<!DOCTYPE"),
4826+ "partial should not have doctype, got: {}",
4827+ html
4828+ );
4829+ assert!(
4830+ !html.contains("<html"),
4831+ "partial should not have html tag, got: {}",
4832+ html
4833+ );
4834+ assert!(
4835+ !html.contains("<head"),
4836+ "partial should not have head tag, got: {}",
4837+ html
4838+ );
4839+ assert!(
4840+ !html.contains("<body"),
4841+ "partial should not have body tag, got: {}",
4842+ html
4843+ );
4844+ assert!(
4845+ html.contains("<style>"),
4846+ "partial should still have CSS, got: {}",
4847+ html
4848+ );
4849+ assert!(
4850+ html.contains("Hello"),
4851+ "partial should have content, got: {}",
4852+ html
4853+ );
39594854}
39604855
39614856#[test]
39624857fn partial_output_dev() {
39634858 let result = htmlang::parser::parse("@page Test\n@el [padding 20]\n Hello");
39644859 let html = htmlang::codegen::generate_partial_dev(&result.document);
3965− assert!(!html.contains("<!DOCTYPE"), "partial dev should not have doctype");
4860+ assert!(
4861+ !html.contains("<!DOCTYPE"),
4862+ "partial dev should not have doctype"
4863+ );
39664864 assert!(html.contains("<style>"), "partial dev should have style");
39674865 assert!(html.contains("Hello"), "partial dev should have content");
39684866}
@@ -3970,11 +4868,31 @@ fn partial_output_dev() {
39704868#[test]
39714869fn responsive_srcset_on_image() {
39724870 let output = compile("@image [responsive 400 800 1200, alt Photo] photo.jpg");
3973− assert!(output.contains("srcset=\""), "should generate srcset, got: {}", output);
3974− assert!(output.contains("photo-400.jpg 400w"), "should have 400w source, got: {}", output);
3975− assert!(output.contains("photo-800.jpg 800w"), "should have 800w source, got: {}", output);
3976− assert!(output.contains("photo-1200.jpg 1200w"), "should have 1200w source, got: {}", output);
3977− assert!(output.contains("sizes="), "should generate sizes attribute, got: {}", output);
4871+ assert!(
4872+ output.contains("srcset=\""),
4873+ "should generate srcset, got: {}",
4874+ output
4875+ );
4876+ assert!(
4877+ output.contains("photo-400.jpg 400w"),
4878+ "should have 400w source, got: {}",
4879+ output
4880+ );
4881+ assert!(
4882+ output.contains("photo-800.jpg 800w"),
4883+ "should have 800w source, got: {}",
4884+ output
4885+ );
4886+ assert!(
4887+ output.contains("photo-1200.jpg 1200w"),
4888+ "should have 1200w source, got: {}",
4889+ output
4890+ );
4891+ assert!(
4892+ output.contains("sizes="),
4893+ "should generate sizes attribute, got: {}",
4894+ output
4895+ );
39784896}
39794897
39804898#[test]
@@ -3994,8 +4912,8 @@ fn auto_image_dimensions_png() {
39944912 0x90, 0x77, 0x53, 0xDE, // CRC
39954913 0x00, 0x00, 0x00, 0x0C, // IDAT length
39964914 0x49, 0x44, 0x41, 0x54, // IDAT
3997− 0x08, 0xD7, 0x63, 0xF8, 0xCF, 0xC0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01,
3998− 0xE2, 0x21, 0xBC, 0x33, // CRC
4915+ 0x08, 0xD7, 0x63, 0xF8, 0xCF, 0xC0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0xE2, 0x21, 0xBC,
4916+ 0x33, // CRC
39994917 0x00, 0x00, 0x00, 0x00, // IEND length
40004918 0x49, 0x45, 0x4E, 0x44, // IEND
40014919 0xAE, 0x42, 0x60, 0x82, // CRC
@@ -4005,8 +4923,16 @@ fn auto_image_dimensions_png() {
40054923 let input = format!("@image [alt test] {}", png_path.display());
40064924 let result = htmlang::parser::parse(&input);
40074925 let html = htmlang::codegen::generate(&result.document);
4008− assert!(html.contains("width=\"1\""), "should auto-detect width=1 from PNG, got: {}", html);
4009− assert!(html.contains("height=\"1\""), "should auto-detect height=1 from PNG, got: {}", html);
4926+ assert!(
4927+ html.contains("width=\"1\""),
4928+ "should auto-detect width=1 from PNG, got: {}",
4929+ html
4930+ );
4931+ assert!(
4932+ html.contains("height=\"1\""),
4933+ "should auto-detect height=1 from PNG, got: {}",
4934+ html
4935+ );
40104936
40114937 let _ = std::fs::remove_dir_all(&dir);
40124938}
@@ -4016,7 +4942,11 @@ fn auto_image_dimensions_not_for_urls() {
40164942 // Remote URLs should not trigger dimension detection
40174943 let output = compile("@image [alt test] https://example.com/photo.png");
40184944 // Should not crash or add dimensions for remote URLs
4019− assert!(output.contains("src=\"https://example.com/photo.png\""), "should keep URL src, got: {}", output);
4945+ assert!(
4946+ output.contains("src=\"https://example.com/photo.png\""),
4947+ "should keep URL src, got: {}",
4948+ output
4949+ );
40204950}
40214951
40224952#[test]
@@ -4026,30 +4956,36 @@ fn auto_image_dimensions_respects_explicit() {
40264956 let _ = std::fs::create_dir_all(&dir);
40274957 let png_path = dir.join("test2.png");
40284958 let png_data: Vec<u8> = vec![
4029− 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
4030− 0x00, 0x00, 0x00, 0x0D,
4031− 0x49, 0x48, 0x44, 0x52,
4032− 0x00, 0x00, 0x00, 0x01,
4033− 0x00, 0x00, 0x00, 0x01,
4034− 0x08, 0x02, 0x00, 0x00, 0x00,
4035− 0x90, 0x77, 0x53, 0xDE,
4036− 0x00, 0x00, 0x00, 0x0C,
4037− 0x49, 0x44, 0x41, 0x54,
4038− 0x08, 0xD7, 0x63, 0xF8, 0xCF, 0xC0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01,
4039− 0xE2, 0x21, 0xBC, 0x33,
4040− 0x00, 0x00, 0x00, 0x00,
4041− 0x49, 0x45, 0x4E, 0x44,
4042− 0xAE, 0x42, 0x60, 0x82,
4959+ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44,
4960+ 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90,
4961+ 0x77, 0x53, 0xDE, 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x08, 0xD7, 0x63, 0xF8,
4962+ 0xCF, 0xC0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0xE2, 0x21, 0xBC, 0x33, 0x00, 0x00, 0x00,
4963+ 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
40434964 ];
40444965 std::fs::write(&png_path, &png_data).unwrap();
40454966
4046− let input = format!("@image [width 100, height 100, alt test] {}", png_path.display());
4967+ let input = format!(
4968+ "@image [width 100, height 100, alt test] {}",
4969+ png_path.display()
4970+ );
40474971 let result = htmlang::parser::parse(&input);
40484972 let html = htmlang::codegen::generate(&result.document);
40494973 // When width/height are set as CSS attrs, auto-dimensions should not add HTML width/height
4050− assert!(html.contains("width:100px"), "should have CSS width, got: {}", html);
4051− assert!(html.contains("height:100px"), "should have CSS height, got: {}", html);
4052− assert!(!html.contains("width=\"1\""), "should NOT auto-detect dimensions when CSS size is set, got: {}", html);
4974+ assert!(
4975+ html.contains("width:100px"),
4976+ "should have CSS width, got: {}",
4977+ html
4978+ );
4979+ assert!(
4980+ html.contains("height:100px"),
4981+ "should have CSS height, got: {}",
4982+ html
4983+ );
4984+ assert!(
4985+ !html.contains("width=\"1\""),
4986+ "should NOT auto-detect dimensions when CSS size is set, got: {}",
4987+ html
4988+ );
40534989
40544990 let _ = std::fs::remove_dir_all(&dir);
40554991}
@@ -4101,7 +5037,11 @@ fn error_circular_include_filesystem() {
41015037 .iter()
41025038 .any(|d| d.severity == htmlang::parser::Severity::Error && d.message.contains("circular"));
41035039 let _ = std::fs::remove_dir_all(&dir);
4104− assert!(has_cycle, "expected circular include error, got: {:?}", result.diagnostics);
5040+ assert!(
5041+ has_cycle,
5042+ "expected circular include error, got: {:?}",
5043+ result.diagnostics
5044+ );
41055045}
41065046
41075047#[test]
@@ -4119,10 +5059,18 @@ fn import_with_alias_prefixes_definitions() {
41195059 let _ = std::fs::remove_dir_all(&dir);
41205060
41215061 assert!(
4122− result.diagnostics.iter().all(|d| d.severity != htmlang::parser::Severity::Error),
4123− "no errors expected, got: {:?}", result.diagnostics
5062+ result
5063+ .diagnostics
5064+ .iter()
5065+ .all(|d| d.severity != htmlang::parser::Severity::Error),
5066+ "no errors expected, got: {:?}",
5067+ result.diagnostics
5068+ );
5069+ assert!(
5070+ html.contains("card-body"),
5071+ "alias prefixed call should expand, got: {}",
5072+ html
41245073 );
4125− assert!(html.contains("card-body"), "alias prefixed call should expand, got: {}", html);
41265074}
41275075
41285076#[test]
@@ -4135,10 +5083,13 @@ fn error_invalid_json_in_data_directive() {
41355083
41365084 let input = "@data bad.json\n";
41375085 let result = htmlang::parser::parse_with_base(input, Some(&dir));
4138− let has_err = result
4139− .diagnostics
4140− .iter()
4141− .any(|d| d.severity == htmlang::parser::Severity::Error && d.message.contains("invalid JSON"));
5086+ let has_err = result.diagnostics.iter().any(|d| {
5087+ d.severity == htmlang::parser::Severity::Error && d.message.contains("invalid JSON")
5088+ });
41425089 let _ = std::fs::remove_dir_all(&dir);
4143− assert!(has_err, "expected invalid JSON error, got: {:?}", result.diagnostics);
5090+ assert!(
5091+ has_err,
5092+ "expected invalid JSON error, got: {:?}",
5093+ result.diagnostics
5094+ );
41445095}