#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod integration {
use crate::reactive::batch::batch;
use crate::reactive::computed::Computed;
use crate::reactive::effect::Effect;
use crate::reactive::scope::ReactiveScope;
use crate::reactive::signal::Signal;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
#[test]
fn counter_app_pattern() {
let count = Signal::new(0i32);
let display = Computed::new({
let count = count.clone();
move || format!("Count: {}", count.get())
});
count.subscribe(display.as_subscriber());
let rendered: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
let effect = Effect::new({
let display = display.clone();
let rendered = Rc::clone(&rendered);
move || {
rendered.borrow_mut().push(display.get());
}
});
display.subscribe(effect.as_subscriber());
count.set(1);
count.set(2);
assert_eq!(*rendered.borrow(), vec!["Count: 0", "Count: 1", "Count: 2"]);
}
#[test]
fn todo_list_pattern() {
let todos: Signal<Vec<(String, bool)>> = Signal::new(vec![
("Buy milk".into(), false),
("Write code".into(), true),
]);
let total = Computed::new({
let todos = todos.clone();
move || todos.with(|t| t.len())
});
todos.subscribe(total.as_subscriber());
let done_count = Computed::new({
let todos = todos.clone();
move || todos.with(|t| t.iter().filter(|(_, done)| *done).count())
});
todos.subscribe(done_count.as_subscriber());
assert_eq!(total.get(), 2);
assert_eq!(done_count.get(), 1);
todos.update(|t| t.push(("Read book".into(), true)));
assert_eq!(total.get(), 3);
assert_eq!(done_count.get(), 2);
}
#[test]
fn theme_switch_pattern() {
let dark_mode = Signal::new(false);
let bg_color = Computed::new({
let dark = dark_mode.clone();
move || {
if dark.get() { "black" } else { "white" }
}
});
dark_mode.subscribe(bg_color.as_subscriber());
let fg_color = Computed::new({
let dark = dark_mode.clone();
move || {
if dark.get() { "white" } else { "black" }
}
});
dark_mode.subscribe(fg_color.as_subscriber());
assert_eq!(bg_color.get(), "white");
assert_eq!(fg_color.get(), "black");
dark_mode.set(true);
assert_eq!(bg_color.get(), "black");
assert_eq!(fg_color.get(), "white");
}
#[test]
fn scope_based_widget_lifecycle() {
let global_sig = Signal::new(0);
let effect_ran = Rc::new(Cell::new(0u32));
{
let mut scope = ReactiveScope::new();
let effect = scope.create_effect({
let sig = global_sig.clone();
let count = Rc::clone(&effect_ran);
move || {
let _ = sig.get();
count.set(count.get() + 1);
}
});
global_sig.subscribe(effect.as_subscriber());
global_sig.set(1);
assert_eq!(effect_ran.get(), 2); }
global_sig.set(2);
assert_eq!(effect_ran.get(), 2); }
#[test]
fn nested_computed_chain_three_levels() {
let base = Signal::new(2);
let level1 = Computed::new({
let base = base.clone();
move || base.get() * 2
});
base.subscribe(level1.as_subscriber());
let level2 = Computed::new({
let level1 = level1.clone();
move || level1.get() + 10
});
level1.subscribe(level2.as_subscriber());
let level3 = Computed::new({
let level2 = level2.clone();
move || format!("Result: {}", level2.get())
});
level2.subscribe(level3.as_subscriber());
assert_eq!(level3.get(), "Result: 14");
base.set(5);
assert_eq!(level3.get(), "Result: 20");
base.set(0);
assert_eq!(level3.get(), "Result: 10");
}
#[test]
fn stress_many_signals_and_effects() {
let signals: Vec<Signal<i32>> = (0..100).map(Signal::new).collect();
let total_count = Rc::new(Cell::new(0u32));
let mut effects = Vec::new();
for sig in &signals {
let effect = Effect::new({
let sig = sig.clone();
let count = Rc::clone(&total_count);
move || {
let _ = sig.get();
count.set(count.get() + 1);
}
});
sig.subscribe(effect.as_subscriber());
effects.push(effect);
}
assert_eq!(total_count.get(), 100);
for (i, sig) in signals.iter().enumerate() {
sig.set(i as i32 + 1000);
}
assert_eq!(total_count.get(), 200);
}
#[test]
fn stress_rapid_sets_with_pruning() {
let sig = Signal::new(0);
for _ in 0..100 {
let sub = Effect::new({
let sig = sig.clone();
move || {
let _ = sig.get();
}
});
sig.subscribe(sub.as_subscriber());
drop(sub);
}
for i in 0..100 {
sig.set(i);
}
}
#[test]
fn batch_complex_dependency_graph() {
let a = Signal::new(1);
let b = Signal::new(2);
let sum = Computed::new({
let a = a.clone();
let b = b.clone();
move || a.get() + b.get()
});
a.subscribe(sum.as_subscriber());
b.subscribe(sum.as_subscriber());
let effect_count = Rc::new(Cell::new(0u32));
let effect = Effect::new({
let sum = sum.clone();
let count = Rc::clone(&effect_count);
move || {
let _ = sum.get();
count.set(count.get() + 1);
}
});
sum.subscribe(effect.as_subscriber());
assert_eq!(effect_count.get(), 1);
batch(|| {
a.set(10);
b.set(20);
});
assert_eq!(effect_count.get(), 2);
assert_eq!(sum.get(), 30);
}
#[test]
fn dynamic_dependency_switching() {
let use_a = Signal::new(true);
let a = Signal::new(10);
let b = Signal::new(20);
let value = Computed::new({
let use_a = use_a.clone();
let a = a.clone();
let b = b.clone();
move || {
if use_a.get() { a.get() } else { b.get() }
}
});
use_a.subscribe(value.as_subscriber());
a.subscribe(value.as_subscriber());
b.subscribe(value.as_subscriber());
assert_eq!(value.get(), 10);
use_a.set(false);
assert_eq!(value.get(), 20);
b.set(30);
assert_eq!(value.get(), 30);
use_a.set(true);
assert_eq!(value.get(), 10);
}
#[test]
fn scope_cleanup_prevents_accumulation() {
let sig = Signal::new(0);
let total_effects = Rc::new(Cell::new(0u32));
for _ in 0..50 {
let mut scope = ReactiveScope::new();
let effect = scope.create_effect({
let sig = sig.clone();
let count = Rc::clone(&total_effects);
move || {
let _ = sig.get();
count.set(count.get() + 1);
}
});
sig.subscribe(effect.as_subscriber());
}
assert_eq!(total_effects.get(), 50);
sig.set(1);
assert_eq!(total_effects.get(), 50);
}
#[test]
fn effect_chain_through_computed() {
let input = Signal::new(5);
let processed = Computed::new({
let input = input.clone();
move || input.get() * 3
});
input.subscribe(processed.as_subscriber());
let output: Rc<RefCell<Vec<i32>>> = Rc::new(RefCell::new(Vec::new()));
let effect = Effect::new({
let processed = processed.clone();
let output = Rc::clone(&output);
move || {
output.borrow_mut().push(processed.get());
}
});
processed.subscribe(effect.as_subscriber());
input.set(10);
input.set(0);
assert_eq!(*output.borrow(), vec![15, 30, 0]);
}
#[test]
fn batch_within_scope() {
let sig = Signal::new(0);
let count = Rc::new(Cell::new(0u32));
let mut scope = ReactiveScope::new();
let effect = scope.create_effect({
let sig = sig.clone();
let count = Rc::clone(&count);
move || {
let _ = sig.get();
count.set(count.get() + 1);
}
});
sig.subscribe(effect.as_subscriber());
assert_eq!(count.get(), 1);
batch(|| {
sig.set(1);
sig.set(2);
sig.set(3);
});
assert_eq!(count.get(), 2);
assert_eq!(sig.get(), 3);
}
#[test]
fn signal_with_complex_type() {
use std::collections::HashMap;
let map: Signal<HashMap<String, i32>> = Signal::new(HashMap::new());
let keys = Computed::new({
let map = map.clone();
move || {
let mut k: Vec<String> = map.with(|m| m.keys().cloned().collect());
k.sort();
k
}
});
map.subscribe(keys.as_subscriber());
assert!(keys.get().is_empty());
map.update(|m| {
m.insert("a".into(), 1);
m.insert("b".into(), 2);
});
assert_eq!(keys.get(), vec!["a", "b"]);
}
}