cranpose-core 0.0.60

Core runtime for a Jetpack Compose inspired UI framework in Rust
Documentation
use super::*;

fn active_group_anchors(table: &SlotTable) -> Vec<AnchorId> {
    table.groups.iter().map(|group| group.anchor).collect()
}

fn panic_message(payload: &(dyn Any + Send)) -> &str {
    if let Some(message) = payload.downcast_ref::<&str>() {
        message
    } else if let Some(message) = payload.downcast_ref::<String>() {
        message.as_str()
    } else {
        ""
    }
}

fn composed_parent_with_two_children(
    parent_key: Key,
    first_child_key: Key,
    second_child_key: Key,
) -> (SlotHarness, AnchorId, AnchorId, AnchorId) {
    let mut harness = SlotHarness::new();
    harness.begin_pass(SlotPassMode::Compose);
    let (parent_anchor, first_anchor, second_anchor) = harness.session(|session| {
        let parent = begin_unkeyed(session, parent_key, None);

        let first = begin_unkeyed(session, first_child_key, None);
        let first_result = session.finish_group_body();
        assert!(first_result.detached_children.is_empty());
        session.end_group();

        let second = begin_unkeyed(session, second_child_key, None);
        let second_result = session.finish_group_body();
        assert!(second_result.detached_children.is_empty());
        session.end_group();

        let parent_result = session.finish_group_body();
        assert!(parent_result.detached_children.is_empty());
        session.end_group();

        (parent.anchor, first.anchor, second.anchor)
    });
    harness.finish_pass();
    (harness, parent_anchor, first_anchor, second_anchor)
}

#[test]
fn keyed_sibling_move_later_child_to_earlier_cursor_succeeds() {
    const PARENT_KEY: Key = 10_031;
    const FIRST_CHILD_KEY: Key = 10_032;
    const SECOND_CHILD_KEY: Key = 10_033;

    let (mut harness, parent_anchor, first_anchor, second_anchor) =
        composed_parent_with_two_children(PARENT_KEY, FIRST_CHILD_KEY, SECOND_CHILD_KEY);

    let cursor = ChildCursor::new(
        parent_anchor,
        harness.table.current_group_index(first_anchor),
    );
    harness
        .table
        .move_later_sibling_subtree_to_cursor(ActiveSubtreeRoot::new(second_anchor), cursor);

    assert_eq!(
        active_group_anchors(&harness.table),
        vec![parent_anchor, second_anchor, first_anchor]
    );
    assert_eq!(
        harness.table.group_anchor_state(second_anchor),
        Some(AnchorState::Active(1))
    );
    assert_eq!(
        harness.table.group_anchor_state(first_anchor),
        Some(AnchorState::Active(2))
    );
    harness.table.debug_verify();
}

#[test]
fn keyed_sibling_move_same_cursor_is_no_op() {
    const PARENT_KEY: Key = 10_041;
    const FIRST_CHILD_KEY: Key = 10_042;
    const SECOND_CHILD_KEY: Key = 10_043;

    let (mut harness, parent_anchor, first_anchor, _) =
        composed_parent_with_two_children(PARENT_KEY, FIRST_CHILD_KEY, SECOND_CHILD_KEY);
    let before = active_group_anchors(&harness.table);

    let cursor = ChildCursor::new(
        parent_anchor,
        harness.table.current_group_index(first_anchor),
    );
    harness
        .table
        .move_later_sibling_subtree_to_cursor(ActiveSubtreeRoot::new(first_anchor), cursor);

    assert_eq!(active_group_anchors(&harness.table), before);
    harness.table.debug_verify();
}

#[test]
fn keyed_sibling_move_rejects_earlier_sibling_to_later_cursor() {
    const PARENT_KEY: Key = 10_051;
    const FIRST_CHILD_KEY: Key = 10_052;
    const SECOND_CHILD_KEY: Key = 10_053;

    let (mut harness, parent_anchor, first_anchor, second_anchor) =
        composed_parent_with_two_children(PARENT_KEY, FIRST_CHILD_KEY, SECOND_CHILD_KEY);
    let before = active_group_anchors(&harness.table);
    let cursor = ChildCursor::new(
        parent_anchor,
        harness.table.current_group_index(second_anchor),
    );

    let move_result = panic::catch_unwind(AssertUnwindSafe(|| {
        harness
            .table
            .move_later_sibling_subtree_to_cursor(ActiveSubtreeRoot::new(first_anchor), cursor);
    }));

    let panic = move_result.expect_err("earlier sibling move must panic");
    assert!(
        panic_message(panic.as_ref())
            .contains("only moves a later direct sibling to an earlier child cursor"),
        "unexpected panic message: {}",
        panic_message(panic.as_ref())
    );
    assert_eq!(active_group_anchors(&harness.table), before);
    harness.table.debug_verify();
}

