Files
Ely-GPUI-Components/src/misc/tests/mod.rs
T
ZacharyZhang-NY 73f3399cea Misc · Numbers: a calculator and a unit converter
misc::Calculator works a typed sum in forms::evaluate's precedence,
from a pad or the keyboard; Enter is equals while the calculator
itself has focus, and Escape clears or passes on. misc::UnitConverter
reads an amount in another unit across seven kinds, with a swap.
Both show numbers through format::significant, which rounds through
Rust's exponent form.
2026-09-28 00:41:49 -04:00

197 lines
5.5 KiB
Rust

use std::{cell::Cell, rc::Rc, time::Duration};
use gpui::{
AnyElement, Context, FocusHandle, IntoElement, KeyUpEvent, Keystroke, ParentElement, Render,
Styled, TestAppContext, VisualTestContext, Window, div, px,
};
use jiff::{Timestamp, tz::TimeZone};
use super::{Clock, Stopwatch, WorldClock};
use crate::{
forms,
primitives::{FocusNext, FocusPrev, FocusScope},
theme::Theme,
};
mod numbers;
struct Stage {
root: FocusHandle,
part: fn() -> AnyElement,
}
impl Render for Stage {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
FocusScope::new(&self.root)
.root()
.size_full()
.child(div().w(px(400.0)).child((self.part)()))
}
}
fn settle(cx: &mut VisualTestContext) {
for _ in 0..3 {
cx.run_until_parked();
cx.update(|window, _| window.refresh());
}
cx.run_until_parked();
}
/// Stages `part`, counting the times the stage is told to redraw.
pub(super) fn stage(
part: fn() -> AnyElement,
cx: &mut TestAppContext,
) -> (Rc<Cell<usize>>, &mut VisualTestContext) {
cx.update(|cx| {
Theme::init(cx);
forms::bind_keys(cx);
cx.bind_keys([
gpui::KeyBinding::new("tab", FocusNext, None),
gpui::KeyBinding::new("shift-tab", FocusPrev, None),
]);
});
let (view, cx) = cx.add_window_view(|_, cx| Stage {
root: cx.focus_handle(),
part,
});
let root = view.read_with(cx, |stage, _| stage.root.clone());
let wakes = Rc::new(Cell::new(0));
let count = wakes.clone();
cx.update(|window, cx| {
window.focus(&root);
cx.observe(&view, move |_, _| count.set(count.get() + 1))
.detach();
});
settle(cx);
(wakes, cx)
}
pub(super) fn press(key: &str, cx: &mut VisualTestContext) {
cx.simulate_keystrokes(key);
settle(cx);
cx.simulate_event(KeyUpEvent {
keystroke: Keystroke::parse(key).expect("a key"),
});
settle(cx);
}
fn wait(ms: u64, cx: &mut VisualTestContext) {
cx.executor().advance_clock(Duration::from_millis(ms));
settle(cx);
}
pub(super) fn shown(selector: &'static str, cx: &mut VisualTestContext) -> bool {
cx.debug_bounds(selector).is_some()
}
fn watch() -> AnyElement {
Stopwatch::new("watch").into_any_element()
}
/// Start runs the time on the executor's clock, Lap lists the laps newest first, Pause holds it, and Reset clears it and hands focus to Start.
#[gpui::test]
fn a_stopwatch_runs_laps_holds_and_resets(cx: &mut TestAppContext) {
let (_, cx) = stage(watch, cx);
press("tab", cx);
press("enter", cx);
wait(1_100, cx);
assert!(shown("stopwatch-00:01.1", cx), "running");
press("tab", cx);
press("enter", cx);
wait(1_200, cx);
press("enter", cx);
let row = |selector: &'static str, cx: &mut VisualTestContext| {
cx.debug_bounds(selector)
.unwrap_or_else(|| panic!("no row {selector}"))
};
assert!(
row("lap-2-00:01.2-00:02.3", cx).origin.y < row("lap-1-00:01.1-00:01.1", cx).origin.y,
"each lap's own time and total, the newest on top"
);
press("shift-tab", cx);
press("enter", cx);
wait(2_000, cx);
assert!(
shown("stopwatch-00:02.3", cx) && !shown("stopwatch-00:04.3", cx),
"Pause, on the same button, holds it"
);
press("tab", cx);
press("enter", cx);
press("enter", cx);
wait(300, cx);
assert!(
shown("stopwatch-00:00.3", cx),
"Lap, now Reset, cleared it and handed focus to Start"
);
}
/// The ticker wakes the stopwatch each tenth while it runs, and not at all once paused.
#[gpui::test]
fn a_running_stopwatch_wakes_each_tenth_and_rests_paused(cx: &mut TestAppContext) {
let (wakes, cx) = stage(watch, cx);
press("tab", cx);
press("enter", cx);
let started = wakes.get();
for _ in 0..12 {
wait(100, cx);
}
assert!(
wakes.get() - started >= 12,
"woke {} times",
wakes.get() - started
);
press("enter", cx);
let paused = wakes.get();
for _ in 0..10 {
wait(100, cx);
}
assert_eq!(wakes.get(), paused, "paused, it sleeps");
}
/// A clock on the wall clock wakes its view within each second.
#[gpui::test]
fn a_clock_ticks_each_second(cx: &mut TestAppContext) {
let (wakes, cx) = stage(
|| Clock::new("clock").zone(TimeZone::UTC).into_any_element(),
cx,
);
for second in 1..=2 {
let before = wakes.get();
wait(1_000, cx);
assert!(wakes.get() > before, "no tick in second {second}");
}
}
fn clocks() -> AnyElement {
let now: Timestamp = "2026-09-28T03:00:00Z".parse().expect("a moment");
let zone = |name: &str| TimeZone::get(name).expect("a zone");
div()
.child(
Clock::new("clock")
.zone(zone("Europe/Paris"))
.now(now)
.label("Paris"),
)
.child(
WorldClock::new(
"world",
[
("Tokyo", zone("Asia/Tokyo")),
("Kolkata", zone("Asia/Kolkata")),
],
)
.home(zone("America/New_York"))
.now(now),
)
.into_any_element()
}
/// Clocks shown at the owner's moment draw without a ticker.
#[gpui::test]
fn clocks_draw_at_the_owners_moment(cx: &mut TestAppContext) {
let (wakes, cx) = stage(clocks, cx);
let before = wakes.get();
wait(5_000, cx);
assert_eq!(wakes.get(), before, "nothing woke them");
}