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.
This commit is contained in:
1 parent
d1345cd14e
commit
73f3399cea
13 files changed
+1023
-7
No files matched your search
@@ -0,0 +1,196 @@
|
||||
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");
|
||||
}
|
||||
Reference in new issue
Block a user