#[test]
fn child_cursor_rejects_cross_parent_active_move() {
    const PARENT_A_KEY: Key = 10_001;
    const CHILD_A_KEY: Key = 10_002;
    const PARENT_B_KEY: Key = 10_003;
    const CHILD_B_KEY: Key = 10_004;

    let mut harness = SlotHarness::new();
    harness.begin_pass(SlotPassMode::Compose);
    let (parent_a_anchor, child_b_anchor) = harness.session(|session| {
        let parent_a = begin_unkeyed(session, PARENT_A_KEY, None);
        begin_unkeyed(session, CHILD_A_KEY, None);
        let child_a_result = session.finish_group_body();
        assert!(child_a_result.detached_children.is_empty());
        session.end_group();
        let parent_a_result = session.finish_group_body();
        assert!(parent_a_result.detached_children.is_empty());
        session.end_group();

        begin_unkeyed(session, PARENT_B_KEY, None);
        let child_b = begin_unkeyed(session, CHILD_B_KEY, None);
        let child_b_result = session.finish_group_body();
        assert!(child_b_result.detached_children.is_empty());
        session.end_group();
        let parent_b_result = session.finish_group_body();
        assert!(parent_b_result.detached_children.is_empty());
        session.end_group();

        (parent_a.anchor, child_b.anchor)
    });
    harness.finish_pass();

    let before = active_group_anchors(&harness.table);
    let cursor = ChildCursor::new(
        parent_a_anchor,
        harness.table.direct_child_range(parent_a_anchor).end(),
    );
    let move_result = panic::catch_unwind(AssertUnwindSafe(|| {
        harness
            .table
            .move_later_sibling_subtree_to_cursor(ActiveSubtreeRoot::new(child_b_anchor), cursor);
    }));

    let panic = move_result.expect_err("cross-parent move must panic");
    assert!(
        panic_message(panic.as_ref()).contains("direct child of the cursor parent"),
        "unexpected panic message: {}",
        panic_message(panic.as_ref())
    );
    assert_eq!(active_group_anchors(&harness.table), before);
    harness.table.debug_verify();
}

#[test]
fn child_cursor_rejects_grandchild_as_direct_sibling_move() {
    const PARENT_KEY: Key = 10_011;
    const CHILD_KEY: Key = 10_012;
    const GRANDCHILD_KEY: Key = 10_013;

    let mut harness = SlotHarness::new();
    harness.begin_pass(SlotPassMode::Compose);
    let (parent_anchor, child_anchor, grandchild_anchor) = harness.session(|session| {
        let parent = begin_unkeyed(session, PARENT_KEY, None);
        let child = begin_unkeyed(session, CHILD_KEY, None);
        let grandchild = begin_unkeyed(session, GRANDCHILD_KEY, None);
        let grandchild_result = session.finish_group_body();
        assert!(grandchild_result.detached_children.is_empty());
        session.end_group();
        let child_result = session.finish_group_body();
        assert!(child_result.detached_children.is_empty());
        session.end_group();
        let parent_result = session.finish_group_body();
        assert!(parent_result.detached_children.is_empty());
        session.end_group();
        (parent.anchor, child.anchor, grandchild.anchor)
    });
    harness.finish_pass();

    let before = active_group_anchors(&harness.table);
    let cursor = ChildCursor::new(
        parent_anchor,
        harness.table.current_group_index(child_anchor),
    );
    let move_result = panic::catch_unwind(AssertUnwindSafe(|| {
        harness.table.move_later_sibling_subtree_to_cursor(
            ActiveSubtreeRoot::new(grandchild_anchor),
            cursor,
        );
    }));

    let panic = move_result.expect_err("grandchild move must panic");
    assert!(
        panic_message(panic.as_ref()).contains("direct child of the cursor parent"),
        "unexpected panic message: {}",
        panic_message(panic.as_ref())
    );
    assert_eq!(active_group_anchors(&harness.table), before);
    harness.table.debug_verify();
}

