use ferro_theme::Theme;
use std::sync::Arc;
use tokio::sync::RwLock;
tokio::task_local! {
static CURRENT_THEME: Arc<RwLock<Option<Arc<Theme>>>>;
}
pub fn current_theme() -> Option<Arc<Theme>> {
CURRENT_THEME
.try_with(|ctx| ctx.try_read().ok().and_then(|guard| guard.clone()))
.ok()
.flatten()
}
pub(crate) fn theme_scope() -> Arc<RwLock<Option<Arc<Theme>>>> {
Arc::new(RwLock::new(None))
}
pub(crate) async fn with_theme_scope<F, R>(ctx: Arc<RwLock<Option<Arc<Theme>>>>, f: F) -> R
where
F: std::future::Future<Output = R>,
{
CURRENT_THEME.scope(ctx, f).await
}
#[cfg(test)]
mod tests {
use super::*;
use ferro_theme::Theme;
fn make_theme() -> Arc<Theme> {
Arc::new(Theme::default_theme())
}
#[test]
fn current_theme_returns_none_outside_scope() {
let result = current_theme();
assert!(result.is_none());
}
#[tokio::test]
async fn current_theme_returns_some_within_scope() {
let scope = theme_scope();
{
let mut guard = scope.write().await;
*guard = Some(make_theme());
}
let result = with_theme_scope(scope, async { current_theme() }).await;
assert!(result.is_some());
assert!(result.unwrap().css.contains("--color-primary"));
}
#[test]
fn theme_scope_creates_arc_rwlock_initialized_to_none() {
let scope = theme_scope();
let guard = scope.try_read().unwrap();
assert!(guard.is_none());
}
#[tokio::test]
async fn with_theme_scope_returns_none_outside_and_some_inside() {
let scope = theme_scope();
{
let mut guard = scope.write().await;
*guard = Some(make_theme());
}
let inside = with_theme_scope(scope, async { current_theme() }).await;
assert!(inside.is_some());
let outside = current_theme();
assert!(outside.is_none());
}
}