//! Tests for the parity harness, and for the per-screen conversion switch. //! //! 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 makenotwork::config::QuasiScreens; 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 -------------------------------------------------- #[test] fn no_screen_is_converted_by_default() { // The state every deployment is in until Max flips one. let screens = QuasiScreens::default(); assert!(!screens.enabled("item_pricing")); assert!(!screens.any()); } #[test] fn an_unset_variable_converts_nothing() { let screens = QuasiScreens::parse(""); assert!(!screens.enabled("item_pricing")); assert!(!screens.any()); } #[test] fn screens_are_named_one_at_a_time() { let screens = QuasiScreens::parse("user_account,item_pricing"); assert!(screens.enabled("user_account")); assert!(screens.enabled("item_pricing")); assert!(!screens.enabled("project_content")); assert!(screens.any()); } #[test] fn whitespace_and_blanks_in_the_list_are_ignored() { let screens = QuasiScreens::parse(" user_account , , item_pricing ,"); assert!(screens.enabled("user_account")); assert!(screens.enabled("item_pricing")); assert!(!screens.enabled("")); } #[test] fn a_star_converts_everything_for_local_work() { let screens = QuasiScreens::parse("*"); assert!(screens.enabled("anything_at_all")); assert!(screens.any()); } #[test] fn a_typo_disables_a_screen_rather_than_enabling_another() { // The safe direction, and the reason nothing validates against a registry. let screens = QuasiScreens::parse("item_pricng"); assert!(!screens.enabled("item_pricing")); }