tests.rs raw

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");
}