1use std::collections::HashMap;
4
5#[derive(Debug, Clone, PartialEq)]
7pub enum VNode {
8 Text(String),
9 Element {
10 tag: String,
11 attrs: HashMap<String, String>,
12 children: Vec<VNode>,
13 },
14}
15
16#[derive(Debug, Clone, PartialEq)]
18pub enum Patch {
19 Replace(VNode),
20 UpdateAttrs {
21 add: HashMap<String, String>,
22 remove: Vec<String>,
23 },
24 InsertChild(usize, VNode),
25 RemoveChild(usize),
26}
27
28pub fn render(vnode: &VNode) -> String {
30 match vnode {
31 VNode::Text(text) => text.clone(),
32 VNode::Element {
33 tag,
34 attrs,
35 children,
36 } => {
37 let mut result = format!("[{}]", tag);
38 if !attrs.is_empty() {
39 result.push(' ');
40 let attr_str: Vec<String> =
41 attrs.iter().map(|(k, v)| format!("{}={}", k, v)).collect();
42 result.push_str(&attr_str.join(" "));
43 }
44 result.push('\n');
45
46 for child in children {
47 let child_str = render(child);
48 for line in child_str.lines() {
49 result.push_str(" ");
50 result.push_str(line);
51 result.push('\n');
52 }
53 }
54
55 result
56 }
57 }
58}
59
60pub fn diff(old: &VNode, new: &VNode) -> Vec<Patch> {
62 match (old, new) {
63 (VNode::Text(old_text), VNode::Text(new_text)) => {
64 if old_text == new_text {
65 vec![]
66 } else {
67 vec![Patch::Replace(new.clone())]
68 }
69 }
70 (
71 VNode::Element {
72 tag: old_tag,
73 attrs: old_attrs,
74 children: old_children,
75 },
76 VNode::Element {
77 tag: new_tag,
78 attrs: new_attrs,
79 children: new_children,
80 },
81 ) => {
82 let mut patches = vec![];
83
84 if old_tag != new_tag {
86 return vec![Patch::Replace(new.clone())];
87 }
88
89 let mut add_attrs = HashMap::new();
91 let mut remove_attrs = Vec::new();
92
93 for (key, old_val) in old_attrs.iter() {
95 match new_attrs.get(key) {
96 Some(new_val) => {
97 if old_val != new_val {
98 add_attrs.insert(key.clone(), new_val.clone());
99 }
100 }
101 None => {
102 remove_attrs.push(key.clone());
103 }
104 }
105 }
106
107 for (key, new_val) in new_attrs.iter() {
109 if !old_attrs.contains_key(key) {
110 add_attrs.insert(key.clone(), new_val.clone());
111 }
112 }
113
114 if !add_attrs.is_empty() || !remove_attrs.is_empty() {
115 patches.push(Patch::UpdateAttrs {
116 add: add_attrs,
117 remove: remove_attrs,
118 });
119 }
120
121 let old_len = old_children.len();
123 let _new_len = new_children.len();
124
125 let common_prefix_len = old_children
127 .iter()
128 .zip(new_children.iter())
129 .take_while(|(o, n)| o == n)
130 .count();
131
132 for (i, child) in new_children.iter().enumerate().skip(common_prefix_len) {
134 patches.push(Patch::InsertChild(i, child.clone()));
135 }
136
137 for _i in common_prefix_len..old_len {
139 patches.push(Patch::RemoveChild(common_prefix_len));
140 }
141
142 patches
143 }
144 (_, _) => vec![Patch::Replace(new.clone())],
145 }
146}
147
148pub fn apply(node: &mut VNode, patches: Vec<Patch>) {
150 let node_ref = node;
151
152 for patch in patches {
153 match patch {
154 Patch::Replace(new_node) => {
155 *node_ref = new_node;
156 }
157 Patch::UpdateAttrs { add, remove } => {
158 if let VNode::Element { attrs, .. } = node_ref {
159 for key in remove {
160 attrs.remove(&key);
161 }
162 for (key, value) in add {
163 attrs.insert(key, value);
164 }
165 }
166 }
167 Patch::InsertChild(index, child) => {
168 if let VNode::Element { children, .. } = node_ref {
169 children.insert(index, child);
170 }
171 }
172 Patch::RemoveChild(index) => {
173 if let VNode::Element { children, .. } = node_ref {
174 if index < children.len() {
175 children.remove(index);
176 }
177 }
178 }
179 }
180 }
181}
182
183impl VNode {
185 pub fn children(&self) -> Option<&Vec<VNode>> {
186 match self {
187 VNode::Element { children, .. } => Some(children),
188 _ => None,
189 }
190 }
191
192 pub fn attrs(&self) -> Option<&HashMap<String, String>> {
193 match self {
194 VNode::Element { attrs, .. } => Some(attrs),
195 _ => None,
196 }
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn test_render_text() {
206 let node = VNode::Text("Hello, World!".to_string());
207 assert_eq!(render(&node), "Hello, World!");
208 }
209
210 #[test]
211 fn test_render_element() {
212 let node = VNode::Element {
213 tag: "div".to_string(),
214 attrs: {
215 let mut map = HashMap::new();
216 map.insert("id".to_string(), "test".to_string());
217 map
218 },
219 children: vec![VNode::Text("Content".to_string())],
220 };
221
222 let result = render(&node);
223 assert!(result.contains("[div]"));
224 assert!(result.contains("id=test"));
225 assert!(result.contains("Content"));
226 }
227
228 #[test]
229 fn test_diff_no_change() {
230 let node = VNode::Text("Same".to_string());
231 let patches = diff(&node, &node);
232 assert!(patches.is_empty());
233 }
234
235 #[test]
236 fn test_diff_text_change() {
237 let old = VNode::Text("Old".to_string());
238 let new = VNode::Text("New".to_string());
239 let patches = diff(&old, &new);
240
241 assert_eq!(patches.len(), 1);
242 assert_eq!(patches[0], Patch::Replace(new.clone()));
243 }
244
245 #[test]
246 fn test_diff_add_child() {
247 let old = VNode::Element {
248 tag: "div".to_string(),
249 attrs: HashMap::new(),
250 children: vec![VNode::Text("First".to_string())],
251 };
252
253 let new = VNode::Element {
254 tag: "div".to_string(),
255 attrs: HashMap::new(),
256 children: vec![
257 VNode::Text("First".to_string()),
258 VNode::Text("Second".to_string()),
259 ],
260 };
261
262 let patches = diff(&old, &new);
263
264 assert_eq!(patches.len(), 1);
265 assert_eq!(
266 patches[0],
267 Patch::InsertChild(1, VNode::Text("Second".to_string()))
268 );
269 }
270
271 #[test]
272 fn test_diff_remove_child() {
273 let old = VNode::Element {
274 tag: "div".to_string(),
275 attrs: HashMap::new(),
276 children: vec![
277 VNode::Text("First".to_string()),
278 VNode::Text("Second".to_string()),
279 ],
280 };
281
282 let new = VNode::Element {
283 tag: "div".to_string(),
284 attrs: HashMap::new(),
285 children: vec![VNode::Text("First".to_string())],
286 };
287
288 let patches = diff(&old, &new);
289
290 assert_eq!(patches.len(), 1);
291 assert_eq!(patches[0], Patch::RemoveChild(1));
292 }
293
294 #[test]
295 fn test_apply_patch() {
296 let mut node = VNode::Text("Old".to_string());
297 let patches = vec![Patch::Replace(VNode::Text("New".to_string()))];
298 apply(&mut node, patches);
299
300 assert_eq!(node, VNode::Text("New".to_string()));
301 }
302
303 #[test]
304 fn test_apply_insert_child() {
305 let mut node = VNode::Element {
306 tag: "div".to_string(),
307 attrs: HashMap::new(),
308 children: vec![VNode::Text("First".to_string())],
309 };
310
311 let patches = vec![Patch::InsertChild(1, VNode::Text("Second".to_string()))];
312 apply(&mut node, patches);
313
314 assert_eq!(node.children().unwrap().len(), 2);
315 assert_eq!(
316 node.children().unwrap()[1],
317 VNode::Text("Second".to_string())
318 );
319 }
320
321 #[test]
322 fn test_apply_remove_child() {
323 let mut node = VNode::Element {
324 tag: "div".to_string(),
325 attrs: HashMap::new(),
326 children: vec![
327 VNode::Text("First".to_string()),
328 VNode::Text("Second".to_string()),
329 ],
330 };
331
332 let patches = vec![Patch::RemoveChild(1)];
333 apply(&mut node, patches);
334
335 assert_eq!(node.children().unwrap().len(), 1);
336 }
337
338 #[test]
339 fn test_apply_update_attrs() {
340 let mut node = VNode::Element {
341 tag: "div".to_string(),
342 attrs: {
343 let mut map = HashMap::new();
344 map.insert("id".to_string(), "old".to_string());
345 map
346 },
347 children: vec![],
348 };
349
350 let patches = vec![Patch::UpdateAttrs {
351 add: {
352 let mut map = HashMap::new();
353 map.insert("class".to_string(), "test".to_string());
354 map
355 },
356 remove: vec!["id".to_string()],
357 }];
358
359 apply(&mut node, patches);
360
361 let attrs = node.attrs().unwrap();
362 assert!(!attrs.contains_key("id"));
363 assert_eq!(attrs.get("class"), Some(&"test".to_string()));
364 }
365}