//! Tests for the parity harness. //! //! The harness is what every phase-3 conversion trusts, so it is tested on both //! sides: that it ignores what it claims to ignore, and that it still fails on //! everything else. A parity harness that quietly passes is worse than none, //! because the conversion's whole safety argument is that this file said yes. // Just the one module, not `mod harness;`. These are unit tests for the // normalizer and the switch: neither serves a request, so pulling the database, // storage and Stripe harnesses in would cost a much slower build for nothing. // The conversions' own parity tests go through the full harness. #[path = "harness/parity.rs"] mod parity; use parity::Parity; // --- what it forgives ------------------------------------------------------- #[test] fn attribute_order_is_not_a_difference() { Parity::strict().assert( "t", r#"
"#, r#"
"#, ); } #[test] fn class_order_is_not_a_difference() { // A description emits classes in whatever order it composes them; a // template emits them in the order somebody typed. Parity::strict().assert( "t", r#"
"#, r#"
"#, ); } #[test] fn whitespace_between_tags_collapses() { Parity::strict().assert( "t", "", "", ); } #[test] fn runs_of_whitespace_inside_text_collapse_to_one_space() { Parity::strict().assert("t", "

one two\n\tthree

", "

one two three

"); } #[test] fn comments_are_not_a_difference() { Parity::strict().assert( "t", "
x
", "
x
", ); } // --- what it still catches -------------------------------------------------- #[test] #[should_panic(expected = "does not render the same page")] fn a_changed_attribute_value_fails() { Parity::strict().assert("t", r#"x"#, r#"x"#); } #[test] #[should_panic(expected = "does not render the same page")] fn a_dropped_class_fails() { Parity::strict().assert( "t", r#"
"#, r#"
"#, ); } #[test] #[should_panic(expected = "does not render the same page")] fn changed_text_fails() { Parity::strict().assert("t", "

Saved.

", "

Saved

"); } #[test] #[should_panic(expected = "does not render the same page")] fn different_nesting_fails() { // The case dropping end tags would have missed: same tags, same text, and // the region closes in the wrong place. Parity::strict().assert( "t", "

x

", "

x

", ); } #[test] #[should_panic(expected = "does not render the same page")] fn whitespace_inside_pre_is_content_and_still_compares() { Parity::strict().assert("t", "
one   two
", "
one two
"); } #[test] #[should_panic(expected = "does not render the same page")] fn a_missing_doctype_fails() { // Not pedantry: it is the difference between standards and quirks mode. Parity::strict().assert("t", "", ""); } // --- the allowlist ---------------------------------------------------------- #[test] fn a_named_attribute_can_be_forgiven() { // Retiring the private data-action vocabulary is progress, and it is named // per screen rather than ignored everywhere. Parity::strict().ignoring_attr("data-action").assert( "t", r#""#, r#""#, ); } #[test] #[should_panic(expected = "does not render the same page")] fn forgiving_one_attribute_does_not_forgive_its_neighbours() { Parity::strict().ignoring_attr("data-action").assert( "t", r#""#, r#""#, ); } #[test] fn a_named_class_can_be_forgiven() { Parity::strict().ignoring_class("raised").assert( "t", r#"
"#, r#"
"#, ); } #[test] fn a_class_attribute_emptied_by_the_allowlist_matches_having_none() { Parity::strict().ignoring_class("raised").assert( "t", "
", r#"
"#, ); } // --- the failure message ---------------------------------------------------- #[test] fn the_failure_names_the_screen_and_shows_both_sides() { let err = std::panic::catch_unwind(|| { Parity::strict().assert("item_pricing", "

a

", "

b

"); }) .expect_err("it fails"); let msg = err .downcast_ref::() .expect("the panic carries a message"); assert!(msg.contains("item_pricing"), "names the screen: {msg}"); assert!(msg.contains("Askama"), "shows the Askama side: {msg}"); assert!(msg.contains("quasi"), "shows the quasi side: {msg}"); assert!( msg.contains("ignoring_attr"), "says what to do about an intended difference: {msg}" ); } // --- the per-screen switch -------------------------------------------------- // // Six tests lived here until 2026-08-26, covering `QuasiScreens::parse`: the // default converting nothing, an unset variable, naming screens one at a time, // whitespace and blanks, `*` for local work, and a typo disabling a screen // rather than enabling another. `64b33b26` deleted the switch, so they have no // subject. // // The normalizer above is untouched and is the half worth keeping: a phase-3 // conversion still wants to compare its described rendering against the Askama // one it replaces. What it cannot do any more is serve both from one binary and // diff them over a request, because there is no flag to serve the old one.