cranpose-ui 0.1.173

UI primitives for Cranpose
Documentation
use std::cell::Cell;

use cranpose_core::{Composition, MemoryApplier, MutableState, location_key};

use super::*;

#[test]
fn layout_recomposes_when_content_reads_state() {
    let _app_context = crate::render_state::app_context_test_scope();
    thread_local! {
        static INVOCATIONS: Cell<usize> = const { Cell::new(0) };
    }

    let mut composition = Composition::new(MemoryApplier::new());
    let runtime = composition.runtime_handle();
    let state = MutableState::with_runtime(0_i32, runtime);

    composition
        .render(location_key(file!(), line!(), column!()), {
            let observed_state = state;
            move || {
                Layout(
                    Modifier::empty(),
                    crate::layout::policies::EmptyMeasurePolicy,
                    {
                        let observed_state = observed_state;
                        move || {
                            let _ = observed_state.value();
                            INVOCATIONS.with(|calls| calls.set(calls.get() + 1));
                        }
                    },
                );
            }
        })
        .expect("initial layout render");

    INVOCATIONS.with(|calls| assert_eq!(calls.get(), 1));

    state.set_value(1);
    composition
        .process_invalid_scopes()
        .expect("layout content recomposition");

    INVOCATIONS.with(|calls| assert_eq!(calls.get(), 2));
}

#[test]
fn subcompose_missing_policy_cell_measures_empty_layout() {
    let result = empty_subcompose_measure_result(Constraints {
        min_width: 12.0,
        max_width: 120.0,
        min_height: 8.0,
        max_height: 90.0,
    });

    assert_eq!(result.size.width, 12.0);
    assert_eq!(result.size.height, 8.0);
    assert!(result.placements.is_empty());
}

fn groups_composed_by(content: impl FnMut() + 'static) -> usize {
    let _app_context = crate::render_state::app_context_test_scope();
    crate::run_test_composition(content)
        .composition
        .debug_slot_table_stats()
        .group_count
}

#[test]
fn a_widget_that_is_one_layout_composes_as_few_groups_as_a_bare_layout() {
    use crate::{
        layout::policies::EmptyMeasurePolicy,
        widgets::{Box, BoxSpec, Column, ColumnSpec, Row, RowSpec, Spacer},
    };
    let layout = groups_composed_by(|| {
        Layout(Modifier::empty(), EmptyMeasurePolicy, || {});
    });
    let widgets = [
        groups_composed_by(|| {
            Row(Modifier::empty(), RowSpec::default(), || {});
        }),
        groups_composed_by(|| {
            Column(Modifier::empty(), ColumnSpec::default(), || {});
        }),
        groups_composed_by(|| {
            Box(Modifier::empty(), BoxSpec::default(), || {});
        }),
        groups_composed_by(|| {
            Spacer(crate::modifier::Size::default());
        }),
    ];
    assert_eq!(
        widgets, [layout; 4],
        "a widget composing through `Layout` pays for a second group, its read \
         observation and its snapshot on every recomposition"
    );
}

#[composable]
fn ReportsCallerModifierChange(revision: MutableState<u32>, seen: Rc<RefCell<Vec<bool>>>) {
    let padded = Modifier::empty().padding(4.0);
    let modifier = if revision.value() < 2 {
        padded
    } else {
        padded.semantics(|_| {})
    };
    seen.borrow_mut().push(caller_modifier_changed(&modifier));
}

#[test]
fn a_caller_modifier_changes_only_when_it_is_not_strictly_equal() {
    let _app_context = crate::render_state::app_context_test_scope();
    let mut composition = Composition::new(MemoryApplier::new());
    let revision = MutableState::with_runtime(0_u32, composition.runtime_handle());
    let seen = Rc::new(RefCell::new(Vec::new()));
    composition
        .render(location_key(file!(), line!(), column!()), {
            let seen = Rc::clone(&seen);
            move || ReportsCallerModifierChange(revision, Rc::clone(&seen))
        })
        .expect("initial render");
    for next in 1..=3 {
        revision.set_value(next);
        composition
            .process_invalid_scopes()
            .expect("recompose with the next modifier");
    }
    assert_eq!(
        *seen.borrow(),
        [false, false, true, true],
        "a rebuilt equal modifier is unchanged; a new closure is a change"
    );
}

#[test]
fn rows_and_columns_default_to_compose_alignments() {
    use cranpose_ui_layout::{HorizontalAlignment, LinearArrangement, VerticalAlignment};

    use crate::widgets::{ColumnSpec, RowSpec};

    let row = RowSpec::default();
    assert_eq!(row.horizontal_arrangement, LinearArrangement::Start);
    assert_eq!(row.vertical_alignment, VerticalAlignment::Top);
    let column = ColumnSpec::default();
    assert_eq!(column.vertical_arrangement, LinearArrangement::Start);
    assert_eq!(column.horizontal_alignment, HorizontalAlignment::Start);
}