cranpose_testing/
testing.rs1use cranpose_core::{
2 location_key, ApplierGuard, Composition, Key, MemoryApplier, NodeError, NodeId, RuntimeHandle,
3 ROOT_RENDER_REPLAY_LIMIT,
4};
5use cranpose_ui::{request_render_invalidation, reset_render_state_for_tests, AppContext};
6
7#[cfg(test)]
8use cranpose_core::{
9 pop_parent, push_parent, with_current_composer, with_node_mut, MutableState, Node,
10};
11#[cfg(test)]
12use std::cell::Cell;
13#[cfg(test)]
14use std::rc::Rc;
15use std::rc::Rc as StdRc;
16
17pub struct ComposeTestRule {
24 _scope: cranpose_ui::AppContextScope,
25 app_context: StdRc<AppContext>,
26 composition: Composition<MemoryApplier>,
27 content: Option<Box<dyn FnMut()>>, initial_root_key: Key,
29}
30
31impl ComposeTestRule {
32 pub fn new() -> Self {
34 let app_context = AppContext::new();
35 app_context.enter(reset_render_state_for_tests);
36 let scope = app_context.enter_scope();
37 Self {
38 _scope: scope,
39 app_context,
40 composition: Composition::new(MemoryApplier::new()),
41 content: None,
42 initial_root_key: location_key(file!(), line!(), column!()),
43 }
44 }
45
46 fn root_key(&self) -> Key {
47 self.composition.root_key().unwrap_or(self.initial_root_key)
48 }
49
50 pub fn set_content(&mut self, content: impl FnMut() + 'static) -> Result<(), NodeError> {
53 self.content = Some(Box::new(content));
54 self.render()
55 }
56
57 pub fn recomposition(&mut self) -> Result<(), NodeError> {
59 self.render()
60 }
61
62 pub fn advance_frame(&mut self, frame_time_nanos: u64) -> Result<(), NodeError> {
65 let app_context = StdRc::clone(&self.app_context);
66 app_context.enter(|| {
67 let handle = self.composition.runtime_handle();
68 handle.drain_frame_callbacks(frame_time_nanos);
69 self.pump_until_idle_in_context()
70 })
71 }
72
73 pub fn pump_until_idle(&mut self) -> Result<(), NodeError> {
76 let app_context = StdRc::clone(&self.app_context);
77 app_context.enter(|| self.pump_until_idle_in_context())
78 }
79
80 fn pump_until_idle_in_context(&mut self) -> Result<(), NodeError> {
81 let mut i = 0;
82 loop {
83 let mut progressed = false;
84 i += 1;
85 if i > ROOT_RENDER_REPLAY_LIMIT {
86 return Err(NodeError::RecompositionLimitExceeded {
87 operation: "pump_until_idle",
88 limit: ROOT_RENDER_REPLAY_LIMIT,
89 });
90 }
91
92 if self.composition.should_render() {
93 self.render()?;
94 progressed = true;
95 }
96
97 let handle = self.composition.runtime_handle();
98 if handle.has_updates() {
99 self.composition.flush_pending_node_updates()?;
100 progressed = true;
101 }
102
103 if handle.has_invalid_scopes() {
104 let changed = self.composition.process_invalid_scopes()?;
105 if changed {
106 request_render_invalidation();
108 }
109 if self.composition.take_root_render_request() {
110 self.render()?;
111 }
112 progressed = true;
113 }
114
115 if !progressed {
116 break;
117 }
118 }
119 Ok(())
120 }
121
122 pub fn runtime_handle(&self) -> RuntimeHandle {
125 self.composition.runtime_handle()
126 }
127
128 pub fn applier_mut(&mut self) -> ApplierGuard<'_, MemoryApplier> {
131 self.composition.applier_mut()
132 }
133
134 pub fn dump_tree(&mut self) -> String {
136 let root = self.composition.root();
137 let applier = self.composition.applier_mut();
138 applier.dump_tree(root)
139 }
140
141 pub fn has_content(&self) -> bool {
143 self.content.is_some()
144 }
145
146 pub fn root_id(&self) -> Option<NodeId> {
148 self.composition.root()
149 }
150
151 pub fn with_app_context<R>(&self, block: impl FnOnce() -> R) -> R {
152 self.app_context.enter(block)
153 }
154
155 pub fn composition(&mut self) -> &mut Composition<MemoryApplier> {
157 &mut self.composition
158 }
159
160 fn render(&mut self) -> Result<(), NodeError> {
161 let app_context = StdRc::clone(&self.app_context);
162 app_context.enter(|| {
163 let key = self.root_key();
164 if let Some(content) = self.content.as_mut() {
165 self.composition.render(key, &mut **content)?;
166 self.drain_root_render_requests()?;
167 request_render_invalidation();
170 }
171 Ok(())
172 })
173 }
174
175 fn drain_root_render_requests(&mut self) -> Result<(), NodeError> {
176 let key = self.root_key();
177 for _ in 0..ROOT_RENDER_REPLAY_LIMIT {
178 if !self.composition.take_root_render_request() {
179 return Ok(());
180 }
181 let content = self
182 .content
183 .as_mut()
184 .ok_or(NodeError::RecompositionLimitExceeded {
185 operation: "root render replay",
186 limit: ROOT_RENDER_REPLAY_LIMIT,
187 })?;
188 self.composition.render(key, &mut **content)?;
189 request_render_invalidation();
190 }
191
192 Err(NodeError::RecompositionLimitExceeded {
193 operation: "root render replay",
194 limit: ROOT_RENDER_REPLAY_LIMIT,
195 })
196 }
197}
198
199impl Default for ComposeTestRule {
200 fn default() -> Self {
201 Self::new()
202 }
203}
204
205pub fn run_test_composition<R>(f: impl FnOnce(&mut ComposeTestRule) -> R) -> R {
208 let mut rule = ComposeTestRule::new();
209 f(&mut rule)
210}
211
212#[cfg(test)]
213#[path = "tests/testing_tests.rs"]
214mod tests;
215
216#[cfg(test)]
217#[path = "tests/recomposition_tests.rs"]
218mod recomposition_tests;