material-ui-rs 0.5.3

A Material3 inspired theme for `iced`
Documentation
use super::*;

fn assert_close(actual: f32, expected: f32) {
    assert!(
        (actual - expected).abs() < 0.001,
        "actual={actual} expected={expected}"
    );
}

#[test]
fn indeterminate_keyframes_match_material3_head_tail_timing() {
    let start = indeterminate_bars(0.0);
    assert_close(start[0].head, 0.0);
    assert_close(start[0].tail, 0.0);
    assert_close(start[1].head, 0.0);
    assert_close(start[1].tail, 0.0);

    let first_head_done = indeterminate_bars(1000.0 / 1750.0);
    assert_close(first_head_done[0].head, 1.0);
    assert!(first_head_done[0].tail > 0.0);

    let second_head_started = indeterminate_bars(700.0 / 1750.0);
    assert!(second_head_started[1].head > 0.0);
    assert_close(second_head_started[1].tail, 0.0);
}

#[test]
fn determinate_wavy_amplitude_flattens_near_edges() {
    assert_eq!(determinate_wave_amplitude(0.0), 0.0);
    assert_eq!(determinate_wave_amplitude(0.1), 0.0);
    assert_eq!(determinate_wave_amplitude(0.5), 1.0);
    assert_eq!(determinate_wave_amplitude(0.95), 0.0);
}

#[test]
fn four_color_indicator_uses_material_color_windows() {
    let primary = Color::from_rgb(1.0, 0.0, 0.0);
    let primary_container = Color::from_rgb(0.0, 1.0, 0.0);
    let tertiary = Color::from_rgb(0.0, 0.0, 1.0);
    let tertiary_container = Color::from_rgb(1.0, 1.0, 0.0);

    assert_eq!(
        four_color_indicator(
            primary,
            primary_container,
            tertiary,
            tertiary_container,
            0.10
        ),
        primary
    );
    assert_eq!(
        four_color_indicator(
            primary,
            primary_container,
            tertiary,
            tertiary_container,
            0.30
        ),
        primary_container
    );
    assert_eq!(
        four_color_indicator(
            primary,
            primary_container,
            tertiary,
            tertiary_container,
            0.55
        ),
        tertiary
    );
    assert_eq!(
        four_color_indicator(
            primary,
            primary_container,
            tertiary,
            tertiary_container,
            0.80
        ),
        tertiary_container
    );
}

#[test]
fn loading_indicator_uses_androidx_material_shape_sequence() {
    let polygons = indeterminate_loading_polygons();

    assert_eq!(
        polygons.len(),
        tokens::component::loading_indicator::INDETERMINATE_SHAPE_COUNT
    );
    assert_eq!(
        polygons.iter().map(corner_count).collect::<Vec<_>>(),
        vec![20, 18, 5, 10, 16, 8, 8]
    );
    assert!(polygons.iter().all(|polygon| !polygon.cubics.is_empty()));
}

#[test]
fn cookie4_uses_androidx_material_shape_definition() {
    let cookie = material_cookie4();
    let repeated = repeat_material_vertices(
        &[
            ShapeVertex::new(1.237, 1.236, CornerRounding::new(0.258)),
            ShapeVertex::new(0.500, 0.918, CornerRounding::new(0.233)),
        ],
        4,
        Point::new(0.5, 0.5),
        false,
    );

    let corners: Vec<_> = cookie
        .features
        .iter()
        .filter(|feature| feature.is_corner())
        .collect();

    assert_eq!(repeated.len(), 8);
    assert_eq!(corners.len(), 8);
    assert!(corners.iter().all(|feature| matches!(
        feature,
        Feature::Corner { cubics, .. } if !cubics.is_empty()
    )));
}

#[test]
fn loading_shape_sequence_wraps_after_seven_shapes() {
    let polygons = indeterminate_loading_polygons();
    let morphs = morph_sequence(&polygons, true);
    let repeated = Morph::new(
        polygons[polygons.len() - 1].normalized(),
        polygons[0].normalized(),
    );

    assert_eq!(morphs.len(), polygons.len());
    assert_eq!(morphs[morphs.len() - 1].pairs, repeated.pairs);
}

#[test]
fn determinate_loading_shape_sequence_uses_circle_to_soft_burst() {
    let polygons = determinate_loading_polygons();

    assert_eq!(polygons.len(), 2);
    assert_eq!(corner_count(&polygons[0]), 10);
    assert_eq!(polygons[1], material_soft_burst());
}

#[test]
fn loading_morphs_use_androidx_feature_mapping() {
    let polygons = indeterminate_loading_polygons();
    let morphs = morph_sequence(&polygons, true);

    for (index, morph) in morphs.iter().enumerate() {
        let from = polygons[index].normalized();
        let to = polygons[(index + 1) % polygons.len()].normalized();

        assert!(!morph.pairs.is_empty());
        assert!(
            morph.pairs.len() >= from.cubics.len().max(to.cubics.len()),
            "morph {index} lost cubic segments"
        );
    }
}

#[test]
fn loading_morphs_are_centered_after_androidx_path_processing() {
    let polygons = indeterminate_loading_polygons();
    let morphs = morph_sequence(&polygons, true);
    let scale = loading_shape_scale(&polygons);
    let target = Point::new(40.0, 72.0);

    for (index, morph) in morphs.iter().enumerate() {
        for progress in [0.0, 0.125, 0.25, 0.5, 0.75, 0.875, 1.0] {
            let cubics = morph.as_cubics(progress);
            let processed = processed_cubics(&cubics, target, 100.0, scale, 0.0);
            let center = bounds_center(cubics_bounds(&processed, false));

            assert_point_close(center, target, index, progress);
        }
    }
}

#[test]
fn loading_morph_spring_reaches_target_before_interval_end() {
    assert_close(loading_spring_progress(0.0), 0.0);
    assert!(loading_spring_progress(0.5) > 0.8);
    assert!(loading_spring_progress(1.0) > 0.99);
}

#[test]
fn loading_shape_scale_accounts_for_rotation_bounds() {
    let polygons = indeterminate_loading_polygons();
    let scale = loading_shape_scale(&polygons);

    assert!(scale > 0.0);
    assert!(scale < tokens::component::loading_indicator::ACTIVE_INDICATOR_SCALE);
}

#[test]
fn degenerate_polygon_normalization_does_not_emit_nan() {
    let polygon = RoundedPolygon::from_vertices(
        &[Point::ORIGIN, Point::ORIGIN, Point::ORIGIN],
        &[CornerRounding::UNROUNDED; 3],
        None,
    );

    let normalized = polygon.normalized();

    assert!(normalized.cubics.iter().all(|cubic| {
        [
            cubic.anchor0_x(),
            cubic.anchor0_y(),
            cubic.control0_x(),
            cubic.control0_y(),
            cubic.control1_x(),
            cubic.control1_y(),
            cubic.anchor1_x(),
            cubic.anchor1_y(),
        ]
        .into_iter()
        .all(f32::is_finite)
    }));
}

fn corner_count(polygon: &RoundedPolygon) -> usize {
    polygon
        .features
        .iter()
        .filter(|feature| feature.is_corner())
        .count()
}

fn assert_point_close(actual: Point, expected: Point, morph_index: usize, progress: f32) {
    assert!(
        (actual.x - expected.x).abs() < 0.001 && (actual.y - expected.y).abs() < 0.001,
        "morph {morph_index} progress {progress} is off-center: actual={actual:?} expected={expected:?}"
    );
}