#[test]
fn restore_rejects_cursor_inside_existing_child_subtree() {
    const PARENT_KEY: Key = 10_021;
    const CHILD_KEY: Key = 10_022;
    const GRANDCHILD_KEY: Key = 10_023;
    const DETACHED_KEY: Key = 10_024;

    let mut harness = SlotHarness::new();
    harness.begin_pass(SlotPassMode::Compose);
    let (parent_anchor, grandchild_anchor) = harness.session(|session| {
        let parent = begin_unkeyed(session, PARENT_KEY, None);
        begin_unkeyed(session, CHILD_KEY, None);
        let grandchild = begin_unkeyed(session, GRANDCHILD_KEY, None);
        let grandchild_result = session.finish_group_body();
        assert!(grandchild_result.detached_children.is_empty());
        session.end_group();
        let child_result = session.finish_group_body();
        assert!(child_result.detached_children.is_empty());
        session.end_group();
        begin_unkeyed(session, DETACHED_KEY, None);
        let detached_result = session.finish_group_body();
        assert!(detached_result.detached_children.is_empty());
        session.end_group();
        let parent_result = session.finish_group_body();
        assert!(parent_result.detached_children.is_empty());
        session.end_group();
        (parent.anchor, grandchild.anchor)
    });
    harness.finish_pass();

    harness.begin_pass(SlotPassMode::Compose);
    let detached = harness.session(|session| {
        begin_unkeyed(session, PARENT_KEY, None);
        begin_unkeyed(session, CHILD_KEY, None);
        begin_unkeyed(session, GRANDCHILD_KEY, None);
        let grandchild_result = session.finish_group_body();
        assert!(grandchild_result.detached_children.is_empty());
        session.end_group();
        let child_result = session.finish_group_body();
        assert!(child_result.detached_children.is_empty());
        session.end_group();
        let parent_result = session.finish_group_body();
        session.end_group();
        assert_eq!(parent_result.detached_children.len(), 1);
        parent_result.detached_children.into_iter().next().unwrap()
    });
    harness.finish_pass();

    let detached_anchor = detached
        .group_anchors()
        .next()
        .expect("detached subtree must contain an anchor");
    let group_count_before = harness.table.groups.len();
    let grandchild_index = harness.table.current_group_index(grandchild_anchor);
    let restore_result = panic::catch_unwind(AssertUnwindSafe(|| {
        restore_detached_child(
            &mut harness.table,
            parent_anchor,
            grandchild_index,
            detached.root_key(),
            detached,
        );
    }));

    assert!(
        restore_result.is_err(),
        "restore must reject cursors that point inside an existing child subtree",
    );
    assert_eq!(harness.table.groups.len(), group_count_before);
    assert_eq!(
        harness.table.anchor_state(detached_anchor),
        Some(AnchorState::Detached),
    );
    harness.table.debug_verify();
}

#[test]
fn restore_rejects_group_anchor_that_is_not_detached() {
    const PARENT_KEY: Key = 10_031;
    const CHILD_KEY: Key = 10_032;

    let (mut harness, detached) = detached_single_child(PARENT_KEY, CHILD_KEY);
    let parent_anchor = harness.table.groups[0].anchor;
    let detached_anchor = detached
        .group_anchors()
        .next()
        .expect("detached subtree must contain an anchor");
    let group_count_before = harness.table.groups.len();

    harness.table.anchors.set_active(detached_anchor, 0);
    let restore_result = panic::catch_unwind(AssertUnwindSafe(|| {
        restore_detached_child(
            &mut harness.table,
            parent_anchor,
            1,
            detached.root_key(),
            detached,
        );
    }));
    harness.table.anchors.mark_detached(detached_anchor);

    assert!(
        restore_result.is_err(),
        "restore must reject group anchors that are already active",
    );
    assert_eq!(harness.table.groups.len(), group_count_before);
    assert_eq!(
        harness.table.anchor_state(detached_anchor),
        Some(AnchorState::Detached),
    );
    harness.table.debug_verify();
}

#[test]
fn restore_rejects_payload_anchor_that_is_not_detached() {
    const PARENT_KEY: Key = 10_041;
    const CHILD_KEY: Key = 10_042;

    let (mut harness, detached, _) =
        detached_single_child_with_options(PARENT_KEY, CHILD_KEY, None, true, false);
    let parent_anchor = harness.table.groups[0].anchor;
    let payload_anchor = detached
        .payload_anchors()
        .next()
        .expect("detached subtree must contain a payload anchor");
    let group_count_before = harness.table.groups.len();

    harness
        .table
        .payload_anchors
        .set_active(payload_anchor, parent_anchor, 0);
    let restore_result = panic::catch_unwind(AssertUnwindSafe(|| {
        restore_detached_child(
            &mut harness.table,
            parent_anchor,
            1,
            detached.root_key(),
            detached,
        );
    }));
    harness.table.payload_anchors.mark_detached(payload_anchor);

    assert!(
        restore_result.is_err(),
        "restore must reject payload anchors that are already active",
    );
    assert_eq!(harness.table.groups.len(), group_count_before);
    assert_eq!(
        harness.table.payload_anchor_lifecycle(payload_anchor),
        Some(PayloadAnchorLifecycle::Detached),
    );
    harness.table.debug_verify();
}