Skip to main content

boxmux_lib/model/
layout.rs

1use crate::{model::muxbox::MuxBox, screen_bounds, Bounds, EntityType, FieldUpdate, Updatable};
2use core::hash::Hash;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::{collections::HashMap, hash::Hasher};
6
7#[derive(Debug, Deserialize, Serialize, Default, PartialEq)]
8pub struct Layout {
9    pub id: String,
10    pub title: Option<String>,
11    pub refresh_interval: Option<u64>,
12    pub children: Option<Vec<MuxBox>>,
13    pub fill: Option<bool>,
14    pub fill_char: Option<char>,
15    pub selected_fill_char: Option<char>,
16    pub highlighted_fill_char: Option<char>,
17    pub border_color: Option<String>,
18    pub selected_border_color: Option<String>,
19    pub highlighted_border_color: Option<String>,
20    pub bg_color: Option<String>,
21    pub selected_bg_color: Option<String>,
22    pub highlighted_bg_color: Option<String>,
23    pub fg_color: Option<String>,
24    pub selected_fg_color: Option<String>,
25    pub highlighted_fg_color: Option<String>,
26    pub title_fg_color: Option<String>,
27    pub title_bg_color: Option<String>,
28    pub title_position: Option<String>,
29    pub selected_title_bg_color: Option<String>,
30    pub selected_title_fg_color: Option<String>,
31    pub highlighted_title_bg_color: Option<String>,
32    pub highlighted_title_fg_color: Option<String>,
33    pub menu_fg_color: Option<String>,
34    pub menu_bg_color: Option<String>,
35    pub selected_menu_fg_color: Option<String>,
36    pub selected_menu_bg_color: Option<String>,
37    pub highlighted_menu_fg_color: Option<String>,
38    pub highlighted_menu_bg_color: Option<String>,
39    pub error_border_color: Option<String>,
40    pub error_bg_color: Option<String>,
41    pub error_fg_color: Option<String>,
42    pub error_title_bg_color: Option<String>,
43    pub error_title_fg_color: Option<String>,
44    pub error_selected_border_color: Option<String>,
45    pub error_selected_bg_color: Option<String>,
46    pub error_selected_fg_color: Option<String>,
47    pub error_selected_title_bg_color: Option<String>,
48    pub error_selected_title_fg_color: Option<String>,
49    pub overflow_behavior: Option<String>,
50    pub root: Option<bool>,
51    #[serde(default)]
52    pub on_keypress: Option<HashMap<String, Vec<String>>>,
53    pub active: Option<bool>,
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub muxbox_ids_in_tab_order: Option<Vec<String>>,
56}
57
58impl Hash for Layout {
59    fn hash<H: Hasher>(&self, state: &mut H) {
60        self.id.hash(state);
61        self.title.hash(state);
62        self.refresh_interval.hash(state);
63        if let Some(children) = &self.children {
64            for muxbox in children {
65                muxbox.hash(state);
66            }
67        }
68        self.fill.hash(state);
69        self.fill_char.hash(state);
70        self.selected_fill_char.hash(state);
71        self.highlighted_fill_char.hash(state);
72        self.border_color.hash(state);
73        self.selected_border_color.hash(state);
74        self.highlighted_border_color.hash(state);
75        self.bg_color.hash(state);
76        self.selected_bg_color.hash(state);
77        self.highlighted_bg_color.hash(state);
78        self.fg_color.hash(state);
79        self.selected_fg_color.hash(state);
80        self.highlighted_fg_color.hash(state);
81        self.title_fg_color.hash(state);
82        self.title_bg_color.hash(state);
83        self.title_position.hash(state);
84        self.selected_title_bg_color.hash(state);
85        self.selected_title_fg_color.hash(state);
86        self.highlighted_title_bg_color.hash(state);
87        self.highlighted_title_fg_color.hash(state);
88        self.menu_fg_color.hash(state);
89        self.menu_bg_color.hash(state);
90        self.selected_menu_fg_color.hash(state);
91        self.selected_menu_bg_color.hash(state);
92        self.highlighted_menu_fg_color.hash(state);
93        self.highlighted_menu_bg_color.hash(state);
94        self.error_border_color.hash(state);
95        self.error_bg_color.hash(state);
96        self.error_fg_color.hash(state);
97        self.error_title_bg_color.hash(state);
98        self.error_title_fg_color.hash(state);
99        self.error_selected_border_color.hash(state);
100        self.error_selected_bg_color.hash(state);
101        self.error_selected_fg_color.hash(state);
102        self.error_selected_title_bg_color.hash(state);
103        self.error_selected_title_fg_color.hash(state);
104        self.overflow_behavior.hash(state);
105        self.root.hash(state);
106        self.active.hash(state);
107    }
108}
109
110impl Layout {
111    pub fn new() -> Self {
112        Layout {
113            id: String::new(),
114            title: None,
115            refresh_interval: None,
116            children: None,
117            fill: None,
118            fill_char: None,
119            selected_fill_char: None,
120            highlighted_fill_char: None,
121            border_color: None,
122            selected_border_color: None,
123            highlighted_border_color: None,
124            bg_color: None,
125            selected_bg_color: None,
126            highlighted_bg_color: None,
127            fg_color: None,
128            selected_fg_color: None,
129            highlighted_fg_color: None,
130            title_fg_color: None,
131            title_bg_color: None,
132            title_position: None,
133            selected_title_bg_color: None,
134            selected_title_fg_color: None,
135            highlighted_title_bg_color: None,
136            highlighted_title_fg_color: None,
137            menu_fg_color: None,
138            menu_bg_color: None,
139            selected_menu_fg_color: None,
140            selected_menu_bg_color: None,
141            highlighted_menu_fg_color: None,
142            highlighted_menu_bg_color: None,
143            error_border_color: None,
144            error_bg_color: None,
145            error_fg_color: None,
146            error_title_bg_color: None,
147            error_title_fg_color: None,
148            error_selected_border_color: None,
149            error_selected_bg_color: None,
150            error_selected_fg_color: None,
151            error_selected_title_bg_color: None,
152            error_selected_title_fg_color: None,
153            overflow_behavior: None,
154            root: Some(false),
155            on_keypress: None,
156            active: Some(false),
157            muxbox_ids_in_tab_order: None,
158        }
159    }
160
161    pub fn get_muxbox_by_id(&self, id: &str) -> Option<&MuxBox> {
162        fn recursive_search<'a>(muxboxes: &'a [MuxBox], id: &str) -> Option<&'a MuxBox> {
163            for muxbox in muxboxes {
164                if muxbox.id == id {
165                    return Some(muxbox);
166                }
167                if let Some(ref children) = muxbox.children {
168                    if let Some(found) = recursive_search(children, id) {
169                        return Some(found);
170                    }
171                }
172            }
173            None
174        }
175
176        if let Some(ref children) = self.children {
177            recursive_search(children, id)
178        } else {
179            None
180        }
181    }
182
183    pub fn get_muxbox_by_id_mut(&mut self, id: &str) -> Option<&mut MuxBox> {
184        fn recursive_search<'a>(muxboxes: &'a mut [MuxBox], id: &str) -> Option<&'a mut MuxBox> {
185            for muxbox in muxboxes {
186                if muxbox.id == id {
187                    return Some(muxbox);
188                }
189                if let Some(ref mut children) = muxbox.children {
190                    if let Some(found) = recursive_search(children, id) {
191                        return Some(found);
192                    }
193                }
194            }
195            None
196        }
197
198        if let Some(ref mut children) = self.children {
199            recursive_search(children, id)
200        } else {
201            None
202        }
203    }
204
205    pub fn get_selected_muxboxes(&self) -> Vec<&MuxBox> {
206        fn recursive_collect<'a>(muxboxes: &'a [MuxBox], selected_muxboxes: &mut Vec<&'a MuxBox>) {
207            for muxbox in muxboxes {
208                if muxbox.selected.unwrap_or(false) {
209                    selected_muxboxes.push(muxbox);
210                }
211                if let Some(ref children) = muxbox.children {
212                    recursive_collect(children, selected_muxboxes);
213                }
214            }
215        }
216
217        let mut selected_muxboxes = Vec::new();
218
219        if let Some(ref children) = self.children {
220            recursive_collect(children, &mut selected_muxboxes);
221        }
222        selected_muxboxes
223    }
224
225    pub fn select_only_muxbox(&mut self, id: &str) {
226        fn recursive_select(muxboxes: &mut [MuxBox], id: &str) {
227            for muxbox in muxboxes {
228                muxbox.selected = Some(muxbox.id == id);
229                if let Some(ref mut children) = muxbox.children {
230                    recursive_select(children, id);
231                }
232            }
233        }
234
235        if let Some(ref mut children) = self.children {
236            recursive_select(children, id);
237        }
238    }
239
240    pub fn get_muxboxes_in_tab_order(&mut self) -> Vec<&MuxBox> {
241        fn collect_muxboxes_recursive<'a>(muxbox: &'a MuxBox, muxboxes: &mut Vec<&'a MuxBox>) {
242            // Check if muxbox has a tab order and add it to the list
243            if muxbox.tab_order.is_some() {
244                muxboxes.push(muxbox);
245            }
246
247            // If children exist, iterate over them recursively
248            if let Some(children) = &muxbox.children {
249                for child in children {
250                    collect_muxboxes_recursive(child, muxboxes);
251                }
252            }
253        }
254
255        if self.muxbox_ids_in_tab_order.is_some() {
256            let mut muxboxes = Vec::new();
257            for muxbox_id in self.muxbox_ids_in_tab_order.as_ref().unwrap() {
258                if let Some(muxbox) = self.get_muxbox_by_id(muxbox_id) {
259                    muxboxes.push(muxbox);
260                }
261            }
262            muxboxes
263        } else {
264            let mut muxboxes = Vec::new();
265            // Start recursion for each top-level child
266            if let Some(children) = &self.children {
267                for muxbox in children {
268                    collect_muxboxes_recursive(muxbox, &mut muxboxes);
269                }
270            }
271
272            // Sort muxboxes by their tab order
273            muxboxes.sort_by(|a, b| {
274                a.tab_order
275                    .as_ref()
276                    .unwrap()
277                    .cmp(b.tab_order.as_ref().unwrap())
278            });
279
280            self.muxbox_ids_in_tab_order = Some(muxboxes.iter().map(|p| p.id.clone()).collect());
281
282            muxboxes
283        }
284    }
285
286    pub fn get_all_muxboxes(&self) -> Vec<&MuxBox> {
287        fn recursive_collect<'a>(muxboxes: &'a [MuxBox], all_muxboxes: &mut Vec<&'a MuxBox>) {
288            for muxbox in muxboxes {
289                all_muxboxes.push(muxbox);
290                if let Some(ref children) = muxbox.children {
291                    recursive_collect(children, all_muxboxes);
292                }
293            }
294        }
295
296        let mut all_muxboxes = Vec::new();
297        if let Some(ref children) = self.children {
298            recursive_collect(children, &mut all_muxboxes);
299        }
300        all_muxboxes
301    }
302
303    pub fn select_next_muxbox(&mut self) {
304        let muxboxes = self.get_muxboxes_in_tab_order();
305        if muxboxes.is_empty() {
306            return; // Early return if there are no muxboxes
307        }
308
309        let selected_muxbox_index = muxboxes.iter().position(|p| p.selected.unwrap_or(false));
310
311        let next_muxbox_index = match selected_muxbox_index {
312            Some(index) => (index + 1) % muxboxes.len(), // Get next muxbox, wrap around if at the end
313            None => 0, // No muxbox is selected, select the first one
314        };
315
316        let next_muxbox_id = muxboxes[next_muxbox_index].id.clone();
317        self.select_only_muxbox(&next_muxbox_id);
318    }
319
320    pub fn select_previous_muxbox(&mut self) {
321        let muxboxes = self.get_muxboxes_in_tab_order();
322        if muxboxes.is_empty() {
323            return; // Early return if there are no muxboxes
324        }
325
326        let selected_muxbox_index = muxboxes.iter().position(|p| p.selected.unwrap_or(false));
327
328        let previous_muxbox_index = match selected_muxbox_index {
329            Some(index) => {
330                if index == 0 {
331                    muxboxes.len() - 1 // Wrap around to the last muxbox if the first one is currently selected
332                } else {
333                    index - 1 // Select the previous muxbox
334                }
335            }
336            None => muxboxes.len() - 1, // No muxbox is selected, select the last one
337        };
338
339        let previous_muxbox_id = muxboxes[previous_muxbox_index].id.clone();
340        self.select_only_muxbox(&previous_muxbox_id);
341    }
342
343    pub fn deselect_all_muxboxes(&mut self) {
344        if let Some(children) = &mut self.children {
345            for muxbox in children {
346                muxbox.selected = Some(false);
347            }
348        }
349    }
350
351    pub fn replace_muxbox_recursive(&mut self, replacement_muxbox: &MuxBox) -> Option<bool> {
352        fn replace_in_muxboxes(muxboxes: &mut [MuxBox], replacement: &MuxBox) -> bool {
353            for muxbox in muxboxes {
354                if muxbox.id == replacement.id {
355                    *muxbox = replacement.clone();
356                    return true;
357                }
358                if let Some(ref mut children) = muxbox.children {
359                    if replace_in_muxboxes(children, replacement) {
360                        return true;
361                    }
362                }
363            }
364            false
365        }
366
367        if let Some(ref mut children) = self.children {
368            Some(replace_in_muxboxes(children, replacement_muxbox))
369        } else {
370            Some(false)
371        }
372    }
373
374    pub fn find_muxbox_with_choice(&self, choice_id: &str) -> Option<&MuxBox> {
375        fn find_in_muxboxes<'a>(muxboxes: &'a [MuxBox], choice_id: &str) -> Option<&'a MuxBox> {
376            for muxbox in muxboxes {
377                // Check all streams, not just active stream, since choice execution
378                // should be possible regardless of which stream/tab is currently active
379                for stream in muxbox.streams.values() {
380                    if let Some(choices) = &stream.choices {
381                        if choices.iter().any(|c| c.id == choice_id) {
382                            return Some(muxbox);
383                        }
384                    }
385                }
386                if let Some(ref children) = muxbox.children {
387                    if let Some(found) = find_in_muxboxes(children, choice_id) {
388                        return Some(found);
389                    }
390                }
391            }
392            None
393        }
394
395        if let Some(ref children) = self.children {
396            find_in_muxboxes(children, choice_id)
397        } else {
398            None
399        }
400    }
401
402    pub fn find_muxbox_at_coordinates(&self, x: u16, y: u16) -> Option<&MuxBox> {
403        let root_bounds = screen_bounds();
404        self.find_muxbox_at_coordinates_with_bounds(x, y, &root_bounds)
405    }
406
407    pub fn find_muxbox_at_coordinates_with_bounds(
408        &self,
409        x: u16,
410        y: u16,
411        root_bounds: &Bounds,
412    ) -> Option<&MuxBox> {
413        fn find_in_muxboxes_at_coords<'a>(
414            muxboxes: &'a [MuxBox],
415            x: usize,
416            y: usize,
417            parent_bounds: &Bounds,
418        ) -> Option<&'a MuxBox> {
419            // Pick the box that is VISUALLY on top at this cell, matching the
420            // renderer exactly. The renderer draws children sorted ascending by
421            // z_index with a stable sort, so the top-most box is the one with the
422            // highest z_index and, among equal z_index, the one that appears LATER
423            // in the children list (drawn last = on top). Adjacent boxes share an
424            // edge cell under inclusive bounds (e.g. a box's bottom row == the next
425            // box's tab-bar row), so returning the first match instead of the
426            // top-most one sends clicks/hover to the wrong (underneath) box.
427            let mut top: Option<(&MuxBox, Bounds)> = None;
428            let mut top_z = i32::MIN;
429            for muxbox in muxboxes {
430                let bounds = muxbox.bounds_with_parent(parent_bounds);
431                if bounds.contains_point(x, y) {
432                    let z = muxbox.effective_z_index();
433                    // `>=` so a later, equal-z box (drawn on top) replaces an earlier one.
434                    if top.is_none() || z >= top_z {
435                        top_z = z;
436                        top = Some((muxbox, bounds));
437                    }
438                }
439            }
440
441            if let Some((muxbox, bounds)) = top {
442                // Check children first (they take priority over parent)
443                if let Some(ref children) = muxbox.children {
444                    if let Some(child_muxbox) = find_in_muxboxes_at_coords(children, x, y, &bounds)
445                    {
446                        return Some(child_muxbox);
447                    }
448                }
449                // Return this muxbox (highest z_index among candidates)
450                return Some(muxbox);
451            }
452            None
453        }
454
455        if let Some(ref children) = self.children {
456            find_in_muxboxes_at_coords(children, x as usize, y as usize, root_bounds)
457        } else {
458            None
459        }
460    }
461
462    fn generate_children_diff(&self, other: &Self) -> Vec<FieldUpdate> {
463        let mut updates = Vec::new();
464
465        // Get references to children, defaulting to an empty slice if None
466        let self_children = self.children.as_deref().unwrap_or(&[]);
467        let other_children = other.children.as_deref().unwrap_or(&[]);
468
469        // Compare each pair of children
470        for (self_child, other_child) in self_children.iter().zip(other_children) {
471            let child_diffs = self_child.generate_diff(other_child);
472            updates.extend(child_diffs.into_iter());
473        }
474
475        // Handle extra children in other
476        if self_children.len() < other_children.len() {
477            for other_child in &other_children[self_children.len()..] {
478                updates.push(FieldUpdate {
479                    entity_type: EntityType::MuxBox,
480                    entity_id: Some(other_child.id.clone()),
481                    field_name: "children".to_string(),
482                    new_value: serde_json::to_value(other_child).unwrap(),
483                });
484            }
485        }
486
487        // Handle extra children in self
488        if self_children.len() > other_children.len() {
489            for self_child in &self_children[other_children.len()..] {
490                updates.push(FieldUpdate {
491                    entity_type: EntityType::MuxBox,
492                    entity_id: Some(self_child.id.clone()),
493                    field_name: "children".to_string(),
494                    new_value: Value::Null, // Representing removal
495                });
496            }
497        }
498
499        updates
500    }
501
502    fn apply_children_updates(&mut self, updates: Vec<FieldUpdate>) {
503        for update in updates {
504            if update.entity_type != EntityType::MuxBox {
505                continue;
506            }
507            if let Some(entity_id) = &update.entity_id {
508                // Check if the update is for a child muxbox
509                if self
510                    .children
511                    .as_ref()
512                    .is_some_and(|children| children.iter().any(|p| p.id == *entity_id))
513                {
514                    // Find the child muxbox and apply the update
515                    if let Some(child_muxbox) = self
516                        .children
517                        .as_mut()
518                        .unwrap()
519                        .iter_mut()
520                        .find(|p| p.id == *entity_id)
521                    {
522                        child_muxbox.apply_updates(vec![FieldUpdate {
523                            entity_type: EntityType::MuxBox,
524                            entity_id: Some(child_muxbox.id.clone()),
525                            field_name: update.field_name.clone(),
526                            new_value: update.new_value.clone(),
527                        }]);
528                    }
529                }
530            }
531
532            // If the entity_id matches the parent itself and field is "children", apply to all children
533            if update.field_name == "children" {
534                match update.new_value {
535                    Value::Null => {
536                        // Removing all children
537                        self.children = None;
538                    }
539                    _ => {
540                        if let Ok(new_children) =
541                            serde_json::from_value::<Vec<MuxBox>>(update.new_value.clone())
542                        {
543                            if self.children.is_none() {
544                                // Assign new children
545                                self.children = Some(new_children);
546                            } else {
547                                let self_children = self.children.as_mut().unwrap();
548                                for new_child in new_children {
549                                    if let Some(existing_child) =
550                                        self_children.iter_mut().find(|p| p.id == new_child.id)
551                                    {
552                                        // Update existing child
553                                        *existing_child = new_child;
554                                    } else {
555                                        // Add new child
556                                        self_children.push(new_child);
557                                    }
558                                }
559                            }
560                        }
561                    }
562                }
563            }
564        }
565    }
566}
567
568impl Clone for Layout {
569    fn clone(&self) -> Self {
570        let mut cloned_children = None;
571        if let Some(ref children) = self.children {
572            cloned_children = Some(children.to_vec());
573        }
574
575        Layout {
576            id: self.id.clone(),
577            title: self.title.clone(),
578            refresh_interval: self.refresh_interval,
579            children: cloned_children,
580            fill: self.fill,
581            fill_char: self.fill_char,
582            selected_fill_char: self.selected_fill_char,
583            highlighted_fill_char: self.highlighted_fill_char,
584            border_color: self.border_color.clone(),
585            selected_border_color: self.selected_border_color.clone(),
586            highlighted_border_color: self.highlighted_border_color.clone(),
587            bg_color: self.bg_color.clone(),
588            selected_bg_color: self.selected_bg_color.clone(),
589            highlighted_bg_color: self.highlighted_bg_color.clone(),
590            fg_color: self.fg_color.clone(),
591            selected_fg_color: self.selected_fg_color.clone(),
592            highlighted_fg_color: self.highlighted_fg_color.clone(),
593            title_fg_color: self.title_fg_color.clone(),
594            title_bg_color: self.title_bg_color.clone(),
595            title_position: self.title_position.clone(),
596            selected_title_bg_color: self.selected_title_bg_color.clone(),
597            selected_title_fg_color: self.selected_title_fg_color.clone(),
598            highlighted_title_bg_color: self.highlighted_title_bg_color.clone(),
599            highlighted_title_fg_color: self.highlighted_title_fg_color.clone(),
600            menu_fg_color: self.menu_fg_color.clone(),
601            menu_bg_color: self.menu_bg_color.clone(),
602            selected_menu_fg_color: self.selected_menu_fg_color.clone(),
603            selected_menu_bg_color: self.selected_menu_bg_color.clone(),
604            highlighted_menu_fg_color: self.highlighted_menu_fg_color.clone(),
605            highlighted_menu_bg_color: self.highlighted_menu_bg_color.clone(),
606            error_border_color: self.error_border_color.clone(),
607            error_bg_color: self.error_bg_color.clone(),
608            error_fg_color: self.error_fg_color.clone(),
609            error_title_bg_color: self.error_title_bg_color.clone(),
610            error_title_fg_color: self.error_title_fg_color.clone(),
611            error_selected_border_color: self.error_selected_border_color.clone(),
612            error_selected_bg_color: self.error_selected_bg_color.clone(),
613            error_selected_fg_color: self.error_selected_fg_color.clone(),
614            error_selected_title_bg_color: self.error_selected_title_bg_color.clone(),
615            error_selected_title_fg_color: self.error_selected_title_fg_color.clone(),
616            overflow_behavior: self.overflow_behavior.clone(),
617            root: self.root,
618            on_keypress: self.on_keypress.clone(),
619            active: self.active,
620            muxbox_ids_in_tab_order: self.muxbox_ids_in_tab_order.clone(),
621        }
622    }
623}
624
625// Implement Updatable for Layout
626impl Updatable for Layout {
627    fn generate_diff(&self, other: &Self) -> Vec<FieldUpdate> {
628        let mut updates = Vec::new();
629
630        // Compare each field
631        if self.title != other.title {
632            if let Some(new_value) = &other.title {
633                updates.push(FieldUpdate {
634                    entity_type: EntityType::Layout,
635                    entity_id: Some(self.id.clone()),
636                    field_name: "title".to_string(),
637                    new_value: serde_json::to_value(new_value).unwrap(),
638                });
639            }
640        }
641        if self.refresh_interval != other.refresh_interval {
642            if let Some(new_value) = other.refresh_interval {
643                updates.push(FieldUpdate {
644                    entity_type: EntityType::Layout,
645                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
646                    field_name: "refresh_interval".to_string(),
647                    new_value: serde_json::to_value(new_value).unwrap(),
648                });
649            }
650        }
651
652        updates.extend(self.generate_children_diff(other));
653
654        // Compare other fields similarly...
655        if self.fill != other.fill {
656            if let Some(new_value) = other.fill {
657                updates.push(FieldUpdate {
658                    entity_type: EntityType::Layout,
659                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
660                    field_name: "fill".to_string(),
661                    new_value: serde_json::to_value(new_value).unwrap(),
662                });
663            }
664        }
665
666        if self.fill_char != other.fill_char {
667            if let Some(new_value) = other.fill_char {
668                updates.push(FieldUpdate {
669                    entity_type: EntityType::Layout,
670                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
671                    field_name: "fill_char".to_string(),
672                    new_value: serde_json::to_value(new_value).unwrap(),
673                });
674            }
675        }
676
677        if self.selected_fill_char != other.selected_fill_char {
678            if let Some(new_value) = other.selected_fill_char {
679                updates.push(FieldUpdate {
680                    entity_type: EntityType::Layout,
681                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
682                    field_name: "selected_fill_char".to_string(),
683                    new_value: serde_json::to_value(new_value).unwrap(),
684                });
685            }
686        }
687
688        if self.border_color != other.border_color {
689            if let Some(new_value) = &other.border_color {
690                updates.push(FieldUpdate {
691                    entity_type: EntityType::Layout,
692                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
693                    field_name: "border_color".to_string(),
694                    new_value: serde_json::to_value(new_value).unwrap(),
695                });
696            }
697        }
698
699        if self.selected_border_color != other.selected_border_color {
700            if let Some(new_value) = &other.selected_border_color {
701                updates.push(FieldUpdate {
702                    entity_type: EntityType::Layout,
703                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
704                    field_name: "selected_border_color".to_string(),
705                    new_value: serde_json::to_value(new_value).unwrap(),
706                });
707            }
708        }
709
710        if self.bg_color != other.bg_color {
711            if let Some(new_value) = &other.bg_color {
712                updates.push(FieldUpdate {
713                    entity_type: EntityType::Layout,
714                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
715                    field_name: "bg_color".to_string(),
716                    new_value: serde_json::to_value(new_value).unwrap(),
717                });
718            }
719        }
720
721        if self.selected_bg_color != other.selected_bg_color {
722            if let Some(new_value) = &other.selected_bg_color {
723                updates.push(FieldUpdate {
724                    entity_type: EntityType::Layout,
725                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
726                    field_name: "selected_bg_color".to_string(),
727                    new_value: serde_json::to_value(new_value).unwrap(),
728                });
729            }
730        }
731
732        if self.fg_color != other.fg_color {
733            if let Some(new_value) = &other.fg_color {
734                updates.push(FieldUpdate {
735                    entity_type: EntityType::Layout,
736                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
737                    field_name: "fg_color".to_string(),
738                    new_value: serde_json::to_value(new_value).unwrap(),
739                });
740            }
741        }
742
743        if self.selected_fg_color != other.selected_fg_color {
744            if let Some(new_value) = &other.selected_fg_color {
745                updates.push(FieldUpdate {
746                    entity_type: EntityType::Layout,
747                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
748                    field_name: "selected_fg_color".to_string(),
749                    new_value: serde_json::to_value(new_value).unwrap(),
750                });
751            }
752        }
753
754        if self.title_fg_color != other.title_fg_color {
755            if let Some(new_value) = &other.title_fg_color {
756                updates.push(FieldUpdate {
757                    entity_type: EntityType::Layout,
758                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
759                    field_name: "title_fg_color".to_string(),
760                    new_value: serde_json::to_value(new_value).unwrap(),
761                });
762            }
763        }
764
765        if self.title_bg_color != other.title_bg_color {
766            if let Some(new_value) = &other.title_bg_color {
767                updates.push(FieldUpdate {
768                    entity_type: EntityType::Layout,
769                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
770                    field_name: "title_bg_color".to_string(),
771                    new_value: serde_json::to_value(new_value).unwrap(),
772                });
773            }
774        }
775
776        if self.title_position != other.title_position {
777            if let Some(new_value) = &other.title_position {
778                updates.push(FieldUpdate {
779                    entity_type: EntityType::Layout,
780                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
781                    field_name: "title_position".to_string(),
782                    new_value: serde_json::to_value(new_value).unwrap(),
783                });
784            }
785        }
786
787        if self.selected_title_bg_color != other.selected_title_bg_color {
788            if let Some(new_value) = &other.selected_title_bg_color {
789                updates.push(FieldUpdate {
790                    entity_type: EntityType::Layout,
791                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
792                    field_name: "selected_title_bg_color".to_string(),
793                    new_value: serde_json::to_value(new_value).unwrap(),
794                });
795            }
796        }
797
798        if self.selected_title_fg_color != other.selected_title_fg_color {
799            if let Some(new_value) = &other.selected_title_fg_color {
800                updates.push(FieldUpdate {
801                    entity_type: EntityType::Layout,
802                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
803                    field_name: "selected_title_fg_color".to_string(),
804                    new_value: serde_json::to_value(new_value).unwrap(),
805                });
806            }
807        }
808
809        if self.menu_fg_color != other.menu_fg_color {
810            if let Some(new_value) = &other.menu_fg_color {
811                updates.push(FieldUpdate {
812                    entity_type: EntityType::Layout,
813                    entity_id: Some(self.id.clone()),
814                    field_name: "menu_fg_color".to_string(),
815                    new_value: serde_json::to_value(new_value).unwrap(),
816                });
817            }
818        }
819
820        if self.menu_bg_color != other.menu_bg_color {
821            if let Some(new_value) = &other.menu_bg_color {
822                updates.push(FieldUpdate {
823                    entity_type: EntityType::Layout,
824                    entity_id: Some(self.id.clone()),
825                    field_name: "menu_bg_color".to_string(),
826                    new_value: serde_json::to_value(new_value).unwrap(),
827                });
828            }
829        }
830
831        if self.selected_menu_fg_color != other.selected_menu_fg_color {
832            if let Some(new_value) = &other.selected_menu_fg_color {
833                updates.push(FieldUpdate {
834                    entity_type: EntityType::Layout,
835                    entity_id: Some(self.id.clone()),
836                    field_name: "selected_menu_fg_color".to_string(),
837                    new_value: serde_json::to_value(new_value).unwrap(),
838                });
839            }
840        }
841
842        if self.selected_menu_bg_color != other.selected_menu_bg_color {
843            if let Some(new_value) = &other.selected_menu_bg_color {
844                updates.push(FieldUpdate {
845                    entity_type: EntityType::Layout,
846                    entity_id: Some(self.id.clone()),
847                    field_name: "selected_menu_bg_color".to_string(),
848                    new_value: serde_json::to_value(new_value).unwrap(),
849                });
850            }
851        }
852
853        if self.error_border_color != other.error_border_color {
854            if let Some(new_value) = &other.error_border_color {
855                updates.push(FieldUpdate {
856                    entity_type: EntityType::Layout,
857                    entity_id: Some(self.id.clone()),
858                    field_name: "error_border_color".to_string(),
859                    new_value: serde_json::to_value(new_value).unwrap(),
860                });
861            }
862        }
863
864        if self.error_bg_color != other.error_bg_color {
865            if let Some(new_value) = &other.error_bg_color {
866                updates.push(FieldUpdate {
867                    entity_type: EntityType::Layout,
868                    entity_id: Some(self.id.clone()),
869                    field_name: "error_bg_color".to_string(),
870                    new_value: serde_json::to_value(new_value).unwrap(),
871                });
872            }
873        }
874
875        if self.error_fg_color != other.error_fg_color {
876            if let Some(new_value) = &other.error_fg_color {
877                updates.push(FieldUpdate {
878                    entity_type: EntityType::Layout,
879                    entity_id: Some(self.id.clone()),
880                    field_name: "error_fg_color".to_string(),
881                    new_value: serde_json::to_value(new_value).unwrap(),
882                });
883            }
884        }
885
886        if self.error_title_bg_color != other.error_title_bg_color {
887            if let Some(new_value) = &other.error_title_bg_color {
888                updates.push(FieldUpdate {
889                    entity_type: EntityType::Layout,
890                    entity_id: Some(self.id.clone()),
891                    field_name: "error_title_bg_color".to_string(),
892                    new_value: serde_json::to_value(new_value).unwrap(),
893                });
894            }
895        }
896
897        if self.error_title_fg_color != other.error_title_fg_color {
898            if let Some(new_value) = &other.error_title_fg_color {
899                updates.push(FieldUpdate {
900                    entity_type: EntityType::Layout,
901                    entity_id: Some(self.id.clone()),
902                    field_name: "error_title_fg_color".to_string(),
903                    new_value: serde_json::to_value(new_value).unwrap(),
904                });
905            }
906        }
907
908        if self.error_selected_border_color != other.error_selected_border_color {
909            if let Some(new_value) = &other.error_selected_border_color {
910                updates.push(FieldUpdate {
911                    entity_type: EntityType::Layout,
912                    entity_id: Some(self.id.clone()),
913                    field_name: "error_selected_border_color".to_string(),
914                    new_value: serde_json::to_value(new_value).unwrap(),
915                });
916            }
917        }
918
919        if self.error_selected_bg_color != other.error_selected_bg_color {
920            if let Some(new_value) = &other.error_selected_bg_color {
921                updates.push(FieldUpdate {
922                    entity_type: EntityType::Layout,
923                    entity_id: Some(self.id.clone()),
924                    field_name: "error_selected_bg_color".to_string(),
925                    new_value: serde_json::to_value(new_value).unwrap(),
926                });
927            }
928        }
929
930        if self.error_selected_fg_color != other.error_selected_fg_color {
931            if let Some(new_value) = &other.error_selected_fg_color {
932                updates.push(FieldUpdate {
933                    entity_type: EntityType::Layout,
934                    entity_id: Some(self.id.clone()),
935                    field_name: "error_selected_fg_color".to_string(),
936                    new_value: serde_json::to_value(new_value).unwrap(),
937                });
938            }
939        }
940
941        if self.error_selected_title_bg_color != other.error_selected_title_bg_color {
942            if let Some(new_value) = &other.error_selected_title_bg_color {
943                updates.push(FieldUpdate {
944                    entity_type: EntityType::Layout,
945                    entity_id: Some(self.id.clone()),
946                    field_name: "error_selected_title_bg_color".to_string(),
947                    new_value: serde_json::to_value(new_value).unwrap(),
948                });
949            }
950        }
951
952        if self.error_selected_title_fg_color != other.error_selected_title_fg_color {
953            if let Some(new_value) = &other.error_selected_title_fg_color {
954                updates.push(FieldUpdate {
955                    entity_type: EntityType::Layout,
956                    entity_id: Some(self.id.clone()),
957                    field_name: "error_selected_title_fg_color".to_string(),
958                    new_value: serde_json::to_value(new_value).unwrap(),
959                });
960            }
961        }
962
963        if self.overflow_behavior != other.overflow_behavior {
964            if let Some(new_value) = &other.overflow_behavior {
965                updates.push(FieldUpdate {
966                    entity_type: EntityType::Layout,
967                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
968                    field_name: "overflow_behavior".to_string(),
969                    new_value: serde_json::to_value(new_value).unwrap(),
970                });
971            }
972        }
973
974        if self.root != other.root {
975            if let Some(new_value) = other.root {
976                updates.push(FieldUpdate {
977                    entity_type: EntityType::Layout,
978                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
979                    field_name: "root".to_string(),
980                    new_value: serde_json::to_value(new_value).unwrap(),
981                });
982            }
983        }
984
985        if self.on_keypress != other.on_keypress {
986            if let Some(new_value) = &other.on_keypress {
987                updates.push(FieldUpdate {
988                    entity_type: EntityType::Layout,
989                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
990                    field_name: "on_keypress".to_string(),
991                    new_value: serde_json::to_value(new_value).unwrap(),
992                });
993            }
994        }
995
996        if self.active != other.active {
997            if let Some(new_value) = other.active {
998                updates.push(FieldUpdate {
999                    entity_type: EntityType::Layout,
1000                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
1001                    field_name: "active".to_string(),
1002                    new_value: serde_json::to_value(new_value).unwrap(),
1003                });
1004            }
1005        }
1006
1007        if self.muxbox_ids_in_tab_order != other.muxbox_ids_in_tab_order {
1008            if let Some(new_value) = &other.muxbox_ids_in_tab_order {
1009                updates.push(FieldUpdate {
1010                    entity_type: EntityType::Layout,
1011                    entity_id: Some(self.id.clone()), // This is the entity id of the layout, not the muxbox
1012                    field_name: "muxbox_ids_in_tab_order".to_string(),
1013                    new_value: serde_json::to_value(new_value).unwrap(),
1014                });
1015            }
1016        }
1017
1018        updates
1019    }
1020
1021    fn apply_updates(&mut self, updates: Vec<FieldUpdate>) {
1022        let updates_for_children = updates.clone();
1023
1024        for update in updates {
1025            if update.entity_type != EntityType::Layout {
1026                continue;
1027            }
1028            match update.field_name.as_str() {
1029                "title" => {
1030                    if let Some(new_title) = update.new_value.as_str() {
1031                        self.title = Some(new_title.to_string());
1032                    }
1033                }
1034                "refresh_interval" => {
1035                    if let Some(new_refresh_interval) = update.new_value.as_u64() {
1036                        self.refresh_interval = Some(new_refresh_interval);
1037                    }
1038                }
1039                "fill" => {
1040                    if let Some(new_fill) = update.new_value.as_bool() {
1041                        self.fill = Some(new_fill);
1042                    }
1043                }
1044                "fill_char" => {
1045                    if let Some(new_fill_char) = update.new_value.as_str() {
1046                        self.fill_char = new_fill_char.chars().next();
1047                    }
1048                }
1049                "selected_fill_char" => {
1050                    if let Some(new_selected_fill_char) = update.new_value.as_str() {
1051                        self.selected_fill_char = new_selected_fill_char.chars().next();
1052                    }
1053                }
1054                "border_color" => {
1055                    if let Some(new_border_color) = update.new_value.as_str() {
1056                        self.border_color = Some(new_border_color.to_string());
1057                    }
1058                }
1059                "selected_border_color" => {
1060                    if let Some(new_selected_border_color) = update.new_value.as_str() {
1061                        self.selected_border_color = Some(new_selected_border_color.to_string());
1062                    }
1063                }
1064                "bg_color" => {
1065                    if let Some(new_bg_color) = update.new_value.as_str() {
1066                        self.bg_color = Some(new_bg_color.to_string());
1067                    }
1068                }
1069                "selected_bg_color" => {
1070                    if let Some(new_selected_bg_color) = update.new_value.as_str() {
1071                        self.selected_bg_color = Some(new_selected_bg_color.to_string());
1072                    }
1073                }
1074                "fg_color" => {
1075                    if let Some(new_fg_color) = update.new_value.as_str() {
1076                        self.fg_color = Some(new_fg_color.to_string());
1077                    }
1078                }
1079                "selected_fg_color" => {
1080                    if let Some(new_selected_fg_color) = update.new_value.as_str() {
1081                        self.selected_fg_color = Some(new_selected_fg_color.to_string());
1082                    }
1083                }
1084                "title_fg_color" => {
1085                    if let Some(new_title_fg_color) = update.new_value.as_str() {
1086                        self.title_fg_color = Some(new_title_fg_color.to_string());
1087                    }
1088                }
1089                "title_bg_color" => {
1090                    if let Some(new_title_bg_color) = update.new_value.as_str() {
1091                        self.title_bg_color = Some(new_title_bg_color.to_string());
1092                    }
1093                }
1094                "title_position" => {
1095                    if let Some(new_title_position) = update.new_value.as_str() {
1096                        self.title_position = Some(new_title_position.to_string());
1097                    }
1098                }
1099                "selected_title_bg_color" => {
1100                    if let Some(new_selected_title_bg_color) = update.new_value.as_str() {
1101                        self.selected_title_bg_color =
1102                            Some(new_selected_title_bg_color.to_string());
1103                    }
1104                }
1105                "selected_title_fg_color" => {
1106                    if let Some(new_selected_title_fg_color) = update.new_value.as_str() {
1107                        self.selected_title_fg_color =
1108                            Some(new_selected_title_fg_color.to_string());
1109                    }
1110                }
1111                "menu_fg_color" => {
1112                    if let Some(new_menu_fg_color) = update.new_value.as_str() {
1113                        self.menu_fg_color = Some(new_menu_fg_color.to_string());
1114                    }
1115                }
1116                "menu_bg_color" => {
1117                    if let Some(new_menu_bg_color) = update.new_value.as_str() {
1118                        self.menu_bg_color = Some(new_menu_bg_color.to_string());
1119                    }
1120                }
1121                "selected_menu_fg_color" => {
1122                    if let Some(new_selected_menu_fg_color) = update.new_value.as_str() {
1123                        self.selected_menu_fg_color = Some(new_selected_menu_fg_color.to_string());
1124                    }
1125                }
1126                "selected_menu_bg_color" => {
1127                    if let Some(new_selected_menu_bg_color) = update.new_value.as_str() {
1128                        self.selected_menu_bg_color = Some(new_selected_menu_bg_color.to_string());
1129                    }
1130                }
1131                "error_border_color" => {
1132                    if let Some(new_error_border_color) = update.new_value.as_str() {
1133                        self.error_border_color = Some(new_error_border_color.to_string());
1134                    }
1135                }
1136                "error_bg_color" => {
1137                    if let Some(new_error_bg_color) = update.new_value.as_str() {
1138                        self.error_bg_color = Some(new_error_bg_color.to_string());
1139                    }
1140                }
1141                "error_fg_color" => {
1142                    if let Some(new_error_fg_color) = update.new_value.as_str() {
1143                        self.error_fg_color = Some(new_error_fg_color.to_string());
1144                    }
1145                }
1146                "error_title_bg_color" => {
1147                    if let Some(new_error_title_bg_color) = update.new_value.as_str() {
1148                        self.error_title_bg_color = Some(new_error_title_bg_color.to_string());
1149                    }
1150                }
1151                "error_title_fg_color" => {
1152                    if let Some(new_error_title_fg_color) = update.new_value.as_str() {
1153                        self.error_title_fg_color = Some(new_error_title_fg_color.to_string());
1154                    }
1155                }
1156                "error_selected_border_color" => {
1157                    if let Some(new_error_selected_border_color) = update.new_value.as_str() {
1158                        self.error_selected_border_color =
1159                            Some(new_error_selected_border_color.to_string());
1160                    }
1161                }
1162                "error_selected_bg_color" => {
1163                    if let Some(new_error_selected_bg_color) = update.new_value.as_str() {
1164                        self.error_selected_bg_color =
1165                            Some(new_error_selected_bg_color.to_string());
1166                    }
1167                }
1168                "error_selected_fg_color" => {
1169                    if let Some(new_error_selected_fg_color) = update.new_value.as_str() {
1170                        self.error_selected_fg_color =
1171                            Some(new_error_selected_fg_color.to_string());
1172                    }
1173                }
1174                "error_selected_title_bg_color" => {
1175                    if let Some(new_error_selected_title_bg_color) = update.new_value.as_str() {
1176                        self.error_selected_title_bg_color =
1177                            Some(new_error_selected_title_bg_color.to_string());
1178                    }
1179                }
1180                "error_selected_title_fg_color" => {
1181                    if let Some(new_error_selected_title_fg_color) = update.new_value.as_str() {
1182                        self.error_selected_title_fg_color =
1183                            Some(new_error_selected_title_fg_color.to_string());
1184                    }
1185                }
1186                "overflow_behavior" => {
1187                    if let Some(new_overflow_behavior) = update.new_value.as_str() {
1188                        self.overflow_behavior = Some(new_overflow_behavior.to_string());
1189                    }
1190                }
1191                "root" => {
1192                    if let Some(new_root) = update.new_value.as_bool() {
1193                        self.root = Some(new_root);
1194                    }
1195                }
1196                "on_keypress" => {
1197                    if let Some(new_on_keypress) = update.new_value.as_object() {
1198                        self.on_keypress = Some(
1199                            new_on_keypress
1200                                .iter()
1201                                .map(|(k, v)| {
1202                                    (
1203                                        k.clone(),
1204                                        v.as_array()
1205                                            .unwrap()
1206                                            .iter()
1207                                            .map(|v| v.as_str().unwrap().to_string())
1208                                            .collect(),
1209                                    )
1210                                })
1211                                .collect(),
1212                        );
1213                    }
1214                }
1215                "active" => {
1216                    if let Some(new_active) = update.new_value.as_bool() {
1217                        self.active = Some(new_active);
1218                    }
1219                }
1220                "muxbox_ids_in_tab_order" => {
1221                    if let Some(new_muxbox_ids_in_tab_order) = update.new_value.as_array() {
1222                        self.muxbox_ids_in_tab_order = Some(
1223                            new_muxbox_ids_in_tab_order
1224                                .iter()
1225                                .map(|v| v.as_str().unwrap().to_string())
1226                                .collect(),
1227                        );
1228                    }
1229                }
1230
1231                _ => {
1232                    log::warn!(
1233                        "Layout::apply_updates: Ignoring unknown field name: {}",
1234                        update.field_name
1235                    );
1236                }
1237            }
1238        }
1239        self.apply_children_updates(updates_for_children);
1240    }
1241}
1242
1243#[cfg(test)]
1244mod tests {
1245    use super::*;
1246    use crate::model::common::InputBounds;
1247    use crate::model::muxbox::MuxBox;
1248
1249    // === Helper Functions ===
1250
1251    /// Creates a basic test muxbox with the given id.
1252    /// This helper demonstrates how to create a MuxBox for Layout testing.
1253    fn create_test_muxbox(id: &str) -> MuxBox {
1254        MuxBox {
1255            id: id.to_string(),
1256            title: Some(format!("Test MuxBox {}", id)),
1257            position: InputBounds {
1258                x1: "0%".to_string(),
1259                y1: "0%".to_string(),
1260                x2: "100%".to_string(),
1261                y2: "100%".to_string(),
1262            },
1263            tab_order: Some(id.to_string()),
1264            selected: Some(false),
1265            ..Default::default()
1266        }
1267    }
1268
1269    /// Creates a test Layout with the given id and optional children.
1270    /// This helper demonstrates how to create a Layout for testing.
1271    fn create_test_layout(id: &str, children: Option<Vec<MuxBox>>) -> Layout {
1272        Layout {
1273            id: id.to_string(),
1274            title: Some(format!("Test Layout {}", id)),
1275            children,
1276            root: Some(false),
1277            active: Some(false),
1278            ..Default::default()
1279        }
1280    }
1281
1282    // === Layout Default Tests ===
1283
1284    /// Tests that Layout::default() creates a layout with expected default values.
1285    /// This test demonstrates the default Layout construction behavior.
1286    #[test]
1287    fn test_layout_default() {
1288        let layout = Layout::default();
1289        assert_eq!(layout.id, "");
1290        assert_eq!(layout.title, None);
1291        assert_eq!(layout.children, None);
1292        assert_eq!(layout.root, None);
1293        assert_eq!(layout.active, None);
1294        assert_eq!(layout.refresh_interval, None);
1295        assert_eq!(layout.muxbox_ids_in_tab_order, None);
1296    }
1297
1298    /// Tests that Layout::new() creates a layout with expected default values.
1299    /// This test demonstrates the Layout::new() construction behavior.
1300    #[test]
1301    fn test_layout_new() {
1302        let layout = Layout::new();
1303        assert_eq!(layout.id, "");
1304        assert_eq!(layout.title, None);
1305        assert_eq!(layout.children, None);
1306        assert_eq!(layout.root, Some(false));
1307        assert_eq!(layout.active, Some(false));
1308        assert_eq!(layout.refresh_interval, None);
1309        assert_eq!(layout.muxbox_ids_in_tab_order, None);
1310    }
1311
1312    // === Layout Creation Tests ===
1313
1314    /// Tests creating a Layout with specific values.
1315    /// This test demonstrates how to create a Layout with custom properties.
1316    #[test]
1317    fn test_layout_creation() {
1318        let muxbox1 = create_test_muxbox("muxbox1");
1319        let muxbox2 = create_test_muxbox("muxbox2");
1320        let children = vec![muxbox1, muxbox2];
1321
1322        let layout = Layout {
1323            id: "test_layout".to_string(),
1324            title: Some("Test Layout".to_string()),
1325            children: Some(children),
1326            root: Some(true),
1327            active: Some(true),
1328            refresh_interval: Some(1000),
1329            ..Default::default()
1330        };
1331
1332        assert_eq!(layout.id, "test_layout");
1333        assert_eq!(layout.title, Some("Test Layout".to_string()));
1334        assert_eq!(layout.children.as_ref().unwrap().len(), 2);
1335        assert_eq!(layout.root, Some(true));
1336        assert_eq!(layout.active, Some(true));
1337        assert_eq!(layout.refresh_interval, Some(1000));
1338    }
1339
1340    // === Layout MuxBox Management Tests ===
1341
1342    /// Tests that Layout::get_muxbox_by_id() finds muxboxes correctly.
1343    /// This test demonstrates the muxbox retrieval feature.
1344    #[test]
1345    fn test_layout_get_muxbox_by_id() {
1346        let muxbox1 = create_test_muxbox("muxbox1");
1347        let muxbox2 = create_test_muxbox("muxbox2");
1348        let layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1349
1350        let found_muxbox = layout.get_muxbox_by_id("muxbox1");
1351        assert!(found_muxbox.is_some());
1352        assert_eq!(found_muxbox.unwrap().id, "muxbox1");
1353
1354        let not_found = layout.get_muxbox_by_id("nonexistent");
1355        assert!(not_found.is_none());
1356    }
1357
1358    /// Tests that Layout::get_muxbox_by_id() finds nested muxboxes correctly.
1359    /// This test demonstrates the recursive muxbox retrieval feature.
1360    #[test]
1361    fn test_layout_get_muxbox_by_id_nested() {
1362        let child_muxbox = create_test_muxbox("child");
1363        let parent_muxbox = MuxBox {
1364            id: "parent".to_string(),
1365            children: Some(vec![child_muxbox]),
1366            ..Default::default()
1367        };
1368        let layout = create_test_layout("test", Some(vec![parent_muxbox]));
1369
1370        let found_child = layout.get_muxbox_by_id("child");
1371        assert!(found_child.is_some());
1372        assert_eq!(found_child.unwrap().id, "child");
1373    }
1374
1375    /// Tests that Layout::get_muxbox_by_id_mut() finds and allows modification.
1376    /// This test demonstrates the mutable muxbox retrieval feature.
1377    #[test]
1378    fn test_layout_get_muxbox_by_id_mut() {
1379        let muxbox1 = create_test_muxbox("muxbox1");
1380        let muxbox2 = create_test_muxbox("muxbox2");
1381        let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1382
1383        let found_muxbox = layout.get_muxbox_by_id_mut("muxbox1");
1384        assert!(found_muxbox.is_some());
1385
1386        // Modify the muxbox
1387        found_muxbox.unwrap().title = Some("Modified Title".to_string());
1388
1389        // Verify the modification
1390        let verified_muxbox = layout.get_muxbox_by_id("muxbox1");
1391        assert_eq!(
1392            verified_muxbox.unwrap().title,
1393            Some("Modified Title".to_string())
1394        );
1395    }
1396
1397    /// Tests that Layout::get_muxbox_by_id_mut() handles empty layout.
1398    /// This test demonstrates edge case handling in mutable muxbox retrieval.
1399    #[test]
1400    fn test_layout_get_muxbox_by_id_mut_empty() {
1401        let mut layout = create_test_layout("test", None);
1402
1403        let found_muxbox = layout.get_muxbox_by_id_mut("nonexistent");
1404        assert!(found_muxbox.is_none());
1405    }
1406
1407    // === Layout MuxBox Selection Tests ===
1408
1409    /// Tests that Layout::get_selected_muxboxes() returns selected muxboxes.
1410    /// This test demonstrates the selected muxbox retrieval feature.
1411    #[test]
1412    fn test_layout_get_selected_muxboxes() {
1413        let mut muxbox1 = create_test_muxbox("muxbox1");
1414        let mut muxbox2 = create_test_muxbox("muxbox2");
1415        let mut muxbox3 = create_test_muxbox("muxbox3");
1416
1417        muxbox1.selected = Some(true);
1418        muxbox2.selected = Some(false);
1419        muxbox3.selected = Some(true);
1420
1421        let layout = create_test_layout("test", Some(vec![muxbox1, muxbox2, muxbox3]));
1422
1423        let selected = layout.get_selected_muxboxes();
1424        assert_eq!(selected.len(), 2);
1425        assert_eq!(selected[0].id, "muxbox1");
1426        assert_eq!(selected[1].id, "muxbox3");
1427    }
1428
1429    /// Tests that Layout::get_selected_muxboxes() handles no selected muxboxes.
1430    /// This test demonstrates edge case handling in selected muxbox retrieval.
1431    #[test]
1432    fn test_layout_get_selected_muxboxes_none() {
1433        let muxbox1 = create_test_muxbox("muxbox1");
1434        let muxbox2 = create_test_muxbox("muxbox2");
1435        let layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1436
1437        let selected = layout.get_selected_muxboxes();
1438        assert_eq!(selected.len(), 0);
1439    }
1440
1441    /// Tests that Layout::select_only_muxbox() selects only the specified muxbox.
1442    /// This test demonstrates the exclusive muxbox selection feature.
1443    #[test]
1444    fn test_layout_select_only_muxbox() {
1445        let mut muxbox1 = create_test_muxbox("muxbox1");
1446        let mut muxbox2 = create_test_muxbox("muxbox2");
1447        let mut muxbox3 = create_test_muxbox("muxbox3");
1448
1449        muxbox1.selected = Some(true);
1450        muxbox2.selected = Some(true);
1451        muxbox3.selected = Some(false);
1452
1453        let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2, muxbox3]));
1454
1455        layout.select_only_muxbox("muxbox2");
1456
1457        let selected = layout.get_selected_muxboxes();
1458        assert_eq!(selected.len(), 1);
1459        assert_eq!(selected[0].id, "muxbox2");
1460    }
1461
1462    /// Tests that Layout::select_only_muxbox() handles nonexistent muxbox.
1463    /// This test demonstrates edge case handling in muxbox selection.
1464    #[test]
1465    fn test_layout_select_only_muxbox_nonexistent() {
1466        let mut muxbox1 = create_test_muxbox("muxbox1");
1467        muxbox1.selected = Some(true);
1468
1469        let mut layout = create_test_layout("test", Some(vec![muxbox1]));
1470
1471        layout.select_only_muxbox("nonexistent");
1472
1473        // All muxboxes should be deselected
1474        let selected = layout.get_selected_muxboxes();
1475        assert_eq!(selected.len(), 0);
1476    }
1477
1478    /// Tests that Layout::deselect_all_muxboxes() deselects all muxboxes.
1479    /// This test demonstrates the muxbox deselection feature.
1480    #[test]
1481    fn test_layout_deselect_all_muxboxes() {
1482        let mut muxbox1 = create_test_muxbox("muxbox1");
1483        let mut muxbox2 = create_test_muxbox("muxbox2");
1484
1485        muxbox1.selected = Some(true);
1486        muxbox2.selected = Some(true);
1487
1488        let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1489
1490        layout.deselect_all_muxboxes();
1491
1492        let selected = layout.get_selected_muxboxes();
1493        assert_eq!(selected.len(), 0);
1494    }
1495
1496    // === Layout Tab Order Tests ===
1497
1498    /// Tests that Layout::get_muxboxes_in_tab_order() returns muxboxes in tab order.
1499    /// This test demonstrates the tab order retrieval feature.
1500    #[test]
1501    fn test_layout_get_muxboxes_in_tab_order() {
1502        let mut muxbox1 = create_test_muxbox("muxbox1");
1503        let mut muxbox2 = create_test_muxbox("muxbox2");
1504        let mut muxbox3 = create_test_muxbox("muxbox3");
1505
1506        muxbox1.tab_order = Some("3".to_string());
1507        muxbox2.tab_order = Some("1".to_string());
1508        muxbox3.tab_order = Some("2".to_string());
1509
1510        let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2, muxbox3]));
1511
1512        let muxboxes_in_order = layout.get_muxboxes_in_tab_order();
1513        assert_eq!(muxboxes_in_order.len(), 3);
1514        assert_eq!(muxboxes_in_order[0].id, "muxbox2"); // tab_order: "1"
1515        assert_eq!(muxboxes_in_order[1].id, "muxbox3"); // tab_order: "2"
1516        assert_eq!(muxboxes_in_order[2].id, "muxbox1"); // tab_order: "3"
1517    }
1518
1519    /// Tests that Layout::get_muxboxes_in_tab_order() ignores muxboxes without tab_order.
1520    /// This test demonstrates tab order filtering behavior.
1521    #[test]
1522    fn test_layout_get_muxboxes_in_tab_order_filtered() {
1523        let mut muxbox1 = create_test_muxbox("muxbox1");
1524        let mut muxbox2 = create_test_muxbox("muxbox2");
1525        let mut muxbox3 = create_test_muxbox("muxbox3");
1526
1527        muxbox1.tab_order = Some("1".to_string());
1528        muxbox2.tab_order = None; // No tab order
1529        muxbox3.tab_order = Some("2".to_string());
1530
1531        let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2, muxbox3]));
1532
1533        let muxboxes_in_order = layout.get_muxboxes_in_tab_order();
1534        assert_eq!(muxboxes_in_order.len(), 2);
1535        assert_eq!(muxboxes_in_order[0].id, "muxbox1");
1536        assert_eq!(muxboxes_in_order[1].id, "muxbox3");
1537    }
1538
1539    /// Tests that Layout::get_muxboxes_in_tab_order() handles nested muxboxes.
1540    /// This test demonstrates recursive tab order retrieval.
1541    #[test]
1542    fn test_layout_get_muxboxes_in_tab_order_nested() {
1543        let mut child_muxbox = create_test_muxbox("child");
1544        child_muxbox.tab_order = Some("2".to_string());
1545
1546        let mut parent_muxbox = create_test_muxbox("parent");
1547        parent_muxbox.tab_order = Some("1".to_string());
1548        parent_muxbox.children = Some(vec![child_muxbox]);
1549
1550        let mut layout = create_test_layout("test", Some(vec![parent_muxbox]));
1551
1552        let muxboxes_in_order = layout.get_muxboxes_in_tab_order();
1553        assert_eq!(muxboxes_in_order.len(), 2);
1554        assert_eq!(muxboxes_in_order[0].id, "parent");
1555        assert_eq!(muxboxes_in_order[1].id, "child");
1556    }
1557
1558    /// Tests that Layout::select_next_muxbox() advances selection correctly.
1559    /// This test demonstrates the next muxbox selection feature.
1560    #[test]
1561    fn test_layout_select_next_muxbox() {
1562        let mut muxbox1 = create_test_muxbox("muxbox1");
1563        let mut muxbox2 = create_test_muxbox("muxbox2");
1564        let mut muxbox3 = create_test_muxbox("muxbox3");
1565
1566        muxbox1.tab_order = Some("1".to_string());
1567        muxbox2.tab_order = Some("2".to_string());
1568        muxbox3.tab_order = Some("3".to_string());
1569
1570        muxbox1.selected = Some(true);
1571        muxbox2.selected = Some(false);
1572        muxbox3.selected = Some(false);
1573
1574        let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2, muxbox3]));
1575
1576        layout.select_next_muxbox();
1577
1578        let selected = layout.get_selected_muxboxes();
1579        assert_eq!(selected.len(), 1);
1580        assert_eq!(selected[0].id, "muxbox2");
1581    }
1582
1583    /// Tests that Layout::select_next_muxbox() wraps around to first muxbox.
1584    /// This test demonstrates the wrap-around behavior in next muxbox selection.
1585    #[test]
1586    fn test_layout_select_next_muxbox_wrap_around() {
1587        let mut muxbox1 = create_test_muxbox("muxbox1");
1588        let mut muxbox2 = create_test_muxbox("muxbox2");
1589
1590        muxbox1.tab_order = Some("1".to_string());
1591        muxbox2.tab_order = Some("2".to_string());
1592
1593        muxbox1.selected = Some(false);
1594        muxbox2.selected = Some(true); // Last muxbox selected
1595
1596        let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1597
1598        layout.select_next_muxbox();
1599
1600        let selected = layout.get_selected_muxboxes();
1601        assert_eq!(selected.len(), 1);
1602        assert_eq!(selected[0].id, "muxbox1"); // Wrapped to first
1603    }
1604
1605    /// Tests that Layout::select_next_muxbox() handles no selection.
1606    /// This test demonstrates next muxbox selection with no current selection.
1607    #[test]
1608    fn test_layout_select_next_muxbox_no_selection() {
1609        let mut muxbox1 = create_test_muxbox("muxbox1");
1610        let mut muxbox2 = create_test_muxbox("muxbox2");
1611
1612        muxbox1.tab_order = Some("1".to_string());
1613        muxbox2.tab_order = Some("2".to_string());
1614
1615        muxbox1.selected = Some(false);
1616        muxbox2.selected = Some(false);
1617
1618        let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1619
1620        layout.select_next_muxbox();
1621
1622        let selected = layout.get_selected_muxboxes();
1623        assert_eq!(selected.len(), 1);
1624        assert_eq!(selected[0].id, "muxbox1"); // First muxbox selected
1625    }
1626
1627    /// Tests that Layout::select_previous_muxbox() moves selection backwards.
1628    /// This test demonstrates the previous muxbox selection feature.
1629    #[test]
1630    fn test_layout_select_previous_muxbox() {
1631        let mut muxbox1 = create_test_muxbox("muxbox1");
1632        let mut muxbox2 = create_test_muxbox("muxbox2");
1633        let mut muxbox3 = create_test_muxbox("muxbox3");
1634
1635        muxbox1.tab_order = Some("1".to_string());
1636        muxbox2.tab_order = Some("2".to_string());
1637        muxbox3.tab_order = Some("3".to_string());
1638
1639        muxbox1.selected = Some(false);
1640        muxbox2.selected = Some(true);
1641        muxbox3.selected = Some(false);
1642
1643        let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2, muxbox3]));
1644
1645        layout.select_previous_muxbox();
1646
1647        let selected = layout.get_selected_muxboxes();
1648        assert_eq!(selected.len(), 1);
1649        assert_eq!(selected[0].id, "muxbox1");
1650    }
1651
1652    /// Tests that Layout::select_previous_muxbox() wraps around to last muxbox.
1653    /// This test demonstrates the wrap-around behavior in previous muxbox selection.
1654    #[test]
1655    fn test_layout_select_previous_muxbox_wrap_around() {
1656        let mut muxbox1 = create_test_muxbox("muxbox1");
1657        let mut muxbox2 = create_test_muxbox("muxbox2");
1658
1659        muxbox1.tab_order = Some("1".to_string());
1660        muxbox2.tab_order = Some("2".to_string());
1661
1662        muxbox1.selected = Some(true); // First muxbox selected
1663        muxbox2.selected = Some(false);
1664
1665        let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1666
1667        layout.select_previous_muxbox();
1668
1669        let selected = layout.get_selected_muxboxes();
1670        assert_eq!(selected.len(), 1);
1671        assert_eq!(selected[0].id, "muxbox2"); // Wrapped to last
1672    }
1673
1674    /// Tests that Layout::select_previous_muxbox() handles no selection.
1675    /// This test demonstrates previous muxbox selection with no current selection.
1676    #[test]
1677    fn test_layout_select_previous_muxbox_no_selection() {
1678        let mut muxbox1 = create_test_muxbox("muxbox1");
1679        let mut muxbox2 = create_test_muxbox("muxbox2");
1680
1681        muxbox1.tab_order = Some("1".to_string());
1682        muxbox2.tab_order = Some("2".to_string());
1683
1684        muxbox1.selected = Some(false);
1685        muxbox2.selected = Some(false);
1686
1687        let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1688
1689        layout.select_previous_muxbox();
1690
1691        let selected = layout.get_selected_muxboxes();
1692        assert_eq!(selected.len(), 1);
1693        assert_eq!(selected[0].id, "muxbox2"); // Last muxbox selected
1694    }
1695
1696    /// Tests that Layout navigation handles empty muxbox lists.
1697    /// This test demonstrates edge case handling in muxbox navigation.
1698    #[test]
1699    fn test_layout_navigation_empty_muxboxes() {
1700        let mut layout = create_test_layout("test", None);
1701
1702        // These should not panic
1703        layout.select_next_muxbox();
1704        layout.select_previous_muxbox();
1705
1706        let selected = layout.get_selected_muxboxes();
1707        assert_eq!(selected.len(), 0);
1708    }
1709
1710    /// Tests that Layout navigation handles muxboxes without tab order.
1711    /// This test demonstrates edge case handling with non-tabbable muxboxes.
1712    #[test]
1713    fn test_layout_navigation_no_tab_order() {
1714        let mut muxbox1 = create_test_muxbox("muxbox1");
1715        let mut muxbox2 = create_test_muxbox("muxbox2");
1716
1717        muxbox1.tab_order = None;
1718        muxbox2.tab_order = None;
1719
1720        let mut layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1721
1722        // These should not panic
1723        layout.select_next_muxbox();
1724        layout.select_previous_muxbox();
1725
1726        let selected = layout.get_selected_muxboxes();
1727        assert_eq!(selected.len(), 0);
1728    }
1729
1730    // === Layout All MuxBoxes Tests ===
1731
1732    /// Tests that Layout::get_all_muxboxes() returns all muxboxes.
1733    /// This test demonstrates the all muxboxes retrieval feature.
1734    #[test]
1735    fn test_layout_get_all_muxboxes() {
1736        let muxbox1 = create_test_muxbox("muxbox1");
1737        let muxbox2 = create_test_muxbox("muxbox2");
1738        let layout = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1739
1740        let all_muxboxes = layout.get_all_muxboxes();
1741        assert_eq!(all_muxboxes.len(), 2);
1742        assert_eq!(all_muxboxes[0].id, "muxbox1");
1743        assert_eq!(all_muxboxes[1].id, "muxbox2");
1744    }
1745
1746    /// Tests that Layout::get_all_muxboxes() includes nested muxboxes.
1747    /// This test demonstrates recursive muxbox retrieval.
1748    #[test]
1749    fn test_layout_get_all_muxboxes_nested() {
1750        let child_muxbox = create_test_muxbox("child");
1751        let parent_muxbox = MuxBox {
1752            id: "parent".to_string(),
1753            children: Some(vec![child_muxbox]),
1754            ..Default::default()
1755        };
1756        let layout = create_test_layout("test", Some(vec![parent_muxbox]));
1757
1758        let all_muxboxes = layout.get_all_muxboxes();
1759        assert_eq!(all_muxboxes.len(), 2);
1760        assert_eq!(all_muxboxes[0].id, "parent");
1761        assert_eq!(all_muxboxes[1].id, "child");
1762    }
1763
1764    /// Tests that Layout::get_all_muxboxes() handles empty layout.
1765    /// This test demonstrates edge case handling in all muxboxes retrieval.
1766    #[test]
1767    fn test_layout_get_all_muxboxes_empty() {
1768        let layout = create_test_layout("test", None);
1769
1770        let all_muxboxes = layout.get_all_muxboxes();
1771        assert_eq!(all_muxboxes.len(), 0);
1772    }
1773
1774    // === Layout Clone Tests ===
1775
1776    /// Tests that Layout implements Clone correctly.
1777    /// This test demonstrates Layout cloning behavior.
1778    #[test]
1779    fn test_layout_clone() {
1780        let muxbox1 = create_test_muxbox("muxbox1");
1781        let muxbox2 = create_test_muxbox("muxbox2");
1782        let layout1 = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1783        let layout2 = layout1.clone();
1784
1785        assert_eq!(layout1.id, layout2.id);
1786        assert_eq!(layout1.title, layout2.title);
1787        assert_eq!(
1788            layout1.children.as_ref().unwrap().len(),
1789            layout2.children.as_ref().unwrap().len()
1790        );
1791        assert_eq!(layout1.root, layout2.root);
1792        assert_eq!(layout1.active, layout2.active);
1793    }
1794
1795    /// Tests that Layout cloning includes nested muxboxes.
1796    /// This test demonstrates Layout cloning with nested structure.
1797    #[test]
1798    fn test_layout_clone_nested() {
1799        let child_muxbox = create_test_muxbox("child");
1800        let parent_muxbox = MuxBox {
1801            id: "parent".to_string(),
1802            children: Some(vec![child_muxbox]),
1803            ..Default::default()
1804        };
1805        let layout1 = create_test_layout("test", Some(vec![parent_muxbox]));
1806        let layout2 = layout1.clone();
1807
1808        assert_eq!(
1809            layout1.children.as_ref().unwrap()[0]
1810                .children
1811                .as_ref()
1812                .unwrap()
1813                .len(),
1814            layout2.children.as_ref().unwrap()[0]
1815                .children
1816                .as_ref()
1817                .unwrap()
1818                .len()
1819        );
1820        assert_eq!(
1821            layout1.children.as_ref().unwrap()[0]
1822                .children
1823                .as_ref()
1824                .unwrap()[0]
1825                .id,
1826            layout2.children.as_ref().unwrap()[0]
1827                .children
1828                .as_ref()
1829                .unwrap()[0]
1830                .id
1831        );
1832    }
1833
1834    // === Layout Hash Tests ===
1835
1836    /// Tests that Layout implements Hash correctly.
1837    /// This test demonstrates Layout hashing behavior.
1838    #[test]
1839    fn test_layout_hash() {
1840        let muxbox1 = create_test_muxbox("muxbox1");
1841        let muxbox2 = create_test_muxbox("muxbox2");
1842        let layout1 = create_test_layout("test", Some(vec![muxbox1.clone(), muxbox2.clone()]));
1843        let layout2 = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1844        let layout3 = create_test_layout("other", Some(vec![]));
1845
1846        use std::collections::hash_map::DefaultHasher;
1847        use std::hash::{Hash, Hasher};
1848
1849        let mut hasher1 = DefaultHasher::new();
1850        let mut hasher2 = DefaultHasher::new();
1851        let mut hasher3 = DefaultHasher::new();
1852
1853        layout1.hash(&mut hasher1);
1854        layout2.hash(&mut hasher2);
1855        layout3.hash(&mut hasher3);
1856
1857        assert_eq!(hasher1.finish(), hasher2.finish());
1858        assert_ne!(hasher1.finish(), hasher3.finish());
1859    }
1860
1861    // === Layout PartialEq Tests ===
1862
1863    /// Tests that Layout implements PartialEq correctly.
1864    /// This test demonstrates Layout equality comparison.
1865    #[test]
1866    fn test_layout_equality() {
1867        let muxbox1 = create_test_muxbox("muxbox1");
1868        let muxbox2 = create_test_muxbox("muxbox2");
1869        let layout1 = create_test_layout("test", Some(vec![muxbox1.clone(), muxbox2.clone()]));
1870        let layout2 = create_test_layout("test", Some(vec![muxbox1, muxbox2]));
1871        let layout3 = create_test_layout("other", Some(vec![]));
1872
1873        assert_eq!(layout1, layout2);
1874        assert_ne!(layout1, layout3);
1875    }
1876
1877    /// Tests that Layout equality considers all fields.
1878    /// This test demonstrates comprehensive Layout equality checking.
1879    #[test]
1880    fn test_layout_equality_comprehensive() {
1881        let muxbox = create_test_muxbox("muxbox");
1882
1883        let layout1 = Layout {
1884            id: "test".to_string(),
1885            title: Some("Test".to_string()),
1886            children: Some(vec![muxbox.clone()]),
1887            root: Some(true),
1888            active: Some(false),
1889            ..Default::default()
1890        };
1891
1892        let layout2 = Layout {
1893            id: "test".to_string(),
1894            title: Some("Test".to_string()),
1895            children: Some(vec![muxbox.clone()]),
1896            root: Some(true),
1897            active: Some(false),
1898            ..Default::default()
1899        };
1900
1901        let layout3 = Layout {
1902            id: "test".to_string(),
1903            title: Some("Test".to_string()),
1904            children: Some(vec![muxbox]),
1905            root: Some(false), // Different root value
1906            active: Some(false),
1907            ..Default::default()
1908        };
1909
1910        assert_eq!(layout1, layout2);
1911        assert_ne!(layout1, layout3);
1912    }
1913
1914    // === Layout Edge Cases ===
1915
1916    /// Tests that Layout handles operations on empty children gracefully.
1917    /// This test demonstrates edge case handling with empty children.
1918    #[test]
1919    fn test_layout_empty_children_operations() {
1920        let mut layout = create_test_layout("test", Some(vec![]));
1921
1922        // These should not panic
1923        let muxboxes = layout.get_all_muxboxes();
1924        assert_eq!(muxboxes.len(), 0);
1925
1926        let selected = layout.get_selected_muxboxes();
1927        assert_eq!(selected.len(), 0);
1928
1929        let tab_ordered = layout.get_muxboxes_in_tab_order();
1930        assert_eq!(tab_ordered.len(), 0);
1931
1932        layout.select_next_muxbox();
1933        layout.select_previous_muxbox();
1934        layout.select_only_muxbox("nonexistent");
1935        layout.deselect_all_muxboxes();
1936    }
1937
1938    /// Tests that Layout handles None children gracefully.
1939    /// This test demonstrates edge case handling with None children.
1940    #[test]
1941    fn test_layout_none_children_operations() {
1942        let mut layout = create_test_layout("test", None);
1943
1944        // These should not panic
1945        let muxboxes = layout.get_all_muxboxes();
1946        assert_eq!(muxboxes.len(), 0);
1947
1948        let selected = layout.get_selected_muxboxes();
1949        assert_eq!(selected.len(), 0);
1950
1951        let tab_ordered = layout.get_muxboxes_in_tab_order();
1952        assert_eq!(tab_ordered.len(), 0);
1953
1954        layout.select_next_muxbox();
1955        layout.select_previous_muxbox();
1956        layout.select_only_muxbox("nonexistent");
1957        layout.deselect_all_muxboxes();
1958    }
1959}