1use std::cell::Ref;
10use std::rc::Rc;
11
12use crate::feature::Dispatch;
13use crate::scope::{DropGuard, Reducer, Scope, Slot, Subscription};
14
15pub struct ReducerBinding<R: Reducer> {
21 owner: Scope,
22 state: Rc<Slot<R>>,
23 dispatch: Dispatch,
24}
25
26impl<R: Reducer> Clone for ReducerBinding<R> {
27 fn clone(&self) -> Self {
28 Self {
29 owner: self.owner,
30 state: self.state.clone(),
31 dispatch: self.dispatch.clone(),
32 }
33 }
34}
35
36impl<R: Reducer> ReducerBinding<R> {
37 pub(crate) fn new(scope: Scope) -> Self {
38 Self {
39 owner: scope,
40 state: scope.state::<R>(),
41 dispatch: Dispatch::owning::<R>(scope),
42 }
43 }
44
45 pub fn peek(&self) -> Ref<'_, R> {
47 Ref::map(self.state.borrow(), |state| &**state)
48 }
49
50 pub fn get(&self) -> Rc<R> {
53 self.state.borrow().clone()
54 }
55
56 pub fn dispatch(&self) -> Dispatch {
58 self.dispatch.clone()
59 }
60
61 pub fn push(&self, update: R::Update) {
64 self.owner.push::<R>(update);
65 }
66
67 pub fn on_change(&self, f: impl Fn(&R) + 'static) -> Subscription {
74 let state = self.state.clone();
75 self.owner.subscribe::<R>(move || {
76 let now = state.borrow().clone();
77 f(&now)
78 })
79 }
80
81 pub fn on_change_owned(&self, f: impl Fn(&R) + 'static) {
84 let subscription = self.on_change(f);
85 self.owner.own(DropGuard(subscription));
86 }
87
88 pub fn bind(&self, f: impl Fn(&R) + 'static) -> Subscription {
90 f(&self.get());
91 self.on_change(f)
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::scope::ScopeTree;
99 use std::cell::Cell;
100
101 #[derive(Clone, Default, Debug)]
102 struct Counter(u32);
103
104 impl Reducer for Counter {
105 type Update = u32;
106
107 fn reduce(&mut self, by: u32) {
108 self.0 += by;
109 }
110 }
111
112 #[test]
113 fn a_binding_reads_on_after_its_scope_is_removed() {
114 let scope = ScopeTree::new();
115 let binding = scope.binding::<Counter>();
116 binding.push(1);
117
118 binding.on_change_owned(|_| {});
119 scope.remove();
120
121 assert_eq!(binding.peek().0, 1, "the state cell outlives the scope");
122 binding.push(1);
123 assert_eq!(binding.peek().0, 1, "pushing into a removed scope is a no-op");
124 }
125
126 #[test]
127 fn subscribing_to_a_removed_scope_hears_nothing() {
128 let scope = ScopeTree::new();
129 let binding = scope.binding::<Counter>();
130 scope.remove();
131
132 let seen = Rc::new(Cell::new(false));
133 let recorder = seen.clone();
134 let _subscription = binding.on_change(move |_| recorder.set(true));
135 binding.push(1);
136
137 assert!(!seen.get());
138 }
139
140 #[test]
141 fn dropping_a_subscription_after_its_scope_is_harmless() {
142 let scope = ScopeTree::new();
143 let subscription = scope.binding::<Counter>().on_change(|_| {});
144 scope.remove();
145
146 drop(subscription);
147 }
148
149 #[test]
150 fn on_change_sees_every_push_until_dropped() {
151 let scope = ScopeTree::new();
152 let binding = scope.binding::<Counter>();
153
154 let seen = Rc::new(Cell::new(0u32));
155 let recorder = seen.clone();
156 let subscription = binding.on_change(move |counter| recorder.set(counter.0));
157
158 binding.push(2);
159 binding.push(3);
160 assert_eq!(seen.get(), 5);
161
162 drop(subscription);
163 binding.push(4);
164 assert_eq!(seen.get(), 5, "no delivery after the subscription is dropped");
165 }
166
167 #[test]
168 fn bind_calls_back_before_anything_changes() {
169 let scope = ScopeTree::new();
170 let binding = scope.binding::<Counter>();
171 binding.push(7);
172
173 let seen = Rc::new(Cell::new(None));
174 let recorder = seen.clone();
175 let _subscription = binding.bind(move |counter| recorder.set(Some(counter.0)));
176
177 assert_eq!(seen.get(), Some(7));
178 }
179
180 #[test]
181 fn a_read_is_shared_and_a_change_while_it_is_held_goes_to_a_copy() {
182 let scope = ScopeTree::new();
183 let binding = scope.binding::<Counter>();
184 binding.push(1);
185
186 let first = binding.get();
187 let again = binding.get();
188 assert!(Rc::ptr_eq(&first, &again), "reading twice copies nothing");
189
190 binding.push(2);
191 assert_eq!(first.0, 1, "what was read stays as it was");
192 assert_eq!(binding.get().0, 3);
193
194 drop((first, again));
195 let before = Rc::as_ptr(&binding.get());
196 binding.push(4);
197 assert_eq!(Rc::as_ptr(&binding.get()), before, "nobody held it, so no copy");
198 }
199}