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));
}