tests.rs raw

use std::cell::RefCell;
use std::rc::Rc;

use guiduck_scene::geom::{Point, Size};
use taffy::prelude::percent;

use super::*;
use crate::clipboard::NoClipboard;
use crate::event::{Key, KeyInput, Modifiers, PointerButton, PointerInput};
use crate::widget::{Container, WidgetId, WidgetTree};

fn sample_font() -> guiduck_scene::paint::Blob<u8> {
    static FONT: &[u8] = include_bytes!("../../../../guiduck-samples/fonts/DejaVuSans.ttf");
    static BLOB: std::sync::OnceLock<guiduck_scene::paint::Blob<u8>> = std::sync::OnceLock::new();
    BLOB.get_or_init(|| guiduck_scene::paint::Blob::new(std::sync::Arc::new(FONT)))
        .clone()
}

/// Root containing a single button at a known location.
fn fixture() -> (WidgetTree, WidgetId, Rc<RefCell<u32>>) {
    let mut tree =
        WidgetTree::with_text_context(crate::text::TextContext::hermetic([sample_font()]));
    let root = tree.insert(
        Container::new(),
        taffy::Style {
            size: taffy::Size {
                width: percent(1.0_f32),
                height: percent(1.0_f32),
            },
            padding: taffy::Rect::length(10.0_f32),
            align_items: Some(taffy::AlignItems::FLEX_START),
            ..Default::default()
        },
        None,
    );
    let button = tree.insert(
        Button::new("Save").family("DejaVu Sans"),
        taffy::Style::default(),
        Some(root),
    );
    let clicks = Rc::new(RefCell::new(0u32));
    let sink = clicks.clone();
    tree.on_event(button, crate::event::EventKind::Click, move |_ctx, _ev| {
        *sink.borrow_mut() += 1;
    });
    tree.render_frame(Size::new(300.0, 120.0));
    (tree, button, clicks)
}

fn key(tree: &mut WidgetTree, key: Key) {
    tree.dispatch_key(
        &KeyInput {
            key,
            modifiers: Modifiers::default(),
            pressed: true,
        },
        &mut NoClipboard,
    );
}

fn button_center(tree: &WidgetTree, button: WidgetId) -> Point {
    let origin = tree.absolute_origin(button);
    let size = tree.layout(button).size();
    Point::new(origin.x + size.width / 2.0, origin.y + size.height / 2.0)
}

#[test]
fn pointer_click_fires_the_wire() {
    let (mut tree, button, clicks) = fixture();
    let center = button_center(&tree, button);
    tree.dispatch_pointer(
        PointerInput::Down {
            pos: center,
            button: PointerButton::Left,
            time: std::time::Instant::now(),
        },
        &mut NoClipboard,
    );
    tree.dispatch_pointer(
        PointerInput::Up {
            pos: center,
            button: PointerButton::Left,
        },
        &mut NoClipboard,
    );
    assert_eq!(*clicks.borrow(), 1);
}

#[test]
fn enter_and_space_activate_a_focused_button() {
    let (mut tree, button, clicks) = fixture();
    // The focus invariant already focused the only focusable widget.
    assert_eq!(tree.focus(), Some(button));

    key(&mut tree, Key::Enter);
    tree.render_frame(Size::new(300.0, 120.0));
    assert_eq!(*clicks.borrow(), 1, "Enter activates");

    key(&mut tree, Key::Character(" ".to_owned()));
    tree.render_frame(Size::new(300.0, 120.0));
    assert_eq!(*clicks.borrow(), 2, "Space activates");
}

#[test]
fn a_disabled_button_is_inert() {
    let (mut tree, button, clicks) = fixture();
    tree.set_enabled(button, false);
    tree.render_frame(Size::new(300.0, 120.0));
    // A disabled button is not focusable, so keyboard activation cannot
    // reach it, and pointer clicks retarget to the enabled ancestor.
    let center = button_center(&tree, button);
    tree.dispatch_pointer(
        PointerInput::Down {
            pos: center,
            button: PointerButton::Left,
            time: std::time::Instant::now(),
        },
        &mut NoClipboard,
    );
    tree.dispatch_pointer(
        PointerInput::Up {
            pos: center,
            button: PointerButton::Left,
        },
        &mut NoClipboard,
    );
    assert_eq!(*clicks.borrow(), 0);
}

#[test]
fn cursor_is_a_pointer_over_the_button() {
    let (mut tree, button, _clicks) = fixture();
    let center = button_center(&tree, button);
    tree.dispatch_pointer(PointerInput::Moved(center), &mut NoClipboard);
    assert_eq!(tree.cursor_shape(), CursorShape::Pointer);
}