use crate::hook_context::{HookContext, clear_hook_context, set_hook_context};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
pub static TEST_MUTEX: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
thread_local! {
static COMPONENT_CONTEXTS: RefCell<HashMap<&'static str, Rc<HookContext>>> =
RefCell::new(HashMap::new());
}
pub fn with_component_id<F, R>(component_id: &'static str, test_fn: F) -> R
where
F: FnOnce(&Rc<HookContext>) -> R,
{
let context = COMPONENT_CONTEXTS.with(|contexts| {
let mut contexts = contexts.borrow_mut();
if let Some(existing_context) = contexts.get(component_id) {
existing_context.reset_hook_index();
existing_context.clone()
} else {
let new_context = Rc::new(HookContext::new());
contexts.insert(component_id, new_context.clone());
new_context
}
});
set_hook_context(context.clone());
let result = test_fn(&context);
clear_hook_context();
result
}
pub async fn with_async_component_id<F, Fut, R>(component_id: &'static str, test_fn: F) -> R
where
F: FnOnce(&Rc<HookContext>) -> Fut,
Fut: std::future::Future<Output = R>,
{
let context = COMPONENT_CONTEXTS.with(|contexts| {
let mut contexts = contexts.borrow_mut();
if let Some(existing_context) = contexts.get(component_id) {
existing_context.reset_hook_index();
existing_context.clone()
} else {
let new_context = Rc::new(HookContext::new());
contexts.insert(component_id, new_context.clone());
new_context
}
});
set_hook_context(context.clone());
let result = test_fn(&context).await;
clear_hook_context();
result
}
pub fn cleanup_component_contexts() {
COMPONENT_CONTEXTS.with(|contexts| {
contexts.borrow_mut().clear();
});
}
pub fn with_test_isolate<F, R>(test_fn: F) -> R
where
F: FnOnce() -> R,
{
cleanup_component_contexts();
struct CleanupGuard;
impl Drop for CleanupGuard {
fn drop(&mut self) {
cleanup_component_contexts();
}
}
let _guard = CleanupGuard;
test_fn()
}
pub async fn with_async_test_isolate<F, Fut, R>(test_fn: F) -> R
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = R>,
{
cleanup_component_contexts();
struct AsyncCleanupGuard;
impl Drop for AsyncCleanupGuard {
fn drop(&mut self) {
cleanup_component_contexts();
}
}
let _guard = AsyncCleanupGuard;
test_fn().await
}
pub fn with_hook_context<F, R>(test_fn: F) -> R
where
F: FnOnce(&Rc<HookContext>) -> R,
{
let context = Rc::new(HookContext::new());
set_hook_context(context.clone());
let result = test_fn(&context);
clear_hook_context();
result
}
pub async fn with_async_hook_context<F, Fut, R>(test_fn: F) -> R
where
F: FnOnce(&Rc<HookContext>) -> Fut,
Fut: std::future::Future<Output = R>,
{
let context = Rc::new(HookContext::new());
set_hook_context(context.clone());
let result = test_fn(&context).await;
clear_hook_context();
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{hook_context::get_hook_context, state::use_state};
#[test]
fn test_with_hook_context_basic() {
let result = with_hook_context(|context| {
assert_eq!(context.current_hook_index(), 0);
let (state, setter) = use_state(|| 42);
assert_eq!(state.get(), 42);
setter.set(100);
assert_eq!(state.get(), 100);
"test_result"
});
assert_eq!(result, "test_result");
assert!(get_hook_context().is_none());
}
#[test]
fn test_with_hook_context_isolation() {
with_hook_context(|_| {
let (state, setter) = use_state(|| 10);
setter.set(20);
assert_eq!(state.get(), 20);
});
with_hook_context(|_| {
let (state, _) = use_state(|| 10);
assert_eq!(state.get(), 10); });
}
#[test]
fn test_with_component_id_basic() {
let result = with_component_id("TestComponent", |context| {
assert_eq!(context.current_hook_index(), 0);
let (state, setter) = use_state(|| 5);
assert_eq!(state.get(), 5);
setter.set(15);
"component_result"
});
assert_eq!(result, "component_result");
}
#[test]
fn test_with_component_id_persistence() {
with_component_id("PersistentComponent", |_| {
let (state, setter) = use_state(|| 0);
assert_eq!(state.get(), 0);
setter.set(42);
});
with_component_id("PersistentComponent", |_| {
let (state, setter) = use_state(|| 0);
assert_eq!(state.get(), 42); setter.set(84);
});
with_component_id("PersistentComponent", |_| {
let (state, _) = use_state(|| 0);
assert_eq!(state.get(), 84);
});
}
#[test]
fn test_with_component_id_hook_counter_reset() {
with_component_id("CounterResetComponent", |context| {
let (state1, _) = use_state(|| 1);
let (state2, _) = use_state(|| 2);
assert_eq!(state1.get(), 1);
assert_eq!(state2.get(), 2);
assert_eq!(context.current_hook_index(), 2);
});
with_component_id("CounterResetComponent", |context| {
assert_eq!(context.current_hook_index(), 0);
let (state1, _) = use_state(|| 1);
let (state2, _) = use_state(|| 2);
assert_eq!(state1.get(), 1);
assert_eq!(state2.get(), 2);
});
}
#[test]
fn test_with_component_id_different_components() {
with_component_id("ComponentA", |_| {
let (state, setter) = use_state(|| 100);
setter.set(200);
assert_eq!(state.get(), 200);
});
with_component_id("ComponentB", |_| {
let (state, setter) = use_state(|| 300);
setter.set(400);
assert_eq!(state.get(), 400);
});
with_component_id("ComponentA", |_| {
let (state, _) = use_state(|| 100);
assert_eq!(state.get(), 200); });
with_component_id("ComponentB", |_| {
let (state, _) = use_state(|| 300);
assert_eq!(state.get(), 400); });
}
#[test]
fn test_cleanup_component_contexts() {
with_component_id("CleanupTest1", |_| {
let (_, setter) = use_state(|| 1);
setter.set(10);
});
with_component_id("CleanupTest2", |_| {
let (_, setter) = use_state(|| 2);
setter.set(20);
});
with_component_id("CleanupTest1", |_| {
let (state, _) = use_state(|| 1);
assert_eq!(state.get(), 10);
});
cleanup_component_contexts();
with_component_id("CleanupTest1", |_| {
let (state, _) = use_state(|| 1);
assert_eq!(state.get(), 1); });
}
#[test]
fn test_with_test_isolate_cleanup() {
with_component_id("IsolateTest", |_| {
let (_, setter) = use_state(|| 50);
setter.set(60);
});
with_component_id("IsolateTest", |_| {
let (state, _) = use_state(|| 50);
assert_eq!(state.get(), 60);
});
let result = with_test_isolate(|| {
with_component_id("IsolateTest", |_| {
let (state, setter) = use_state(|| 50);
assert_eq!(state.get(), 50); setter.set(70);
});
"isolated_result"
});
assert_eq!(result, "isolated_result");
with_component_id("IsolateTest", |_| {
let (state, _) = use_state(|| 50);
assert_eq!(state.get(), 50); });
}
#[test]
fn test_with_test_isolate_panic_cleanup() {
with_component_id("PanicTest", |_| {
let (_, setter) = use_state(|| 80);
setter.set(90);
});
let panic_result = std::panic::catch_unwind(|| {
with_test_isolate(|| {
with_component_id("PanicTest", |_| {
let (_, setter) = use_state(|| 80);
setter.set(95);
});
panic!("Test panic");
})
});
assert!(panic_result.is_err());
with_component_id("PanicTest", |_| {
let (state, _) = use_state(|| 80);
assert_eq!(state.get(), 80); });
}
#[test]
fn test_multiple_hooks_in_component() {
with_component_id("MultiHookComponent", |_| {
let (count, set_count) = use_state(|| 0);
let (name, set_name) = use_state(|| "initial".to_string());
let (flag, set_flag) = use_state(|| false);
assert_eq!(count.get(), 0);
assert_eq!(name.get(), "initial");
assert!(!flag.get());
set_count.set(42);
set_name.set("updated".to_string());
set_flag.set(true);
});
with_component_id("MultiHookComponent", |_| {
let (count, _) = use_state(|| 0);
let (name, _) = use_state(|| "initial".to_string());
let (flag, _) = use_state(|| false);
assert_eq!(count.get(), 42);
assert_eq!(name.get(), "updated");
assert!(flag.get());
});
}
#[tokio::test]
async fn test_with_async_hook_context() {
let result = with_async_hook_context(|context| {
let context = context.clone();
async move {
assert_eq!(context.current_hook_index(), 0);
let (state, setter) = use_state(|| 123);
assert_eq!(state.get(), 123);
setter.set(456);
assert_eq!(state.get(), 456);
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
"async_result"
}
})
.await;
assert_eq!(result, "async_result");
assert!(get_hook_context().is_none());
}
#[tokio::test]
async fn test_with_async_component_id() {
let result = with_async_component_id("AsyncComponent", |context| {
let context = context.clone();
async move {
assert_eq!(context.current_hook_index(), 0);
let (state, setter) = use_state(|| 777);
assert_eq!(state.get(), 777);
setter.set(888);
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
"async_component_result"
}
})
.await;
assert_eq!(result, "async_component_result");
with_async_component_id("AsyncComponent", |_| {
async move {
let (state, _) = use_state(|| 777);
assert_eq!(state.get(), 888); }
})
.await;
}
#[tokio::test]
async fn test_with_async_test_isolate() {
with_component_id("AsyncIsolateTest", |_| {
let (_, setter) = use_state(|| 999);
setter.set(1000);
});
let result = with_async_test_isolate(|| async {
with_component_id("AsyncIsolateTest", |_| {
let (state, setter) = use_state(|| 999);
assert_eq!(state.get(), 999);
setter.set(1001);
});
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
"async_isolate_result"
})
.await;
assert_eq!(result, "async_isolate_result");
with_component_id("AsyncIsolateTest", |_| {
let (state, _) = use_state(|| 999);
assert_eq!(state.get(), 999);
});
}
}