cranpose-ui 0.21.2

Composable widgets, text, layouts and input for Cranpose apps
Documentation
use cranpose_core::{NodeId, PackedU64};

use super::{LayoutSync, MAX_PENDING, SemanticsLayoutLog, record_layout_change};

fn synced(log: &SemanticsLayoutLog) -> (LayoutSync, Vec<NodeId>) {
    let mut changed = Vec::new();
    let sync = log.begin_sync(&mut changed);
    (sync, changed)
}

#[test]
fn nothing_is_recorded_before_a_tree_reads_the_log() {
    let log = SemanticsLayoutLog::default();
    let mut stamp = PackedU64::default();
    log.record(NodeId(7), &mut stamp);
    let (sync, changed) = synced(&log);
    assert!(!sync.continuous, "the first reader walks the whole tree");
    assert!(changed.is_empty());
}

#[test]
fn each_node_is_recorded_once_between_reads() {
    let log = SemanticsLayoutLog::default();
    synced(&log);
    let (mut first, mut second) = (PackedU64::default(), PackedU64::default());
    log.record(NodeId(1), &mut first);
    log.record(NodeId(1), &mut first);
    log.record(NodeId(2), &mut second);
    let (sync, changed) = synced(&log);
    assert!(sync.continuous);
    assert_eq!(changed, vec![NodeId(1), NodeId(2)]);

    log.record(NodeId(1), &mut first);
    assert_eq!(
        synced(&log).1,
        vec![NodeId(1)],
        "a node changing again after a read is recorded again"
    );
}

#[test]
fn an_overflowing_log_stops_recording_and_starts_a_new_epoch() {
    let log = SemanticsLayoutLog::default();
    let (before, _) = synced(&log);
    let mut stamps = vec![PackedU64::default(); MAX_PENDING + 1];
    for (id, stamp) in stamps.iter_mut().enumerate() {
        log.record(NodeId(id), stamp);
    }
    let mut late = PackedU64::default();
    log.record(NodeId(99_999), &mut late);
    let (sync, changed) = synced(&log);
    assert!(!sync.continuous, "the reader walks the whole tree again");
    assert_ne!(sync.epoch, before.epoch);
    assert!(changed.is_empty());
    assert!(synced(&log).0.continuous, "the next read tracks again");
}

#[test]
fn changes_are_recorded_in_the_current_app_context_only_while_armed() {
    let _app_context = crate::render_state::app_context_test_scope();
    let mut stamp = PackedU64::default();
    record_layout_change(NodeId(3), &mut stamp);
    let drain = || {
        crate::render_state::with_current_semantics_layout_log(synced).map(|(_, changed)| changed)
    };
    assert_eq!(drain(), Some(Vec::new()), "an unarmed log records nothing");
    record_layout_change(NodeId(3), &mut stamp);
    assert_eq!(drain(), Some(vec![NodeId(3)]));
}