floem_reactive/
context.rs1use std::any::{Any, TypeId};
2
3use crate::runtime::RUNTIME;
4
5pub struct Context;
23
24impl Context {
25 pub fn provide<T>(value: T)
43 where
44 T: Clone + 'static,
45 {
46 let ty = TypeId::of::<T>();
47
48 RUNTIME.with(|runtime| {
49 let scope = *runtime.current_scope.borrow();
50 runtime
51 .scope_contexts
52 .borrow_mut()
53 .entry(scope)
54 .or_default()
55 .insert(ty, Box::new(value) as Box<dyn Any>);
56 });
57 }
58
59 pub fn get<T>() -> Option<T>
80 where
81 T: Clone + 'static,
82 {
83 let ty = TypeId::of::<T>();
84 RUNTIME.with(|runtime| {
85 let mut scope = *runtime.current_scope.borrow();
86 let scope_contexts = runtime.scope_contexts.borrow();
87 let parents = runtime.parents.borrow();
88
89 loop {
90 if let Some(contexts) = scope_contexts.get(&scope) {
91 if let Some(value) = contexts.get(&ty) {
92 return value.downcast_ref::<T>().cloned();
93 }
94 }
95 match parents.get(&scope) {
97 Some(&parent) => scope = parent,
98 None => return None,
99 }
100 }
101 })
102 }
103}
104
105#[deprecated(
126 since = "0.2.0",
127 note = "Use Context::get instead; this will be removed in a future release"
128)]
129pub fn use_context<T>() -> Option<T>
130where
131 T: Clone + 'static,
132{
133 let ty = TypeId::of::<T>();
134 RUNTIME.with(|runtime| {
135 let mut scope = *runtime.current_scope.borrow();
136 let scope_contexts = runtime.scope_contexts.borrow();
137 let parents = runtime.parents.borrow();
138
139 loop {
140 if let Some(contexts) = scope_contexts.get(&scope) {
141 if let Some(value) = contexts.get(&ty) {
142 return value.downcast_ref::<T>().cloned();
143 }
144 }
145 match parents.get(&scope) {
147 Some(&parent) => scope = parent,
148 None => return None,
149 }
150 }
151 })
152}
153
154#[deprecated(
178 since = "0.2.0",
179 note = "Use Context::provide instead; this will be removed in a future release"
180)]
181pub fn provide_context<T>(value: T)
182where
183 T: Clone + 'static,
184{
185 let ty = TypeId::of::<T>();
186
187 RUNTIME.with(|runtime| {
188 let scope = *runtime.current_scope.borrow();
189 runtime
190 .scope_contexts
191 .borrow_mut()
192 .entry(scope)
193 .or_default()
194 .insert(ty, Box::new(value) as Box<dyn Any>);
195 });
196}
197
198#[cfg(test)]
199mod tests {
200 use crate::scope::Scope;
201
202 use super::*;
203
204 #[test]
205 fn context_in_same_scope() {
206 let scope = Scope::new();
207 scope.enter(|| {
208 provide_context(42i32);
209 assert_eq!(use_context::<i32>(), Some(42));
210 });
211 }
212
213 #[test]
214 fn context_inherited_from_parent() {
215 let parent = Scope::new();
216 parent.enter(|| {
217 provide_context(42i32);
218
219 let child = parent.create_child();
220 child.enter(|| {
221 assert_eq!(use_context::<i32>(), Some(42));
223 });
224 });
225 }
226
227 #[test]
228 fn context_shadowing_in_child() {
229 let parent = Scope::new();
230 parent.enter(|| {
231 provide_context(42i32);
232
233 let child = parent.create_child();
234 child.enter(|| {
235 provide_context(100i32);
237 assert_eq!(use_context::<i32>(), Some(100));
238 });
239
240 assert_eq!(use_context::<i32>(), Some(42));
242 });
243 }
244
245 #[test]
246 fn sibling_scopes_isolated() {
247 let parent = Scope::new();
248 parent.enter(|| {
249 provide_context(0i32);
250
251 let child1 = parent.create_child();
252 let child2 = parent.create_child();
253
254 child1.enter(|| {
255 provide_context(1i32);
256 assert_eq!(use_context::<i32>(), Some(1));
257 });
258
259 child2.enter(|| {
260 provide_context(2i32);
261 assert_eq!(use_context::<i32>(), Some(2));
262 });
263
264 child1.enter(|| {
266 assert_eq!(use_context::<i32>(), Some(1));
267 });
268
269 child2.enter(|| {
270 assert_eq!(use_context::<i32>(), Some(2));
271 });
272 });
273 }
274
275 #[test]
276 fn context_cleaned_up_on_dispose() {
277 let parent = Scope::new();
278 let child_value = parent.enter(|| {
279 provide_context(42i32);
280
281 let child = parent.create_child();
282 let value = child.enter(|| {
283 provide_context(100i32);
284 use_context::<i32>()
285 });
286
287 child.dispose();
289
290 value
291 });
292
293 assert_eq!(child_value, Some(100));
294
295 parent.enter(|| {
297 assert_eq!(use_context::<i32>(), Some(42));
298 });
299 }
300
301 #[test]
302 fn deeply_nested_context_lookup() {
303 let root = Scope::new();
304 root.enter(|| {
305 provide_context(String::from("root"));
306
307 let level1 = root.create_child();
308 level1.enter(|| {
309 let level2 = level1.create_child();
310 level2.enter(|| {
311 let level3 = level2.create_child();
312 level3.enter(|| {
313 assert_eq!(use_context::<String>(), Some(String::from("root")));
315 });
316 });
317 });
318 });
319 }
320
321 #[test]
322 fn multiple_context_types() {
323 let scope = Scope::new();
324 scope.enter(|| {
325 provide_context(42i32);
326 provide_context(String::from("hello"));
327 provide_context(3.15f64);
328
329 assert_eq!(use_context::<i32>(), Some(42));
330 assert_eq!(use_context::<String>(), Some(String::from("hello")));
331 assert_eq!(use_context::<f64>(), Some(3.15));
332 assert_eq!(use_context::<bool>(), None);
333 });
334 }
335
336 #[test]
337 fn context_not_found_returns_none() {
338 let scope = Scope::new();
339 scope.enter(|| {
340 assert_eq!(use_context::<i32>(), None);
342 assert_eq!(use_context::<String>(), None);
343 });
344 }
345
346 #[test]
347 fn overwrite_context_in_same_scope() {
348 let scope = Scope::new();
349 scope.enter(|| {
350 provide_context(42i32);
351 assert_eq!(use_context::<i32>(), Some(42));
352
353 provide_context(100i32);
355 assert_eq!(use_context::<i32>(), Some(100));
356 });
357 }
358
359 #[test]
360 fn parent_disposal_cleans_up_children() {
361 let parent = Scope::new();
362 let child = parent.create_child();
363
364 parent.enter(|| {
365 provide_context(42i32);
366 });
367
368 child.enter(|| {
369 provide_context(100i32);
370 });
371
372 parent.enter(|| {
374 assert_eq!(use_context::<i32>(), Some(42));
375 });
376 child.enter(|| {
377 assert_eq!(use_context::<i32>(), Some(100));
378 });
379
380 parent.dispose();
382
383 RUNTIME.with(|runtime| {
385 assert!(runtime.scope_contexts.borrow().get(&parent.0).is_none());
386 assert!(runtime.scope_contexts.borrow().get(&child.0).is_none());
387 assert!(runtime.parents.borrow().get(&child.0).is_none());
388 });
389 }
390
391 #[test]
392 fn context_shadowing_at_multiple_levels() {
393 let root = Scope::new();
394 root.enter(|| {
395 provide_context(String::from("root"));
396
397 let level1 = root.create_child();
398 level1.enter(|| {
399 assert_eq!(use_context::<String>(), Some(String::from("root")));
401
402 let level2 = level1.create_child();
403 level2.enter(|| {
404 provide_context(String::from("level2"));
406 assert_eq!(use_context::<String>(), Some(String::from("level2")));
407
408 let level3 = level2.create_child();
409 level3.enter(|| {
410 assert_eq!(use_context::<String>(), Some(String::from("level2")));
412 });
413 });
414
415 assert_eq!(use_context::<String>(), Some(String::from("root")));
417 });
418 });
419 }
420
421 #[test]
422 fn child_created_via_set_scope_inherits_context() {
423 use crate::id::Id;
424
425 let parent = Scope::new();
426 parent.enter(|| {
427 provide_context(42i32);
428
429 let child_id = Id::next();
431 child_id.set_scope();
432
433 RUNTIME.with(|runtime| {
435 let parents = runtime.parents.borrow();
436 assert_eq!(parents.get(&child_id), Some(&parent.0));
437 });
438 });
439 }
440
441 #[test]
442 fn orphan_scope_has_no_parent_context() {
443 let orphan = Scope::new();
445 orphan.enter(|| {
446 assert_eq!(use_context::<i32>(), None);
448
449 provide_context(42i32);
451 assert_eq!(use_context::<i32>(), Some(42));
452 });
453 }
454
455 #[test]
456 fn newtype_wrappers_are_distinct_types() {
457 #[derive(Clone, PartialEq, Debug)]
458 struct UserId(i32);
459
460 #[derive(Clone, PartialEq, Debug)]
461 struct PostId(i32);
462
463 let scope = Scope::new();
464 scope.enter(|| {
465 provide_context(UserId(1));
466 provide_context(PostId(2));
467
468 assert_eq!(use_context::<UserId>(), Some(UserId(1)));
470 assert_eq!(use_context::<PostId>(), Some(PostId(2)));
471
472 assert_eq!(use_context::<i32>(), None);
474 });
475 }
476
477 #[test]
478 fn context_with_rw_signal() {
479 use crate::{create_rw_signal, SignalGet, SignalUpdate};
480
481 let scope = Scope::new();
482 scope.enter(|| {
483 let signal = create_rw_signal(42);
484 provide_context(signal);
485
486 let retrieved = use_context::<crate::signal::RwSignal<i32>>().unwrap();
488 assert_eq!(retrieved.get_untracked(), 42);
489
490 retrieved.set(100);
493 assert_eq!(signal.get_untracked(), 100);
494 });
495 }
496
497 #[test]
498 fn dispose_middle_of_hierarchy() {
499 let root = Scope::new();
500 let middle = root.create_child();
501 let leaf = middle.create_child();
502
503 root.enter(|| provide_context(String::from("root")));
504 middle.enter(|| provide_context(String::from("middle")));
505 leaf.enter(|| provide_context(String::from("leaf")));
506
507 RUNTIME.with(|runtime| {
509 assert!(runtime.scope_contexts.borrow().contains_key(&root.0));
510 assert!(runtime.scope_contexts.borrow().contains_key(&middle.0));
511 assert!(runtime.scope_contexts.borrow().contains_key(&leaf.0));
512 });
513
514 middle.dispose();
516
517 RUNTIME.with(|runtime| {
518 assert!(runtime.scope_contexts.borrow().contains_key(&root.0));
520 assert!(!runtime.scope_contexts.borrow().contains_key(&middle.0));
522 assert!(!runtime.scope_contexts.borrow().contains_key(&leaf.0));
523 assert!(!runtime.parents.borrow().contains_key(&leaf.0));
525 });
526
527 root.enter(|| {
529 assert_eq!(use_context::<String>(), Some(String::from("root")));
530 });
531 }
532
533 #[test]
534 fn create_child_outside_enter() {
535 let parent = Scope::new();
536
537 parent.enter(|| {
539 provide_context(42i32);
540 });
541
542 let child = parent.create_child();
544
545 child.enter(|| {
547 assert_eq!(use_context::<i32>(), Some(42));
548 });
549 }
550
551 #[test]
552 fn context_not_visible_to_parent() {
553 let parent = Scope::new();
554 let child = parent.create_child();
555
556 child.enter(|| {
558 provide_context(100i32);
559 });
560
561 parent.enter(|| {
563 assert_eq!(use_context::<i32>(), None);
564 });
565 }
566
567 #[test]
568 fn scope_reentry_preserves_context() {
569 let scope = Scope::new();
570
571 scope.enter(|| {
572 provide_context(42i32);
573 });
574
575 scope.enter(|| {
577 assert_eq!(use_context::<i32>(), Some(42));
579
580 provide_context(100i32);
582 });
583
584 scope.enter(|| {
586 assert_eq!(use_context::<i32>(), Some(100));
587 });
588 }
589
590 #[test]
591 fn nested_enter_same_scope() {
592 let scope = Scope::new();
593
594 scope.enter(|| {
595 provide_context(42i32);
596
597 scope.enter(|| {
599 assert_eq!(use_context::<i32>(), Some(42));
600 provide_context(100i32);
601 });
602
603 assert_eq!(use_context::<i32>(), Some(100));
606 });
607 }
608
609 #[test]
610 fn context_with_enter_child() {
611 let parent = Scope::new();
612
613 parent.enter(|| {
614 provide_context(42i32);
615
616 let make_child = parent.enter_child(|multiplier: i32| {
618 let val = use_context::<i32>().unwrap();
620 provide_context(String::from("child"));
622 val * multiplier
623 });
624
625 let (result, child_scope) = make_child(2);
627 assert_eq!(result, 84);
628
629 child_scope.enter(|| {
631 assert_eq!(use_context::<String>(), Some(String::from("child")));
632 assert_eq!(use_context::<i32>(), Some(42));
634 });
635
636 assert_eq!(use_context::<String>(), None);
638 });
639 }
640
641 #[test]
642 fn context_visible_in_effect() {
643 use crate::{create_effect, create_rw_signal, SignalGet};
644 use std::cell::Cell;
645 use std::rc::Rc;
646
647 let scope = Scope::new();
648 let seen_value = Rc::new(Cell::new(0i32));
649
650 scope.enter(|| {
651 provide_context(42i32);
652 let trigger = create_rw_signal(0);
653
654 let seen = seen_value.clone();
655 create_effect(move |_| {
656 trigger.get(); if let Some(val) = use_context::<i32>() {
659 seen.set(val);
660 }
661 });
662 });
663
664 assert_eq!(seen_value.get(), 42);
666 }
667
668 #[test]
669 fn zero_sized_type_as_context_marker() {
670 #[derive(Clone, Debug, PartialEq)]
672 struct DarkMode;
673
674 #[derive(Clone, Debug, PartialEq)]
675 struct DebugEnabled;
676
677 let scope = Scope::new();
678 scope.enter(|| {
679 provide_context(DarkMode);
681
682 assert!(use_context::<DarkMode>().is_some());
684 assert!(use_context::<DebugEnabled>().is_none());
685
686 let child = scope.create_child();
687 child.enter(|| {
688 assert!(use_context::<DarkMode>().is_some());
690
691 provide_context(DebugEnabled);
693 assert!(use_context::<DebugEnabled>().is_some());
694 });
695
696 assert!(use_context::<DebugEnabled>().is_none());
698 });
699 }
700
701 #[test]
703 fn context_struct_provide_and_get() {
704 let scope = Scope::new();
705 scope.enter(|| {
706 Context::provide(42i32);
707 Context::provide(String::from("hello"));
708
709 assert_eq!(Context::get::<i32>(), Some(42));
710 assert_eq!(Context::get::<String>(), Some(String::from("hello")));
711 assert_eq!(Context::get::<f64>(), None);
712 });
713 }
714
715 #[test]
716 fn context_struct_inheritance() {
717 let parent = Scope::new();
718 parent.enter(|| {
719 Context::provide(42i32);
720
721 let child = parent.create_child();
722 child.enter(|| {
723 assert_eq!(Context::get::<i32>(), Some(42));
725
726 Context::provide(100i32);
728 assert_eq!(Context::get::<i32>(), Some(100));
729 });
730
731 assert_eq!(Context::get::<i32>(), Some(42));
733 });
734 }
735
736 #[test]
738 fn scope_provide_and_get_context() {
739 let scope = Scope::new();
740 scope.provide_context(42i32);
741 scope.provide_context(String::from("hello"));
742
743 assert_eq!(scope.get_context::<i32>(), Some(42));
744 assert_eq!(scope.get_context::<String>(), Some(String::from("hello")));
745 assert_eq!(scope.get_context::<f64>(), None);
746 }
747
748 #[test]
749 fn scope_context_inheritance() {
750 let parent = Scope::new();
751 parent.provide_context(42i32);
752
753 let child = parent.create_child();
754 assert_eq!(child.get_context::<i32>(), Some(42));
756
757 child.provide_context(100i32);
759 assert_eq!(child.get_context::<i32>(), Some(100));
760
761 assert_eq!(parent.get_context::<i32>(), Some(42));
763 }
764
765 #[test]
766 fn scope_context_not_visible_to_parent() {
767 let parent = Scope::new();
768 let child = parent.create_child();
769
770 child.provide_context(100i32);
771
772 assert_eq!(parent.get_context::<i32>(), None);
774 assert_eq!(child.get_context::<i32>(), Some(100));
775 }
776
777 #[test]
778 fn dispose_cleans_up_multiple_children() {
779 let parent = Scope::new();
780 let child1 = parent.create_child();
781 let child2 = parent.create_child();
782 let child3 = parent.create_child();
783
784 parent.provide_context(String::from("parent"));
785 child1.provide_context(String::from("child1"));
786 child2.provide_context(String::from("child2"));
787 child3.provide_context(String::from("child3"));
788
789 RUNTIME.with(|runtime| {
791 assert!(runtime.scope_contexts.borrow().contains_key(&parent.0));
792 assert!(runtime.scope_contexts.borrow().contains_key(&child1.0));
793 assert!(runtime.scope_contexts.borrow().contains_key(&child2.0));
794 assert!(runtime.scope_contexts.borrow().contains_key(&child3.0));
795 });
796
797 parent.dispose();
799
800 RUNTIME.with(|runtime| {
801 assert!(!runtime.scope_contexts.borrow().contains_key(&parent.0));
803 assert!(!runtime.scope_contexts.borrow().contains_key(&child1.0));
804 assert!(!runtime.scope_contexts.borrow().contains_key(&child2.0));
805 assert!(!runtime.scope_contexts.borrow().contains_key(&child3.0));
806 assert!(!runtime.parents.borrow().contains_key(&child1.0));
808 assert!(!runtime.parents.borrow().contains_key(&child2.0));
809 assert!(!runtime.parents.borrow().contains_key(&child3.0));
810 });
811 }
812
813 #[test]
814 fn dispose_parent_without_context_cleans_children_context() {
815 let parent = Scope::new();
816 let child1 = parent.create_child();
817 let child2 = parent.create_child();
818
819 child1.provide_context(String::from("child1"));
821 child2.provide_context(String::from("child2"));
822
823 RUNTIME.with(|runtime| {
825 assert!(!runtime.scope_contexts.borrow().contains_key(&parent.0));
826 assert!(runtime.scope_contexts.borrow().contains_key(&child1.0));
827 assert!(runtime.scope_contexts.borrow().contains_key(&child2.0));
828 });
829
830 parent.dispose();
832
833 RUNTIME.with(|runtime| {
834 assert!(!runtime.scope_contexts.borrow().contains_key(&child1.0));
835 assert!(!runtime.scope_contexts.borrow().contains_key(&child2.0));
836 assert!(!runtime.parents.borrow().contains_key(&child1.0));
837 assert!(!runtime.parents.borrow().contains_key(&child2.0));
838 });
839 }
840
841 #[test]
842 fn double_dispose_is_idempotent() {
843 let scope = Scope::new();
844 let child = scope.create_child();
845
846 scope.provide_context(42i32);
847 child.provide_context(100i32);
848
849 scope.dispose();
851
852 RUNTIME.with(|runtime| {
853 assert!(!runtime.scope_contexts.borrow().contains_key(&scope.0));
854 assert!(!runtime.scope_contexts.borrow().contains_key(&child.0));
855 });
856
857 scope.dispose();
859 child.dispose();
860
861 RUNTIME.with(|runtime| {
863 assert!(!runtime.scope_contexts.borrow().contains_key(&scope.0));
864 assert!(!runtime.scope_contexts.borrow().contains_key(&child.0));
865 });
866 }
867}