use pagetop::prelude::*; fn assert_classes(c: &Classes, expected: Option<&str>) { let got = c.get(); assert_eq!( got.as_deref(), expected, "Expected {:?}, got {:?}", expected, got ); } // **< Construction & invariants (new/get) >******************************************************** #[pagetop::test] async fn classes_new_empty_and_whitespace_is_empty() { assert_classes(&Classes::new(""), None); assert_classes(&Classes::new(" "), None); assert_classes(&Classes::new("\t\n\r "), None); } #[pagetop::test] async fn classes_new_normalizes_and_dedups_and_preserves_first_occurrence_order() { let c = Classes::new("Btn btn BTN btn-primary BTN-PRIMARY"); assert_classes(&c, Some("btn btn-primary")); assert!(c.contains("BTN")); assert!(c.contains("btn-primary")); } #[pagetop::test] async fn classes_get_returns_none_when_empty_some_when_not() { assert_classes(&Classes::new(" "), None); assert_classes(&Classes::new("a"), Some("a")); } // **< Basic operations (add/prepend/set) >********************************************************* #[pagetop::test] async fn classes_add_appends_unique_and_normalizes() { let c = Classes::new("a b").with_classes(ClassesOp::Add, "C b D"); assert_classes(&c, Some("a b c d")); } #[pagetop::test] async fn classes_add_ignores_empty_input() { let c = Classes::new("a b").with_classes(ClassesOp::Add, " \t"); assert_classes(&c, Some("a b")); } #[pagetop::test] async fn classes_add_same_tokens() { let c = Classes::new("a b").with_classes(ClassesOp::Add, "A B a b"); assert_classes(&c, Some("a b")); } #[pagetop::test] async fn classes_add_rejects_non_ascii_is_noop() { let c = Classes::new("a b").with_classes(ClassesOp::Add, "c ñ d"); assert_classes(&c, Some("a b")); } #[pagetop::test] async fn classes_prepend_inserts_at_front_preserving_new_order() { let c = Classes::new("c d").with_classes(ClassesOp::Prepend, "A b"); assert_classes(&c, Some("a b c d")); } #[pagetop::test] async fn classes_prepend_inserts_new_tokens_skipping_duplicates() { let c = Classes::new("b c").with_classes(ClassesOp::Prepend, "a b d"); assert_classes(&c, Some("a d b c")); } #[pagetop::test] async fn classes_prepend_ignores_empty_input() { let c = Classes::new("a b").with_classes(ClassesOp::Prepend, ""); assert_classes(&c, Some("a b")); } #[pagetop::test] async fn classes_reset_replaces_entire_list_and_dedups() { let c = Classes::new("a b c").with_classes(ClassesOp::Reset, "X y y Z"); assert_classes(&c, Some("x y z")); } #[pagetop::test] async fn classes_reset_with_empty_input_clears() { let base = Classes::new("a b"); let c = base.with_classes(ClassesOp::Reset, " \n "); assert_classes(&c, None); } // **< Mutation operations (remove/toggle) >******************************************************** #[pagetop::test] async fn classes_remove_is_case_insensitive() { let c = Classes::new("a b c d").with_classes(ClassesOp::Remove, "B D"); assert_classes(&c, Some("a c")); } #[pagetop::test] async fn classes_remove_non_existing_is_noop() { let c = Classes::new("a b c").with_classes(ClassesOp::Remove, "x y z"); assert_classes(&c, Some("a b c")); } #[pagetop::test] async fn classes_remove_with_extra_whitespace() { let c = Classes::new("a b c d").with_classes(ClassesOp::Remove, " b\t\t \n d "); assert_classes(&c, Some("a c")); } #[pagetop::test] async fn classes_toggle_removes_if_present_case_insensitive() { let c = Classes::new("a b c").with_classes(ClassesOp::Toggle, "B"); assert_classes(&c, Some("a c")); } #[pagetop::test] async fn classes_toggle_adds_if_missing_and_normalizes() { let c = Classes::new("a b").with_classes(ClassesOp::Toggle, "C"); assert_classes(&c, Some("a b c")); } #[pagetop::test] async fn classes_toggle_multiple_tokens_is_sequential_and_order_dependent() { let c = Classes::new("a b").with_classes(ClassesOp::Toggle, "C B A"); assert_classes(&c, Some("c")); } #[pagetop::test] async fn classes_toggle_duplicate_tokens_are_applied_sequentially() { let c = Classes::new("b").with_classes(ClassesOp::Toggle, "a a"); assert_classes(&c, Some("b")); let c = Classes::new("a b").with_classes(ClassesOp::Toggle, "a a"); assert_classes(&c, Some("b a")); } // **< Queries (contains) >************************************************************************* #[pagetop::test] async fn classes_contains_single() { let c = Classes::new("btn btn-primary"); assert!(c.contains("btn")); assert!(c.contains("BTN")); assert!(!c.contains("missing")); } #[pagetop::test] async fn classes_contains_all_and_any() { let c = Classes::new("btn btn-primary active"); assert!(c.contains("btn active")); assert!(c.contains("BTN BTN-PRIMARY")); assert!(!c.contains("btn missing")); assert!(c.contains_any("missing active")); assert!(c.contains_any("BTN-PRIMARY missing")); assert!(!c.contains_any("missing other")); } #[pagetop::test] async fn classes_contains_empty_and_whitespace_is_false() { let c = Classes::new("a b"); assert!(!c.contains("")); assert!(!c.contains(" \t")); assert!(!c.contains_any("")); assert!(!c.contains_any(" \n ")); } #[pagetop::test] async fn classes_contains_non_ascii_is_false() { let c = Classes::new("a b"); assert!(!c.contains("ñ")); assert!(!c.contains_any("a ñ")); } // **< Properties / regression (combined sequences, ordering) >************************************* #[pagetop::test] async fn classes_order_is_stable_for_existing_items() { let c = Classes::new("a b c") .with_classes(ClassesOp::Add, "d") // a b c d .with_classes(ClassesOp::Prepend, "x") // x a b c d .with_classes(ClassesOp::Remove, "b") // x a c d .with_classes(ClassesOp::Add, "b"); // x a c d b assert_classes(&c, Some("x a c d b")); }