euv-ui 0.28.17

Reusable UI component library for the euv framework, providing buttons, cards, modals, inputs, and more.
Documentation
use super::*;

fn badge_node(outline: bool, text: &'static str) -> VirtualNode<EuvBadgeProps> {
    VirtualNode::Element {
        tag: Tag::Element(Cow::Borrowed("div")),
        attributes: Vec::new(),
        children: Vec::new(),
        key: None,
        props: Some(Box::new(EuvBadgeProps {
            text,
            outline,
            on_click: None,
        })),
    }
}

fn tag_of(node: &VirtualNode) -> String {
    match node {
        VirtualNode::Element { tag, .. } => format!("{tag:?}"),
        _ => panic!("expected an element node"),
    }
}

#[test]
fn a_filled_badge_renders_its_label_as_a_text_child() {
    let rendered: VirtualNode = euv_badge(badge_node(false, "New"));
    assert!(
        matches!(rendered, VirtualNode::Element { .. }),
        "a badge is an element"
    );
    let children: &[VirtualNode] = rendered.get_children();
    assert_eq!(children.len(), 1, "the label is the one nested child");
    match &children[0] {
        VirtualNode::Text(text_node) => assert!(
            format!("{:?}", text_node).contains("New"),
            "the label text is carried through, got {text_node:?}"
        ),
        _ => panic!("the label must be a text node, got {:?}", children[0]),
    }
}

#[test]
fn an_outlined_badge_renders_the_same_shape_as_a_filled_one() {
    let filled: VirtualNode = euv_badge(badge_node(false, "New"));
    let outlined: VirtualNode = euv_badge(badge_node(true, "New"));
    assert_eq!(
        tag_of(&outlined),
        tag_of(&filled),
        "the outline variant only swaps the class list, not the element"
    );
}

#[test]
fn a_badge_without_props_falls_back_to_defaults() {
    let bare: VirtualNode<EuvBadgeProps> = VirtualNode::Element {
        tag: Tag::Element(Cow::Borrowed("div")),
        attributes: Vec::new(),
        children: Vec::new(),
        key: None,
        props: None,
    };
    let rendered: VirtualNode = euv_badge(bare);
    assert!(
        matches!(rendered, VirtualNode::Element { .. }),
        "defaults still render"
    );
}

#[test]
fn a_half_filled_rating_is_fifty_percent() {
    let observed: f64 = rating_fill_percent(2.5, 5.0);
    assert!(
        (observed - 50.0).abs() < 1e-9,
        "half of five is fifty percent, got {observed}"
    );
}

#[test]
fn a_full_rating_is_one_hundred_percent() {
    let observed: f64 = rating_fill_percent(5.0, 5.0);
    assert!(
        (observed - 100.0).abs() < 1e-9,
        "every star lit is one hundred percent, got {observed}"
    );
}

#[test]
fn an_empty_rating_is_zero_percent() {
    let observed: f64 = rating_fill_percent(0.0, 5.0);
    assert_eq!(observed, 0.0, "no stars lit is zero percent");
}

#[test]
fn the_fill_is_clamped_when_the_value_runs_past_the_maximum() {
    let observed: f64 = rating_fill_percent(9.0, 5.0);
    assert_eq!(
        observed, 100.0,
        "a value beyond the maximum must not overflow the bar"
    );
}

#[test]
fn the_fill_is_clamped_when_the_value_is_negative() {
    let observed: f64 = rating_fill_percent(-3.0, 5.0);
    assert_eq!(observed, 0.0, "a negative value must not underflow the bar");
}

#[test]
fn a_zero_maximum_reports_no_fill_rather_than_dividing_by_zero() {
    let observed: f64 = rating_fill_percent(3.0, 0.0);
    assert_eq!(
        observed, 0.0,
        "an unset maximum is a degenerate scale, not an infinite bar"
    );
}

#[test]
fn a_negative_maximum_reports_no_fill() {
    let observed: f64 = rating_fill_percent(3.0, -1.0);
    assert_eq!(observed, 0.0, "a negative scale is treated as degenerate");
}

#[test]
fn a_single_star_scale_reaches_one_hundred_percent() {
    let observed: f64 = rating_fill_percent(1.0, 1.0);
    assert!(
        (observed - 100.0).abs() < 1e-9,
        "a one-star scale fills at the first star, got {observed}"
    );
}

#[test]
fn a_ten_star_scale_halves_at_five() {
    let observed: f64 = rating_fill_percent(5.0, 10.0);
    assert!(
        (observed - 50.0).abs() < 1e-9,
        "five of ten is fifty percent, got {observed}"
    );
}

#[test]
fn a_progress_percent_inside_the_range_is_unchanged() {
    let observed: f64 = progress_percent_clamp(42.0);
    assert!(
        (observed - 42.0).abs() < 1e-9,
        "a value already in range passes through, got {observed}"
    );
}

#[test]
fn a_progress_percent_above_the_maximum_is_clamped() {
    let observed: f64 = progress_percent_clamp(150.0);
    assert_eq!(observed, 100.0, "a bar can never exceed full");
}

#[test]
fn a_progress_percent_below_zero_is_clamped() {
    let observed: f64 = progress_percent_clamp(-20.0);
    assert_eq!(observed, 0.0, "a bar can never run backwards");
}

#[test]
fn a_nan_progress_percent_becomes_zero() {
    let observed: f64 = progress_percent_clamp(f64::NAN);
    assert_eq!(
        observed, 0.0,
        "NaN fails every comparison, so it must be caught before the clamp \
         or it would pass through unchanged and poison the style string"
    );
}

#[test]
fn the_endpoints_of_the_progress_range_survive() {
    let at_zero: f64 = progress_percent_clamp(0.0);
    let at_full: f64 = progress_percent_clamp(100.0);
    assert_eq!(at_zero, 0.0, "the lower endpoint is inclusive");
    assert_eq!(at_full, 100.0, "and so is the upper one");
}

#[test]
fn an_infinite_progress_percent_is_clamped() {
    let positive: f64 = progress_percent_clamp(f64::INFINITY);
    let negative: f64 = progress_percent_clamp(f64::NEG_INFINITY);
    assert_eq!(positive, 100.0, "positive infinity saturates at full");
    assert_eq!(negative, 0.0, "negative infinity saturates at empty");
}

#[test]
fn a_skeleton_line_count_inside_the_range_is_unchanged() {
    let observed: usize = skeleton_line_count(3);
    assert_eq!(observed, 3, "a middling count passes through");
}

#[test]
fn a_skeleton_line_count_below_the_minimum_is_raised() {
    let observed: usize = skeleton_line_count(0);
    assert!(
        observed > 0,
        "a skeleton always draws at least one placeholder line, got {observed}"
    );
}

#[test]
fn a_skeleton_line_count_above_the_maximum_is_capped() {
    let observed: usize = skeleton_line_count(1000);
    assert!(
        observed < 1000,
        "an absurd line count must be capped so a tall skeleton does not \
         blow up the layout, got {observed}"
    );
}

#[test]
fn the_skeleton_line_count_is_monotonic() {
    let low: usize = skeleton_line_count(1);
    let high: usize = skeleton_line_count(2);
    assert!(
        high >= low,
        "asking for more lines must never produce fewer, got {low} then {high}"
    );
}