#![cfg(debug_assertions)]
use crate::animation::{Animate, TimingFunction, Transition};
use crate::layout::{Constraints, Flex};
use crate::reactive::create_signal;
use crate::reactive::diagnostics::report_count;
use crate::renderer::{RenderNode, Shadow};
use crate::tree::Tree;
use crate::widgets::widget::{Event, Key, Modifiers, MouseButton, ScrollSource};
use crate::widgets::{Color, ImageSource, LinearGradient, Overflow, Scroll};
use crate::widgets::{Stateful, Widget, container, image, text, text_input};
fn diagnostics_from_full_lifecycle(widget: impl Widget + 'static) -> u64 {
let before = report_count();
let mut tree = Tree::new();
let root = tree.register(Box::new(widget));
tree.with_widget_mut(root, |w, id, t| w.register_children(t, id));
tree.set_frame_instant(Some(std::time::Instant::now()));
tree.with_widget(root, |w| w.layout_hints());
let constraints = Constraints::new(0.0, 0.0, 300.0, 300.0);
tree.layout_widget(root, constraints);
let mut node = RenderNode::new(root.as_u64());
tree.paint_widget(root, &mut node, 1.0);
tree.with_widget_mut(root, |w, id, t| {
w.advance_animations(t, id);
});
tree.with_widget_mut(root, |w, id, t| {
w.reconcile_children(t, id);
});
tree.set_frame_instant(None);
tree.set_event_instant(Some(std::time::Instant::now()));
crate::reactive::diagnostics::snapshot_zone(|| {
for event in [
Event::mouse_enter(10.0, 10.0),
Event::mouse_move(10.0, 10.0),
Event::mouse_down(10.0, 10.0, MouseButton::Left),
Event::mouse_up(10.0, 10.0, MouseButton::Left),
Event::scroll(10.0, 10.0, 0.0, 10.0, ScrollSource::Wheel),
Event::KeyDown {
key: Key::Char('a'),
modifiers: Modifiers::default(),
repeat: false,
},
Event::MouseLeave,
] {
tree.dispatch(root, &event);
}
});
tree.set_event_instant(None);
tree.set_frame_instant(Some(std::time::Instant::now()));
tree.layout_widget(root, constraints);
let mut node2 = RenderNode::new(root.as_u64());
tree.paint_widget(root, &mut node2, 1.0);
report_count() - before
}
#[track_caller]
fn assert_quiet(what: &str, count: u64) {
assert_eq!(
count, 0,
"{what} drew {count} diagnostic(s) from the library. Re-run with \
`--nocapture` to see which and where. A read with no reactive scope \
either needs a tracking scope or is a legitimate snapshot and needs \
to say so with `snapshot_zone`; a clock read outside its pass either \
belongs inside one or is asking the wrong clock."
);
}
#[test]
fn a_fully_reactive_container_is_quiet() {
let n = create_signal(0.0f32);
let flag = create_signal(false);
let t = || Transition::new(200, TimingFunction::EaseOut);
let widget = container()
.layout(Flex::row().spacing(move || n.get()))
.padding((move || n.get()).transition(t()))
.background((move || if flag.get() { Color::RED } else { Color::BLUE }).transition(t()))
.border(
(move || n.get() + 1.0).transition(t()),
Color::WHITE.transition(t()),
)
.corners(
(move || crate::widgets::Corners::superellipse(n.get() + 4.0, n.get() + 1.0))
.transition(t()),
)
.shadow(
(move || Shadow::new((0.0, n.get()), n.get() * 2.0, 0.0, Color::BLACK)).transition(t()),
)
.width((move || n.get() + 100.0).transition(t()))
.height((move || n.get() + 100.0).transition(t()))
.visible(move || !flag.get())
.rotate((move || n.get()).transition(t()))
.translate(move || (n.get(), 0.0))
.scale(move || n.get())
.gradient(move || {
Some(LinearGradient::horizontal(
if flag.get() { Color::RED } else { Color::BLUE },
Color::WHITE,
))
})
.backdrop_blur(move || n.get() + 8.0)
.overflow(move || {
if flag.get() {
Overflow::Hidden
} else {
Overflow::Visible
}
})
.when_hovered(|s| s.lighter(0.1))
.when_pressed(|s| s.ripple())
.when_focused(|s| s.border(2.0, Color::WHITE))
.on_click(|| {})
.child(container().width(20.0).height(20.0));
assert_quiet(
"a fully reactive container",
diagnostics_from_full_lifecycle(widget),
);
}
#[test]
fn a_scrollable_container_is_quiet() {
let n = create_signal(0.0f32);
let widget = container()
.width(100.0)
.height(100.0)
.scroll(Scroll::both())
.padding(move || n.get())
.child(container().width(500.0).height(500.0));
assert_quiet(
"a scrollable container",
diagnostics_from_full_lifecycle(widget),
);
}
#[test]
fn a_container_with_reactive_children_is_quiet() {
let items = create_signal(vec![1u64, 2, 3]);
let show = create_signal(true);
let widget = container()
.children(crate::widgets::keyed(
move || items.get(),
|i| *i,
|_| container().width(20.0).height(20.0),
))
.child(move || show.get().then(|| container().width(10.0).height(10.0)));
assert_quiet(
"a container with reactive children",
diagnostics_from_full_lifecycle(widget),
);
}
#[test]
fn a_text_that_styles_itself_is_quiet() {
let n = create_signal(0.0f32);
let widget = container().child(
text("ciao")
.color(move || {
if n.get() > 0.0 {
Color::RED
} else {
Color::WHITE
}
})
.font_size(move || n.get() + 12.0),
);
assert_quiet(
"a text that declares its own style",
diagnostics_from_full_lifecycle(widget),
);
}
#[test]
fn an_input_that_styles_itself_is_quiet() {
let n = create_signal(0.0f32);
let value = create_signal(String::new());
let widget = container().child(
text_input(value)
.placeholder("prompt")
.placeholder_color(move || {
if n.get() > 0.0 {
Color::RED
} else {
Color::WHITE
}
})
.cursor_color(move || {
if n.get() > 0.0 {
Color::RED
} else {
Color::WHITE
}
}),
);
assert_quiet(
"an input that declares its own caret and placeholder",
diagnostics_from_full_lifecycle(widget),
);
}
#[test]
fn a_text_with_state_overrides_is_quiet() {
let wrong = create_signal(false);
let widget = container().control().on_click(|| {}).child(
text("ciao")
.color(Color::WHITE)
.when_hovered(|s| s.color(Color::RED))
.when_focused(|s| s.color(Color::GREEN))
.state(wrong, |s| s.color(Color::BLUE)),
);
assert_quiet(
"a text with state overrides",
diagnostics_from_full_lifecycle(widget),
);
}
#[test]
fn a_text_that_is_its_own_unit_is_quiet() {
let widget = container().child(
text("ciao")
.color(Color::WHITE)
.when_hovered(|s| s.color(Color::RED)),
);
assert_quiet(
"a text with no control above it",
diagnostics_from_full_lifecycle(widget),
);
}
#[test]
fn a_fully_reactive_text_input_is_quiet() {
let value = create_signal(String::from("ciao"));
let n = create_signal(0.0f32);
let widget = container().child(
text_input(value)
.color(move || {
if n.get() > 0.0 {
Color::RED
} else {
Color::WHITE
}
})
.cursor_color(move || Color::WHITE.with_alpha(n.get().max(0.5)))
.font_size(move || n.get() + 12.0),
);
assert_quiet(
"a fully reactive text input",
diagnostics_from_full_lifecycle(widget),
);
}
#[test]
fn an_image_is_quiet() {
let widget = container()
.width(20.0)
.height(20.0)
.child(image(ImageSource::Rgba {
width: 2,
height: 2,
pixels: std::sync::Arc::from(vec![255u8; 2 * 2 * 4].into_boxed_slice()),
}));
assert_quiet("an image", diagnostics_from_full_lifecycle(widget));
}