feat: add spinner component
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
use std::{cell::RefCell, rc::Rc, time::Duration};
|
||||
|
||||
use gpui::prelude::FluentBuilder as _;
|
||||
use gpui::{
|
||||
Context, IntoElement, ParentElement as _, Render, Styled as _, TestAppContext, Window, div,
|
||||
};
|
||||
use gpui_native_core::spinner::{Spinner, SpinnerVariant};
|
||||
use gpui_native_theme::Theme;
|
||||
|
||||
struct SpinnerHost {
|
||||
variant: SpinnerVariant,
|
||||
determinate: bool,
|
||||
renders: Rc<RefCell<usize>>,
|
||||
}
|
||||
|
||||
impl Render for SpinnerHost {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
*self.renders.borrow_mut() += 1;
|
||||
let spinner = Spinner::new("test-spinner")
|
||||
.variant(self.variant)
|
||||
.when(self.determinate, |spinner| spinner.value(0.4));
|
||||
div().size_full().child(spinner)
|
||||
}
|
||||
}
|
||||
|
||||
fn host_window(
|
||||
variant: SpinnerVariant,
|
||||
determinate: bool,
|
||||
reduce_motion: bool,
|
||||
) -> (TestAppContext, Rc<RefCell<usize>>) {
|
||||
let mut cx = TestAppContext::single();
|
||||
cx.update(|cx| {
|
||||
Theme::init(cx);
|
||||
if reduce_motion {
|
||||
cx.set_reduce_motion(true);
|
||||
}
|
||||
});
|
||||
let renders = Rc::new(RefCell::new(0));
|
||||
let host_renders = renders.clone();
|
||||
cx.add_window(move |_, _| SpinnerHost {
|
||||
variant,
|
||||
determinate,
|
||||
renders: host_renders,
|
||||
});
|
||||
cx.run_until_parked();
|
||||
(cx, renders)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spinning_repaints_on_the_animation_clock() {
|
||||
let (cx, renders) = host_window(SpinnerVariant::Ring, false, false);
|
||||
assert_eq!(*renders.borrow(), 1);
|
||||
|
||||
// The 60fps cap drives re-renders with timers, not frame callbacks.
|
||||
cx.executor().advance_clock(Duration::from_millis(100));
|
||||
cx.run_until_parked();
|
||||
assert!(
|
||||
*renders.borrow() > 1,
|
||||
"the spinner must repaint while spinning"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn determinate_paints_one_static_frame() {
|
||||
let (cx, renders) = host_window(SpinnerVariant::Ring, true, false);
|
||||
assert_eq!(*renders.borrow(), 1);
|
||||
|
||||
cx.executor().advance_clock(Duration::from_millis(200));
|
||||
cx.run_until_parked();
|
||||
assert_eq!(*renders.borrow(), 1, "a determinate ring holds still");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reduce_motion_paints_one_static_frame() {
|
||||
let (cx, renders) = host_window(SpinnerVariant::Ring, false, true);
|
||||
assert_eq!(*renders.borrow(), 1);
|
||||
|
||||
cx.executor().advance_clock(Duration::from_millis(200));
|
||||
cx.run_until_parked();
|
||||
assert_eq!(*renders.borrow(), 1, "reduced motion must freeze the spin");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_icon_variant_repaints_while_spinning() {
|
||||
let (cx, renders) = host_window(SpinnerVariant::Icon, false, false);
|
||||
assert_eq!(*renders.borrow(), 1);
|
||||
|
||||
cx.executor().advance_clock(Duration::from_millis(100));
|
||||
cx.run_until_parked();
|
||||
assert!(
|
||||
*renders.borrow() > 1,
|
||||
"the icon must repaint while spinning"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_icon_variant_holds_still_when_determinate() {
|
||||
let (cx, renders) = host_window(SpinnerVariant::Icon, true, false);
|
||||
assert_eq!(*renders.borrow(), 1);
|
||||
|
||||
cx.executor().advance_clock(Duration::from_millis(200));
|
||||
cx.run_until_parked();
|
||||
assert_eq!(*renders.borrow(), 1, "a held icon must not animate");
|
||||
}
|
||||
Reference in New Issue
Block a user