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>, } impl Render for SpinnerHost { fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> 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>) { 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"); }