use super::*; use crate::geom::{Affine, Point, Rect, Stroke}; use crate::paint::color::palette::css; use crate::paint::{Brush, Color}; fn sample_fragment() -> Fragment { let mut fragment = Fragment::new(); fragment.fill(Rect::new(0.0, 0.0, 100.0, 50.0), css::REBECCA_PURPLE); fragment.stroke( Rect::new(10.0, 10.0, 90.0, 40.0), css::WHITE, Stroke::new(2.0), ); fragment } #[test] fn structural_equality() { assert_eq!(sample_fragment(), sample_fragment()); let mut other = sample_fragment(); other.fill(Rect::new(0.0, 0.0, 1.0, 1.0), css::BLACK); assert_ne!(sample_fragment(), other); } #[test] fn brush_conversion_from_color() { let mut fragment = Fragment::new(); fragment.fill(Rect::new(0.0, 0.0, 1.0, 1.0), Color::from_rgb8(1, 2, 3)); let DisplayItem::Fill { brush, .. } = &fragment.items[0] else { panic!("expected fill"); }; assert_eq!(*brush, Brush::Solid(Color::from_rgb8(1, 2, 3))); } #[test] fn balanced_stack_items() { let mut fragment = Fragment::new(); assert!(fragment.is_balanced()); fragment.push_clip(Rect::new(0.0, 0.0, 10.0, 10.0)); assert!(!fragment.is_balanced()); fragment.push_transform(Affine::translate((5.0, 5.0))); fragment.fill(Rect::new(0.0, 0.0, 1.0, 1.0), css::RED); fragment.pop_transform(); fragment.pop_clip(); assert!(fragment.is_balanced()); } #[test] fn mismatched_pop_kind_is_unbalanced() { let mut fragment = Fragment::new(); fragment.push_clip(Rect::new(0.0, 0.0, 10.0, 10.0)); fragment.pop_transform(); assert!(!fragment.is_balanced()); } #[test] fn interleaved_pops_are_unbalanced() { let mut fragment = Fragment::new(); fragment.push_clip(Rect::new(0.0, 0.0, 10.0, 10.0)); fragment.push_transform(Affine::IDENTITY); fragment.pop_clip(); fragment.pop_transform(); assert!(!fragment.is_balanced()); } #[test] fn store_create_link_remove() { let mut store = FragmentStore::new(); let child = store.insert(sample_fragment()); let root = store.create(); store .get_mut(root) .unwrap() .child(Affine::translate((20.0, 30.0)), child); let DisplayItem::Child { transform, fragment, } = &store.get(root).unwrap().items[0] else { panic!("expected child item"); }; assert_eq!(*transform * Point::ORIGIN, Point::new(20.0, 30.0)); assert_eq!(store.get(*fragment), Some(&sample_fragment())); store.remove(child); assert_eq!(store.get(child), None); } #[test] fn child_inherits_open_scopes() { // A scroll-area-shaped fragment: clip, offset, child, pop, pop — the child // sits inside both open scopes and the fragment is still balanced. let mut store = FragmentStore::new(); let content = store.insert(sample_fragment()); let mut scroller = Fragment::new(); scroller.push_clip(Rect::new(0.0, 0.0, 100.0, 100.0)); scroller.push_transform(Affine::translate((0.0, -25.0))); scroller.child(Affine::IDENTITY, content); scroller.pop_transform(); scroller.pop_clip(); assert!(scroller.is_balanced()); } #[test] fn shape_to_path_preserves_bounds() { let rect = Rect::new(0.0, 0.0, 40.0, 20.0); let shape = Shape::from(rect.to_rounded_rect(4.0)); assert_eq!(shape.bounding_box(), rect); let path = shape.to_path(0.1); let bounds = path.bounding_box(); assert!((bounds.x0 - rect.x0).abs() < 0.5 && (bounds.x1 - rect.x1).abs() < 0.5); } #[test] fn epochs_advance_on_mutation_and_vanish_on_removal() { let mut store = FragmentStore::new(); let a = store.insert(sample_fragment()); let b = store.create(); let epoch_a = store.epoch(a).expect("a exists"); let epoch_b = store.epoch(b).expect("b exists"); assert_ne!(epoch_a, epoch_b, "every stamp is unique"); // Immutable access does not advance the epoch. let _ = store.get(a); assert_eq!(store.epoch(a), Some(epoch_a)); // Mutable access advances it, even without a write. let _ = store.get_mut(a); let bumped = store.epoch(a).expect("a still exists"); assert!(bumped > epoch_a); assert_eq!(store.epoch(b), Some(epoch_b), "b is untouched"); store.remove(a); assert_eq!(store.epoch(a), None, "removed fragments have no epoch"); } #[test] fn store_ids_are_unique() { let a = FragmentStore::new(); let b = FragmentStore::new(); assert_ne!(a.store_id(), b.store_id()); } #[test] fn mutation_counter_advances_on_every_kind_of_change() { let mut store = FragmentStore::new(); let start = store.mutation_counter(); let a = store.insert(sample_fragment()); assert!(store.mutation_counter() > start, "insert counts"); let after_insert = store.mutation_counter(); let _ = store.get(a); assert_eq!(store.mutation_counter(), after_insert, "reads do not count"); let _ = store.get_mut(a); assert!(store.mutation_counter() > after_insert, "get_mut counts"); let after_mut = store.mutation_counter(); store.remove(a); assert!(store.mutation_counter() > after_mut, "remove counts"); }