tests.rs
raw
use super::*;
use crate::clipboard::NoClipboard;
use crate::event::PointerInput;
use crate::text::TextContext;
use guiduck_scene::geom::Point;
use taffy::prelude::length;
const VIEWPORT: Size = Size::new(200.0, 200.0);
fn sample_font() -> guiduck_scene::paint::Blob<u8> {
static FONT: &[u8] = include_bytes!("../../../../guiduck-samples/fonts/DejaVuSans.ttf");
guiduck_scene::paint::Blob::new(std::sync::Arc::new(FONT))
}
/// Two 100×50 boxes stacked; only the first has a tooltip.
fn fixture() -> (WidgetTree, WidgetId, WidgetId) {
let mut tree = WidgetTree::with_text_context(TextContext::hermetic([sample_font()]));
let root = tree.insert(
super::super::Container::new(),
taffy::Style {
flex_direction: taffy::FlexDirection::Column,
..Default::default()
},
None,
);
let boxes: Vec<WidgetId> = (0..2)
.map(|_| {
tree.insert(
super::super::Container::new(),
taffy::Style {
size: taffy::Size {
width: length(100.0_f32),
height: length(50.0_f32),
},
..Default::default()
},
Some(root),
)
})
.collect();
tree.set_tooltip(boxes[0], Some("the first one".to_owned()));
tree.render_frame(VIEWPORT);
(tree, boxes[0], boxes[1])
}
fn hover(tree: &mut WidgetTree, at: Point) {
tree.dispatch_pointer(PointerInput::Moved(at), &mut NoClipboard);
tree.render_frame(VIEWPORT);
}
fn showing(tree: &WidgetTree) -> bool {
tree.overlay_roots().iter().any(|root| {
tree.children(*root)
.iter()
.any(|c| tree.widget::<TooltipPanel>(*c).is_some())
})
}
#[test]
fn a_tooltip_waits_for_the_pointer_to_rest() {
let (mut tree, _with, _without) = fixture();
let now = std::time::Instant::now();
hover(&mut tree, Point::new(50.0, 25.0));
assert!(!showing(&tree), "not the instant the pointer arrives");
// Resting arms a deadline — the tree says "in this long" and the platform
// says when now is, the same no-hidden-clock rule widgets follow.
let due = tree.next_wake(now).expect("a tooltip is pending");
assert_eq!(due, now + TOOLTIP_DELAY);
tree.tick(now + TOOLTIP_DELAY);
tree.render_frame(VIEWPORT);
assert!(showing(&tree), "it appears once the pointer has rested");
}
#[test]
fn moving_away_takes_it_back() {
let (mut tree, _with, _without) = fixture();
let now = std::time::Instant::now();
hover(&mut tree, Point::new(50.0, 25.0));
tree.next_wake(now);
tree.tick(now + TOOLTIP_DELAY);
tree.render_frame(VIEWPORT);
assert!(showing(&tree));
// A tooltip is about the thing you are pointing at; point elsewhere and
// it is about nothing.
hover(&mut tree, Point::new(50.0, 75.0));
assert!(!showing(&tree));
assert!(
tree.next_wake(now).is_none(),
"and nothing is pending for a widget with no tooltip"
);
}
#[test]
fn a_widget_without_one_arms_nothing() {
// The idle guarantee: hovering plain content must not start a clock.
let (mut tree, _with, _without) = fixture();
hover(&mut tree, Point::new(50.0, 75.0));
assert!(tree.next_wake(std::time::Instant::now()).is_none());
}
#[test]
fn an_open_tooltip_does_not_re_arm_itself() {
// Once it is showing there is nothing to wait for; a deadline that kept
// renewing would be a frame every 600ms, forever.
let (mut tree, _with, _without) = fixture();
let now = std::time::Instant::now();
hover(&mut tree, Point::new(50.0, 25.0));
tree.next_wake(now);
tree.tick(now + TOOLTIP_DELAY);
tree.render_frame(VIEWPORT);
assert!(showing(&tree));
assert!(tree.next_wake(now + TOOLTIP_DELAY).is_none());
}
#[test]
fn the_pointer_passes_through_a_tooltip() {
// It sits *below* its widget, which is exactly where the next widget is.
// If it took hover it would steal it from the thing it describes — and
// since losing that hover is what dismisses it, it would flicker itself
// out of existence. So the pointer goes straight through.
let (mut tree, with, without) = fixture();
let now = std::time::Instant::now();
hover(&mut tree, Point::new(50.0, 25.0));
tree.next_wake(now);
tree.tick(now + TOOLTIP_DELAY);
tree.render_frame(VIEWPORT);
assert!(showing(&tree));
// The tooltip is anchored below `with`, over `without`'s territory — and
// hit testing finds `without`, not the tooltip.
assert_eq!(tree.hit_test(Point::new(50.0, 60.0)), Some(without));
assert_eq!(tree.hit_test(Point::new(50.0, 25.0)), Some(with));
}