use std::cell::RefCell; use std::rc::Rc; use std::time::Duration; use gpui::{AppContext as _, TestAppContext}; use gpui_native_core::toast::{Toast, ToastAction, Toasts}; use gpui_native_theme::Theme; fn toasts_store() -> (TestAppContext, gpui::Entity) { let cx = TestAppContext::single(); cx.update(Theme::init); let toasts = cx.update(|cx| cx.new(|_| Toasts::default())); (cx, toasts) } #[test] fn push_raises_a_toast_and_the_timer_fades_it_out() { let (cx, toasts) = toasts_store(); let id = cx.update(|cx| toasts.update(cx, |toasts, cx| toasts.push(Toast::new("Saved"), cx))); assert_eq!(cx.update(|cx| toasts.read(cx).count()), 1); // The default lifetime is five seconds; the fade-out follows it. cx.executor().advance_clock(Toast::DURATION); cx.run_until_parked(); assert_eq!(cx.update(|cx| toasts.read(cx).count()), 1, "fading out"); cx.executor() .advance_clock(std::time::Duration::from_secs(1)); cx.run_until_parked(); assert_eq!(cx.update(|cx| toasts.read(cx).count()), 0); let _ = id; } #[test] fn pushing_replaces_the_toast_on_screen() { let (cx, toasts) = toasts_store(); cx.update(|cx| { toasts.update(cx, |toasts, cx| toasts.push(Toast::new("first"), cx)); }); cx.update(|cx| { toasts.update(cx, |toasts, cx| toasts.push(Toast::new("second"), cx)); }); // One showing, one fading out: never two current toasts. let (current, total) = cx.update(|cx| { toasts.read_with(cx, |toasts, _| { (toasts.current().map(|entry| entry.id), toasts.count()) }) }); assert_eq!(current, Some(1)); assert_eq!(total, 2); cx.executor() .advance_clock(std::time::Duration::from_secs(6)); cx.run_until_parked(); assert_eq!(cx.update(|cx| toasts.read(cx).count()), 0); } #[test] fn sticky_toasts_ignore_the_timer_until_dismissed() { let (cx, toasts) = toasts_store(); let id = cx.update(|cx| { toasts.update(cx, |toasts, cx| { toasts.push(Toast::new("Sticky").duration(Duration::ZERO), cx) }) }); cx.executor().advance_clock(Toast::DURATION * 4); cx.run_until_parked(); assert_eq!(cx.update(|cx| toasts.read(cx).count()), 1); cx.update(|cx| { toasts.update(cx, |toasts, cx| toasts.dismiss(id, cx)); }); assert_eq!(cx.update(|cx| toasts.read(cx).count()), 1, "fading out"); cx.executor() .advance_clock(std::time::Duration::from_secs(1)); cx.run_until_parked(); assert_eq!(cx.update(|cx| toasts.read(cx).count()), 0); } #[test] fn dismiss_runs_the_handler_only_after_the_fade_completes() { let (cx, toasts) = toasts_store(); let dismissed = Rc::new(std::cell::RefCell::new(Vec::<&'static str>::new())); cx.update(|cx| { let first = dismissed.clone(); toasts.update(cx, |toasts, cx| { toasts.push( Toast::new("first").on_dismiss(move |_| record(&first, "first")), cx, ); }); }); cx.update(|cx| { toasts.update(cx, |toasts, cx| toasts.dismiss(0, cx)); }); assert!( dismissed.borrow().is_empty(), "the handler waits out the fade" ); cx.executor() .advance_clock(std::time::Duration::from_secs(1)); cx.run_until_parked(); assert_eq!(*dismissed.borrow(), vec!["first"]); cx.update(|cx| { toasts.update(cx, |toasts, cx| toasts.clear(cx)); }); cx.executor() .advance_clock(std::time::Duration::from_secs(1)); cx.run_until_parked(); assert_eq!(cx.update(|cx| toasts.read(cx).count()), 0); fn record(sink: &Rc>>, what: &'static str) { sink.borrow_mut().push(what); } } /// Dismissing by any other path never runs the action: only a click on the /// action itself does (the view wires the two together). #[test] fn dismissal_does_not_run_the_action() { let (cx, toasts) = toasts_store(); let ran = Rc::new(std::cell::RefCell::new(false)); let dismissed = Rc::new(std::cell::RefCell::new(false)); cx.update(|cx| { let action_ran = ran.clone(); let toast_dismissed = dismissed.clone(); toasts.update(cx, |toasts, cx| { toasts.push( Toast::new("Draft deleted") .action(ToastAction::new("Undo", move |_| { *action_ran.borrow_mut() = true; })) .on_dismiss(move |_| *toast_dismissed.borrow_mut() = true), cx, ); }); }); cx.update(|cx| { toasts.update(cx, |toasts, cx| toasts.dismiss(0, cx)); }); assert!(!*ran.borrow(), "dismiss is not the action"); cx.executor() .advance_clock(std::time::Duration::from_secs(1)); cx.run_until_parked(); assert!( *dismissed.borrow(), "on_dismiss still reports the dismissal" ); assert!( *dismissed.borrow(), "on_dismiss still reports the dismissal" ); }