use std::{cell::RefCell, rc::Rc};
use cranpose_core::{
Composition, MemoryApplier, location_key, mutableStateList, mutableStateListOf,
mutableStateMap, mutableStateMapOf, ownedMutableStateOf, remember, rememberMutableStateOf,
rememberMutableStateOfNeverEqual, rememberUpdatedState, try_mutableStateOf,
};
fn compose(passes: usize, mut content: impl FnMut()) {
let mut composition = Composition::new(MemoryApplier::new());
let key = location_key(file!(), line!(), column!());
for _ in 0..passes {
composition
.render(key, &mut content)
.expect("the composition renders");
}
}
#[test]
fn a_state_list_is_allocated_per_pass_and_remembered_only_when_asked() {
let fresh = Rc::new(RefCell::new(Vec::new()));
let sink = Rc::clone(&fresh);
compose(3, move || {
let list = mutableStateList::<u32>();
sink.borrow_mut().push(list.len());
list.push(1);
});
assert_eq!(
*fresh.borrow(),
vec![0, 0, 0],
"an unremembered list must start empty on every pass"
);
let kept = Rc::new(RefCell::new(Vec::new()));
let sink = Rc::clone(&kept);
compose(3, move || {
let list = remember(mutableStateList::<u32>).with(Clone::clone);
sink.borrow_mut().push(list.len());
list.push(1);
});
assert_eq!(
*kept.borrow(),
vec![0, 1, 2],
"a remembered list must carry its contents across recomposition"
);
}
#[test]
fn a_state_list_can_be_seeded_with_values() {
let seen = Rc::new(RefCell::new(Vec::new()));
let sink = Rc::clone(&seen);
compose(1, move || {
let list = mutableStateListOf([10u32, 20, 30]);
sink.borrow_mut().extend(list.iter());
});
assert_eq!(*seen.borrow(), vec![10, 20, 30]);
}
#[test]
fn a_state_map_is_allocated_per_pass_and_remembered_only_when_asked() {
let fresh = Rc::new(RefCell::new(Vec::new()));
let sink = Rc::clone(&fresh);
compose(3, move || {
let map = mutableStateMap::<String, u32>();
sink.borrow_mut().push(map.len());
map.insert(format!("key-{}", map.len()), 1);
});
assert_eq!(*fresh.borrow(), vec![0, 0, 0]);
let kept = Rc::new(RefCell::new(Vec::new()));
let sink = Rc::clone(&kept);
compose(3, move || {
let map = remember(mutableStateMap::<String, u32>).with(Clone::clone);
sink.borrow_mut().push(map.len());
map.insert(format!("key-{}", map.len()), 1);
});
assert_eq!(*kept.borrow(), vec![0, 1, 2]);
}
#[test]
fn a_state_map_can_be_seeded_with_pairs() {
let found = Rc::new(RefCell::new(None));
let sink = Rc::clone(&found);
compose(1, move || {
let map = mutableStateMapOf([("a".to_string(), 1u32), ("b".to_string(), 2)]);
*sink.borrow_mut() = map.get(&"b".to_string());
});
assert_eq!(*found.borrow(), Some(2));
}
#[test]
fn an_updated_state_follows_the_value_it_was_given_this_pass() {
let observed = Rc::new(RefCell::new(Vec::new()));
let sink = Rc::clone(&observed);
let pass = Rc::new(std::cell::Cell::new(0u32));
compose(3, move || {
pass.set(pass.get() + 1);
let latest = rememberUpdatedState(pass.get());
sink.borrow_mut().push(latest.get());
});
assert_eq!(*observed.borrow(), vec![1, 2, 3]);
}
#[test]
fn a_never_equal_state_notifies_even_when_the_value_does_not_change() {
let writes = Rc::new(std::cell::Cell::new(0u32));
let counter = Rc::clone(&writes);
compose(1, move || {
let state = rememberMutableStateOfNeverEqual(|| 7u32);
state.set(7);
state.set(7);
counter.set(state.get());
});
assert_eq!(writes.get(), 7);
}
#[test]
fn a_structurally_equal_state_holds_the_value_it_was_given() {
let seen = Rc::new(std::cell::Cell::new(0u32));
let sink = Rc::clone(&seen);
compose(2, move || {
let state = rememberMutableStateOf(|| 1u32);
state.set(state.get() + 1);
sink.set(state.get());
});
assert_eq!(seen.get(), 3);
}
#[test]
fn an_owned_state_outlives_the_composer_that_made_it() {
let mut composition = Composition::new(MemoryApplier::new());
let escaped: Rc<RefCell<Option<_>>> = Rc::default();
let sink = Rc::clone(&escaped);
composition
.render(location_key(file!(), line!(), column!()), move || {
*sink.borrow_mut() = Some(ownedMutableStateOf(41u32));
})
.expect("the composition renders");
let owned = escaped.borrow_mut().take().expect("state was created");
owned.handle().set(42);
assert_eq!(owned.handle().get(), 42);
}
#[test]
fn a_fallible_state_allocation_answers_none_with_no_runtime() {
assert!(
try_mutableStateOf(0u32).is_none(),
"a state was allocated with no runtime to own it"
);
}
#[test]
fn a_fallible_state_allocation_succeeds_inside_a_composition() {
let made = Rc::new(std::cell::Cell::new(false));
let flag = Rc::clone(&made);
compose(1, move || {
flag.set(try_mutableStateOf(0u32).is_some());
});
assert!(made.get(), "a composition could not allocate state");
}