use std::cell::{Cell, RefCell};
use std::rc::Rc;
use gpui::{div, prelude::*, Context, Render, TestAppContext, Window};
use herogpui_theme::{
set_reduce_motion, set_theme, toggle_light_dark, use_theme, ActiveTheme, Theme, ThemeProvider,
};
struct Probe {
frames: Rc<Cell<u32>>,
seen_theme: Rc<RefCell<String>>,
}
impl Render for Probe {
fn render(&mut self, _window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
self.frames.set(self.frames.get() + 1);
*self.seen_theme.borrow_mut() = cx.theme().id.to_string();
div()
}
}
#[gpui::test]
fn theme_mutations_repaint_every_open_window(cx: &mut TestAppContext) {
cx.update(ThemeProvider::init);
let frames_a = Rc::new(Cell::new(0));
let frames_b = Rc::new(Cell::new(0));
let seen_a = Rc::new(RefCell::new(String::new()));
let seen_b = Rc::new(RefCell::new(String::new()));
let _window_a = cx.add_window_view(|_, _| Probe {
frames: frames_a.clone(),
seen_theme: seen_a.clone(),
});
let _window_b = cx.add_window_view(|_, _| Probe {
frames: frames_b.clone(),
seen_theme: seen_b.clone(),
});
assert_eq!(seen_a.borrow().as_str(), "light");
assert_eq!(seen_b.borrow().as_str(), "light");
let baseline_a = frames_a.get();
let baseline_b = frames_b.get();
cx.update(|cx| use_theme("dark", cx).unwrap());
assert_eq!(
frames_a.get(),
baseline_a + 1,
"window A must repaint after use_theme"
);
assert_eq!(
frames_b.get(),
baseline_b + 1,
"window B must repaint after use_theme"
);
assert_eq!(seen_a.borrow().as_str(), "dark");
assert_eq!(seen_b.borrow().as_str(), "dark");
cx.update(|cx| set_theme(Theme::builder("dusk", Theme::dark()).build(), cx));
assert_eq!(
frames_a.get(),
baseline_a + 2,
"window A must repaint after set_theme"
);
assert_eq!(
frames_b.get(),
baseline_b + 2,
"window B must repaint after set_theme"
);
assert_eq!(seen_a.borrow().as_str(), "dusk");
assert_eq!(seen_b.borrow().as_str(), "dusk");
cx.update(|cx| set_reduce_motion(true, cx));
assert_eq!(
frames_a.get(),
baseline_a + 3,
"window A must repaint after set_reduce_motion"
);
assert_eq!(
frames_b.get(),
baseline_b + 3,
"window B must repaint after set_reduce_motion"
);
assert_eq!(seen_a.borrow().as_str(), "dusk");
assert_eq!(seen_b.borrow().as_str(), "dusk");
assert!(
cx.read(ActiveTheme::reduce_motion),
"the preference itself must be readable app-wide"
);
cx.update(toggle_light_dark);
assert_eq!(
frames_a.get(),
baseline_a + 4,
"window A must repaint after toggle_light_dark"
);
assert_eq!(
frames_b.get(),
baseline_b + 4,
"window B must repaint after toggle_light_dark"
);
assert_eq!(seen_a.borrow().as_str(), "light");
assert_eq!(seen_b.borrow().as_str(), "light");
}
#[gpui::test]
fn use_theme_rejects_an_unknown_id_without_panicking_or_repainting(cx: &mut TestAppContext) {
cx.update(ThemeProvider::init);
let frames = Rc::new(Cell::new(0));
let seen = Rc::new(RefCell::new(String::new()));
let _window = cx.add_window_view(|_, _| Probe {
frames: frames.clone(),
seen_theme: seen.clone(),
});
let baseline = frames.get();
let error = cx
.update(|cx| use_theme("Dark", cx))
.expect_err("an unregistered id must be refused");
assert_eq!(error.id.as_ref(), "Dark");
assert!(error.to_string().contains("\"Dark\""));
assert_eq!(cx.read(|cx| cx.theme().id.to_string()), "light");
assert_eq!(frames.get(), baseline, "a refused switch must not repaint");
cx.update(|cx| {
assert!(cx.global_mut::<ThemeProvider>().set_active("nope").is_err());
assert_eq!(ThemeProvider::get(cx).active_id().as_ref(), "light");
assert!(ThemeProvider::get(cx).contains("dark"));
});
cx.update(|cx| use_theme("dark", cx)).unwrap();
assert_eq!(seen.borrow().as_str(), "dark");
}