1use iced::Theme;
19use serde_json::{Value, json};
20
21use crate::extensions::{ExtensionCaches, ExtensionDispatcher, RenderCtx, WidgetEnv};
22use crate::image_registry::ImageRegistry;
23use crate::protocol::TreeNode;
24use crate::widgets::WidgetCaches;
25
26pub fn node(id: &str, type_name: &str) -> TreeNode {
32 TreeNode {
33 id: id.to_string(),
34 type_name: type_name.to_string(),
35 props: json!({}),
36 children: vec![],
37 }
38}
39
40pub fn node_with_props(id: &str, type_name: &str, props: Value) -> TreeNode {
42 TreeNode {
43 id: id.to_string(),
44 type_name: type_name.to_string(),
45 props,
46 children: vec![],
47 }
48}
49
50pub fn node_with_children(id: &str, type_name: &str, children: Vec<TreeNode>) -> TreeNode {
52 TreeNode {
53 id: id.to_string(),
54 type_name: type_name.to_string(),
55 props: json!({}),
56 children,
57 }
58}
59
60pub struct TestEnv {
87 pub ext_caches: ExtensionCaches,
88 pub widget_caches: WidgetCaches,
89 pub images: ImageRegistry,
90 pub theme: Theme,
91 pub dispatcher: ExtensionDispatcher,
92 pub default_text_size: Option<f32>,
93 pub default_font: Option<iced::Font>,
94}
95
96impl Default for TestEnv {
97 fn default() -> Self {
98 Self {
99 ext_caches: ExtensionCaches::new(),
100 widget_caches: WidgetCaches::new(),
101 images: ImageRegistry::new(),
102 theme: Theme::Dark,
103 dispatcher: ExtensionDispatcher::new(vec![]),
104 default_text_size: None,
105 default_font: None,
106 }
107 }
108}
109
110impl TestEnv {
111 pub fn render_ctx(&self) -> RenderCtx<'_> {
113 RenderCtx {
114 caches: &self.widget_caches,
115 images: &self.images,
116 theme: &self.theme,
117 extensions: &self.dispatcher,
118 default_text_size: self.default_text_size,
119 default_font: self.default_font,
120 }
121 }
122
123 pub fn env<'a>(&'a self, ctx: &RenderCtx<'a>) -> WidgetEnv<'a> {
133 WidgetEnv {
134 caches: &self.ext_caches,
135 ctx: *ctx,
136 }
137 }
138}
139
140#[cfg(test)]
145mod tests {
146 use super::*;
147 use crate::extensions::GenerationCounter;
148 use crate::prop_helpers::{prop_f32, prop_str};
149
150 #[test]
153 fn node_has_empty_props_and_no_children() {
154 let n = node("btn-1", "button");
155 assert_eq!(n.id, "btn-1");
156 assert_eq!(n.type_name, "button");
157 assert!(n.children.is_empty());
158 assert_eq!(n.props, json!({}));
159 }
160
161 #[test]
162 fn node_with_props_stores_props() {
163 let n = node_with_props("txt-1", "text", json!({"content": "hello", "size": 14}));
164 assert_eq!(n.props["content"], "hello");
165 assert_eq!(n.props["size"], 14);
166 }
167
168 #[test]
169 fn node_with_children_stores_children() {
170 let children = vec![node("a", "text"), node("b", "button")];
171 let n = node_with_children("col-1", "column", children);
172 assert_eq!(n.children.len(), 2);
173 assert_eq!(n.children[0].id, "a");
174 assert_eq!(n.children[1].id, "b");
175 }
176
177 #[test]
178 fn node_props_work_with_prop_helpers() {
179 let n = node_with_props("s-1", "sparkline", json!({"label": "cpu", "max": 100.0}));
180 let props = n.props.as_object();
181 assert_eq!(prop_str(props, "label"), Some("cpu".to_string()));
182 assert!((prop_f32(props, "max").unwrap() - 100.0).abs() < 0.001);
183 }
184
185 #[test]
188 fn default_env_has_no_text_defaults() {
189 let test = TestEnv::default();
190 let ctx = test.render_ctx();
191 let env = test.env(&ctx);
192 assert!(env.default_text_size().is_none());
193 assert!(env.default_font().is_none());
194 }
195
196 #[test]
197 fn default_env_has_empty_state() {
198 let test = TestEnv::default();
199 let ctx = test.render_ctx();
200 let env = test.env(&ctx);
201 assert!(!env.caches.contains("test", "anything"));
202 assert!(ctx.extensions.is_empty());
203 }
204
205 #[test]
206 fn env_inherits_text_defaults() {
207 let test = TestEnv {
208 default_text_size: Some(18.0),
209 default_font: Some(iced::Font::MONOSPACE),
210 ..TestEnv::default()
211 };
212
213 let ctx = test.render_ctx();
214 let env = test.env(&ctx);
215 assert_eq!(env.default_text_size(), Some(18.0));
216 assert_eq!(env.default_font(), Some(iced::Font::MONOSPACE));
217 }
218
219 #[test]
220 fn env_theme_is_customizable() {
221 let test = TestEnv {
222 theme: Theme::Light,
223 ..TestEnv::default()
224 };
225 let ctx = test.render_ctx();
226 let _env = test.env(&ctx);
227 }
228
229 #[test]
232 fn generation_counter_lifecycle() {
233 let mut counter = GenerationCounter::new();
234 assert_eq!(counter.get(), 0);
235 counter.bump();
236 counter.bump();
237 assert_eq!(counter.get(), 2);
238 }
239
240 #[test]
241 fn generation_counter_in_caches() {
242 let mut test = TestEnv::default();
243 test.ext_caches
244 .insert("spark", "spark-1:gen", GenerationCounter::new());
245
246 let counter = test
247 .ext_caches
248 .get_mut::<GenerationCounter>("spark", "spark-1:gen")
249 .unwrap();
250 counter.bump();
251
252 let counter = test
253 .ext_caches
254 .get::<GenerationCounter>("spark", "spark-1:gen")
255 .unwrap();
256 assert_eq!(counter.get(), 1);
257 }
258}