Skip to main content

revue/devtools/
mod.rs

1//! Developer tools for Revue applications
2//!
3//! Provides debugging and inspection tools for development:
4//!
5//! | Tool | Description |
6//! |------|-------------|
7//! | [`Inspector`] | Widget tree inspector |
8//! | [`StateDebugger`] | Reactive state viewer |
9//! | [`StyleInspector`] | CSS style inspector |
10//! | [`EventLogger`] | Event stream logger |
11//!
12//! # Quick Start
13//!
14//! ```rust,ignore
15//! use revue::devtools::{DevTools, Inspector};
16//!
17//! // Enable dev tools with F12
18//! let app = App::builder()
19//!     .with_devtools(true)
20//!     .build();
21//! ```
22//!
23//! # Widget Inspector
24//!
25//! ```rust,ignore
26//! use revue::devtools::Inspector;
27//!
28//! let inspector = Inspector::new()
29//!     .show_bounds(true)
30//!     .show_classes(true);
31//! ```
32
33mod events;
34mod helpers;
35mod inspector;
36mod profiler;
37mod state;
38mod style;
39mod time_travel;
40
41pub use events::{EventFilter, EventLogger, EventType, LoggedEvent};
42pub use inspector::{ComponentPicker, Inspector, InspectorConfig, PickerMode, WidgetNode};
43pub use profiler::{ComponentStats, Frame, Profiler, ProfilerView, RenderEvent, RenderReason};
44pub use state::{StateDebugger, StateEntry, StateValue};
45pub use style::{ComputedProperty, PropertySource, StyleCategory, StyleInspector};
46pub use time_travel::{
47    Action, SnapshotValue, StateDiff, StateSnapshot, TimeTravelConfig, TimeTravelDebugger,
48    TimeTravelView,
49};
50
51use crate::layout::Rect;
52use crate::render::Buffer;
53use crate::style::Color;
54use std::sync::atomic::{AtomicBool, Ordering};
55
56// =============================================================================
57// DevTools Panel
58// =============================================================================
59
60/// DevTools panel position
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
62pub enum DevToolsPosition {
63    /// Right side panel
64    #[default]
65    Right,
66    /// Bottom panel
67    Bottom,
68    /// Left side panel
69    Left,
70    /// Floating overlay
71    Overlay,
72}
73
74/// DevTools configuration
75#[derive(Debug, Clone)]
76pub struct DevToolsConfig {
77    /// Panel position
78    pub position: DevToolsPosition,
79    /// Panel size (width or height depending on position)
80    pub size: u16,
81    /// Is visible
82    pub visible: bool,
83    /// Active tab
84    pub active_tab: DevToolsTab,
85    /// Background color
86    pub bg_color: Color,
87    /// Text color
88    pub fg_color: Color,
89    /// Accent color
90    pub accent_color: Color,
91}
92
93impl Default for DevToolsConfig {
94    fn default() -> Self {
95        Self {
96            position: DevToolsPosition::Right,
97            size: 50,
98            visible: false,
99            active_tab: DevToolsTab::Inspector,
100            bg_color: Color::rgb(25, 25, 35),
101            fg_color: Color::rgb(200, 200, 210),
102            accent_color: Color::rgb(130, 180, 255),
103        }
104    }
105}
106
107/// DevTools tab
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
109pub enum DevToolsTab {
110    /// Widget inspector
111    #[default]
112    Inspector,
113    /// State debugger
114    State,
115    /// Style inspector
116    Styles,
117    /// Event logger
118    Events,
119    /// Performance profiler
120    Profiler,
121    /// Time-travel debugger
122    TimeTravel,
123}
124
125impl DevToolsTab {
126    /// Get tab label
127    pub fn label(&self) -> &'static str {
128        match self {
129            Self::Inspector => "Inspector",
130            Self::State => "State",
131            Self::Styles => "Styles",
132            Self::Events => "Events",
133            Self::Profiler => "Profiler",
134            Self::TimeTravel => "Travel",
135        }
136    }
137
138    /// Get all tabs
139    pub fn all() -> &'static [DevToolsTab] {
140        &[
141            DevToolsTab::Inspector,
142            DevToolsTab::State,
143            DevToolsTab::Styles,
144            DevToolsTab::Events,
145            DevToolsTab::Profiler,
146            DevToolsTab::TimeTravel,
147        ]
148    }
149
150    /// Next tab
151    pub fn next(&self) -> Self {
152        match self {
153            Self::Inspector => Self::State,
154            Self::State => Self::Styles,
155            Self::Styles => Self::Events,
156            Self::Events => Self::Profiler,
157            Self::Profiler => Self::TimeTravel,
158            Self::TimeTravel => Self::Inspector,
159        }
160    }
161
162    /// Previous tab
163    pub fn prev(&self) -> Self {
164        match self {
165            Self::Inspector => Self::TimeTravel,
166            Self::State => Self::Inspector,
167            Self::Styles => Self::State,
168            Self::Events => Self::Styles,
169            Self::Profiler => Self::Events,
170            Self::TimeTravel => Self::Profiler,
171        }
172    }
173}
174
175// =============================================================================
176// DevTools
177// =============================================================================
178
179/// Main DevTools panel
180pub struct DevTools {
181    /// Configuration
182    config: DevToolsConfig,
183    /// Widget inspector
184    inspector: Inspector,
185    /// State debugger
186    state: StateDebugger,
187    /// Style inspector
188    styles: StyleInspector,
189    /// Event logger
190    events: EventLogger,
191    /// Performance profiler
192    profiler: Profiler,
193    /// Time-travel debugger
194    time_travel: TimeTravelDebugger,
195}
196
197impl DevTools {
198    /// Create new DevTools
199    pub fn new() -> Self {
200        Self {
201            config: DevToolsConfig::default(),
202            inspector: Inspector::new(),
203            state: StateDebugger::new(),
204            styles: StyleInspector::new(),
205            events: EventLogger::new(),
206            profiler: Profiler::new(),
207            time_travel: TimeTravelDebugger::new(),
208        }
209    }
210
211    /// Set configuration
212    pub fn config(mut self, config: DevToolsConfig) -> Self {
213        self.config = config;
214        self
215    }
216
217    /// Set position
218    pub fn position(mut self, position: DevToolsPosition) -> Self {
219        self.config.position = position;
220        self
221    }
222
223    /// Set size
224    pub fn size(mut self, size: u16) -> Self {
225        self.config.size = size;
226        self
227    }
228
229    /// Toggle visibility
230    pub fn toggle(&mut self) {
231        self.config.visible = !self.config.visible;
232    }
233
234    /// Set visibility
235    pub fn set_visible(&mut self, visible: bool) {
236        self.config.visible = visible;
237    }
238
239    /// Is visible
240    pub fn is_visible(&self) -> bool {
241        self.config.visible
242    }
243
244    /// Set active tab
245    pub fn set_tab(&mut self, tab: DevToolsTab) {
246        self.config.active_tab = tab;
247    }
248
249    /// Next tab
250    pub fn next_tab(&mut self) {
251        self.config.active_tab = self.config.active_tab.next();
252    }
253
254    /// Previous tab
255    pub fn prev_tab(&mut self) {
256        self.config.active_tab = self.config.active_tab.prev();
257    }
258
259    /// Get inspector
260    pub fn inspector(&self) -> &Inspector {
261        &self.inspector
262    }
263
264    /// Get mutable inspector
265    pub fn inspector_mut(&mut self) -> &mut Inspector {
266        &mut self.inspector
267    }
268
269    /// Get state debugger
270    pub fn state(&self) -> &StateDebugger {
271        &self.state
272    }
273
274    /// Get mutable state debugger
275    pub fn state_mut(&mut self) -> &mut StateDebugger {
276        &mut self.state
277    }
278
279    /// Get style inspector
280    pub fn styles(&self) -> &StyleInspector {
281        &self.styles
282    }
283
284    /// Get mutable style inspector
285    pub fn styles_mut(&mut self) -> &mut StyleInspector {
286        &mut self.styles
287    }
288
289    /// Get event logger
290    pub fn events(&self) -> &EventLogger {
291        &self.events
292    }
293
294    /// Get mutable event logger
295    pub fn events_mut(&mut self) -> &mut EventLogger {
296        &mut self.events
297    }
298
299    /// Get profiler
300    pub fn profiler(&self) -> &Profiler {
301        &self.profiler
302    }
303
304    /// Get mutable profiler
305    pub fn profiler_mut(&mut self) -> &mut Profiler {
306        &mut self.profiler
307    }
308
309    /// Get time-travel debugger
310    pub fn time_travel(&self) -> &TimeTravelDebugger {
311        &self.time_travel
312    }
313
314    /// Get mutable time-travel debugger
315    pub fn time_travel_mut(&mut self) -> &mut TimeTravelDebugger {
316        &mut self.time_travel
317    }
318
319    /// Calculate panel rect based on position
320    pub fn panel_rect(&self, area: Rect) -> Option<Rect> {
321        if !self.config.visible {
322            return None;
323        }
324
325        let size = self.config.size;
326
327        Some(match self.config.position {
328            DevToolsPosition::Right => Rect::new(
329                area.x + area.width.saturating_sub(size),
330                area.y,
331                size.min(area.width),
332                area.height,
333            ),
334            DevToolsPosition::Left => Rect::new(area.x, area.y, size.min(area.width), area.height),
335            DevToolsPosition::Bottom => Rect::new(
336                area.x,
337                area.y + area.height.saturating_sub(size),
338                area.width,
339                size.min(area.height),
340            ),
341            DevToolsPosition::Overlay => {
342                let width = (area.width * 2 / 3).min(80);
343                let height = (area.height * 2 / 3).min(30);
344                Rect::new(
345                    area.x + (area.width - width) / 2,
346                    area.y + (area.height - height) / 2,
347                    width,
348                    height,
349                )
350            }
351        })
352    }
353
354    /// Calculate content area (excluding devtools)
355    pub fn content_rect(&self, area: Rect) -> Rect {
356        if !self.config.visible {
357            return area;
358        }
359
360        let size = self.config.size;
361
362        match self.config.position {
363            DevToolsPosition::Right => {
364                Rect::new(area.x, area.y, area.width.saturating_sub(size), area.height)
365            }
366            DevToolsPosition::Left => Rect::new(
367                area.x + size.min(area.width),
368                area.y,
369                area.width.saturating_sub(size),
370                area.height,
371            ),
372            DevToolsPosition::Bottom => {
373                Rect::new(area.x, area.y, area.width, area.height.saturating_sub(size))
374            }
375            DevToolsPosition::Overlay => area,
376        }
377    }
378
379    /// Render devtools panel
380    pub fn render(&self, buffer: &mut Buffer, area: Rect) {
381        if let Some(panel) = self.panel_rect(area) {
382            self.render_panel(buffer, panel);
383        }
384    }
385
386    fn render_panel(&self, buffer: &mut Buffer, area: Rect) {
387        // Fill background
388        for y in area.y..area.y + area.height {
389            for x in area.x..area.x + area.width {
390                if let Some(cell) = buffer.get_mut(x, y) {
391                    cell.symbol = ' ';
392                    cell.bg = Some(self.config.bg_color);
393                    cell.fg = Some(self.config.fg_color);
394                }
395            }
396        }
397
398        // Draw border
399        self.draw_border(buffer, area);
400
401        // Tab bar
402        let tab_area = Rect::new(area.x + 1, area.y + 1, area.width - 2, 1);
403        self.render_tabs(buffer, tab_area);
404
405        // Content area
406        let content_area = Rect::new(
407            area.x + 1,
408            area.y + 3,
409            area.width - 2,
410            area.height.saturating_sub(4),
411        );
412
413        match self.config.active_tab {
414            DevToolsTab::Inspector => {
415                self.inspector
416                    .render_content(buffer, content_area, &self.config)
417            }
418            DevToolsTab::State => self
419                .state
420                .render_content(buffer, content_area, &self.config),
421            DevToolsTab::Styles => self
422                .styles
423                .render_content(buffer, content_area, &self.config),
424            DevToolsTab::Events => self
425                .events
426                .render_content(buffer, content_area, &self.config),
427            DevToolsTab::Profiler => {
428                self.profiler
429                    .render_content(buffer, content_area, &self.config)
430            }
431            DevToolsTab::TimeTravel => {
432                self.time_travel
433                    .render_content(buffer, content_area, &self.config)
434            }
435        }
436    }
437
438    fn render_tabs(&self, buffer: &mut Buffer, area: Rect) {
439        let mut x = area.x;
440
441        for tab in DevToolsTab::all() {
442            let label = format!(" {} ", tab.label());
443            let is_active = *tab == self.config.active_tab;
444
445            let (fg, bg) = if is_active {
446                (self.config.bg_color, self.config.accent_color)
447            } else {
448                (self.config.fg_color, self.config.bg_color)
449            };
450
451            for ch in label.chars() {
452                if x < area.x + area.width {
453                    if let Some(cell) = buffer.get_mut(x, area.y) {
454                        cell.symbol = ch;
455                        cell.fg = Some(fg);
456                        cell.bg = Some(bg);
457                    }
458                    x += 1;
459                }
460            }
461
462            x += 1; // Gap between tabs
463        }
464    }
465
466    fn draw_border(&self, buffer: &mut Buffer, area: Rect) {
467        let color = self.config.accent_color;
468
469        // Corners and edges
470        for x in area.x..area.x + area.width {
471            if let Some(cell) = buffer.get_mut(x, area.y) {
472                cell.symbol = if x == area.x {
473                    '┌'
474                } else if x == area.x + area.width - 1 {
475                    '┐'
476                } else {
477                    '─'
478                };
479                cell.fg = Some(color);
480            }
481            if let Some(cell) = buffer.get_mut(x, area.y + area.height - 1) {
482                cell.symbol = if x == area.x {
483                    '└'
484                } else if x == area.x + area.width - 1 {
485                    '┘'
486                } else {
487                    '─'
488                };
489                cell.fg = Some(color);
490            }
491        }
492
493        for y in area.y + 1..area.y + area.height - 1 {
494            if let Some(cell) = buffer.get_mut(area.x, y) {
495                cell.symbol = '│';
496                cell.fg = Some(color);
497            }
498            if let Some(cell) = buffer.get_mut(area.x + area.width - 1, y) {
499                cell.symbol = '│';
500                cell.fg = Some(color);
501            }
502        }
503
504        // Separator after tabs
505        for x in area.x..area.x + area.width {
506            if let Some(cell) = buffer.get_mut(x, area.y + 2) {
507                cell.symbol = if x == area.x {
508                    '├'
509                } else if x == area.x + area.width - 1 {
510                    '┤'
511                } else {
512                    '─'
513                };
514                cell.fg = Some(color);
515            }
516        }
517    }
518}
519
520impl Default for DevTools {
521    fn default() -> Self {
522        Self::new()
523    }
524}
525
526// =============================================================================
527// Global DevTools State (Deprecated)
528// =============================================================================
529//
530// These global functions are deprecated. Use `App::is_devtools_enabled()`,
531// `App::enable_devtools()`, `App::disable_devtools()`, and `App::toggle_devtools()`
532// instead for proper test isolation and cleaner architecture.
533
534static DEVTOOLS_ENABLED: AtomicBool = AtomicBool::new(false);
535
536/// Enable global devtools
537///
538/// # Deprecated
539/// Use `App::enable_devtools()` instead for proper test isolation.
540#[deprecated(since = "2.1.0", note = "Use App::enable_devtools() instead")]
541pub fn enable_devtools() {
542    DEVTOOLS_ENABLED.store(true, Ordering::Relaxed);
543}
544
545/// Disable global devtools
546///
547/// # Deprecated
548/// Use `App::disable_devtools()` instead for proper test isolation.
549#[deprecated(since = "2.1.0", note = "Use App::disable_devtools() instead")]
550pub fn disable_devtools() {
551    DEVTOOLS_ENABLED.store(false, Ordering::Relaxed);
552}
553
554/// Check if devtools are enabled
555///
556/// # Deprecated
557/// Use `App::is_devtools_enabled()` instead for proper test isolation.
558#[deprecated(since = "2.1.0", note = "Use App::is_devtools_enabled() instead")]
559pub fn is_devtools_enabled() -> bool {
560    DEVTOOLS_ENABLED.load(Ordering::Relaxed)
561}
562
563/// Toggle devtools
564///
565/// # Deprecated
566/// Use `App::toggle_devtools()` instead for proper test isolation.
567#[deprecated(since = "2.1.0", note = "Use App::toggle_devtools() instead")]
568pub fn toggle_devtools() -> bool {
569    let was = DEVTOOLS_ENABLED.fetch_xor(true, Ordering::Relaxed);
570    !was
571}
572
573// =============================================================================
574// Tests
575// =============================================================================
576
577#[cfg(test)]
578mod tests {
579    use super::*;
580    use crate::core::app::App;
581
582    #[test]
583    fn test_devtools_config_default() {
584        let config = DevToolsConfig::default();
585        assert!(!config.visible);
586        assert_eq!(config.position, DevToolsPosition::Right);
587        assert_eq!(config.active_tab, DevToolsTab::Inspector);
588        assert_eq!(config.size, 50);
589    }
590
591    #[test]
592    fn test_devtools_tab_cycle() {
593        let tab = DevToolsTab::Inspector;
594        assert_eq!(tab.next(), DevToolsTab::State);
595        assert_eq!(tab.prev(), DevToolsTab::TimeTravel);
596    }
597
598    #[test]
599    fn test_devtools_toggle() {
600        let mut devtools = DevTools::new();
601        assert!(!devtools.is_visible());
602
603        devtools.toggle();
604        assert!(devtools.is_visible());
605
606        devtools.toggle();
607        assert!(!devtools.is_visible());
608    }
609
610    #[test]
611    fn test_panel_rect_right() {
612        let devtools = DevTools::new().size(30);
613        let mut dt = devtools;
614        dt.set_visible(true);
615
616        let area = Rect::new(0, 0, 100, 50);
617        let panel = dt.panel_rect(area).unwrap();
618
619        assert_eq!(panel.x, 70);
620        assert_eq!(panel.width, 30);
621        assert_eq!(panel.height, 50);
622    }
623
624    #[test]
625    fn test_panel_rect_left() {
626        let mut devtools = DevTools::new().size(25);
627        devtools.set_visible(true);
628        devtools.config.position = DevToolsPosition::Left;
629
630        let area = Rect::new(10, 5, 100, 50);
631        let panel = devtools.panel_rect(area).unwrap();
632
633        assert_eq!(panel.x, 10);
634        assert_eq!(panel.y, 5);
635        assert_eq!(panel.width, 25);
636        assert_eq!(panel.height, 50);
637    }
638
639    #[test]
640    fn test_panel_rect_bottom() {
641        let mut devtools = DevTools::new().size(15);
642        devtools.set_visible(true);
643        devtools.config.position = DevToolsPosition::Bottom;
644
645        let area = Rect::new(0, 0, 100, 50);
646        let panel = devtools.panel_rect(area).unwrap();
647
648        assert_eq!(panel.x, 0);
649        assert_eq!(panel.y, 35);
650        assert_eq!(panel.width, 100);
651        assert_eq!(panel.height, 15);
652    }
653
654    #[test]
655    fn test_panel_rect_overlay() {
656        let mut devtools = DevTools::new();
657        devtools.set_visible(true);
658        devtools.config.position = DevToolsPosition::Overlay;
659
660        let area = Rect::new(0, 0, 100, 50);
661        let panel = devtools.panel_rect(area).unwrap();
662
663        // Overlay should be centered: 2/3 of width/height, capped at 80/30
664        assert_eq!(panel.width, 66); // 100 * 2 / 3 = 66
665        assert_eq!(panel.height, 30); // 50 * 2 / 3 = 33, capped at 30
666    }
667
668    #[test]
669    fn test_panel_rect_invisible() {
670        let mut devtools = DevTools::new();
671        devtools.set_visible(false);
672
673        let area = Rect::new(0, 0, 100, 50);
674        assert!(devtools.panel_rect(area).is_none());
675    }
676
677    #[test]
678    fn test_content_rect_right() {
679        let mut devtools = DevTools::new().size(30);
680        devtools.set_visible(true);
681
682        let area = Rect::new(0, 0, 100, 50);
683        let content = devtools.content_rect(area);
684
685        assert_eq!(content.x, 0);
686        assert_eq!(content.y, 0);
687        assert_eq!(content.width, 70);
688        assert_eq!(content.height, 50);
689    }
690
691    #[test]
692    fn test_content_rect_left() {
693        let mut devtools = DevTools::new().size(30);
694        devtools.set_visible(true);
695        devtools.config.position = DevToolsPosition::Left;
696
697        let area = Rect::new(0, 0, 100, 50);
698        let content = devtools.content_rect(area);
699
700        assert_eq!(content.x, 30);
701        assert_eq!(content.width, 70);
702    }
703
704    #[test]
705    fn test_content_rect_bottom() {
706        let mut devtools = DevTools::new().size(20);
707        devtools.set_visible(true);
708        devtools.config.position = DevToolsPosition::Bottom;
709
710        let area = Rect::new(0, 0, 100, 50);
711        let content = devtools.content_rect(area);
712
713        assert_eq!(content.y, 0);
714        assert_eq!(content.height, 30);
715    }
716
717    #[test]
718    fn test_content_rect_overlay() {
719        let mut devtools = DevTools::new();
720        devtools.set_visible(true);
721        devtools.config.position = DevToolsPosition::Overlay;
722
723        let area = Rect::new(0, 0, 100, 50);
724        let content = devtools.content_rect(area);
725
726        // Overlay doesn't reduce content area
727        assert_eq!(content, area);
728    }
729
730    #[test]
731    fn test_content_rect_invisible() {
732        let mut devtools = DevTools::new();
733        devtools.set_visible(false);
734
735        let area = Rect::new(0, 0, 100, 50);
736        let content = devtools.content_rect(area);
737
738        assert_eq!(content, area);
739    }
740
741    #[test]
742    fn test_devtools_new() {
743        let devtools = DevTools::new();
744        assert!(!devtools.is_visible());
745        assert_eq!(devtools.config.active_tab, DevToolsTab::Inspector);
746    }
747
748    #[test]
749    fn test_devtools_default() {
750        let devtools = DevTools::default();
751        assert!(!devtools.is_visible());
752        assert_eq!(devtools.config.active_tab, DevToolsTab::Inspector);
753    }
754
755    #[test]
756    fn test_devtools_config_builder() {
757        let config = DevToolsConfig {
758            position: DevToolsPosition::Left,
759            size: 40,
760            visible: true,
761            active_tab: DevToolsTab::Profiler,
762            bg_color: Color::rgb(10, 10, 10),
763            fg_color: Color::rgb(255, 255, 255),
764            accent_color: Color::rgb(100, 100, 255),
765        };
766
767        let devtools = DevTools::new().config(config);
768        assert!(devtools.is_visible());
769        assert_eq!(devtools.config.position, DevToolsPosition::Left);
770        assert_eq!(devtools.config.size, 40);
771        assert_eq!(devtools.config.active_tab, DevToolsTab::Profiler);
772    }
773
774    #[test]
775    fn test_devtools_position() {
776        let devtools = DevTools::new().position(DevToolsPosition::Bottom);
777        assert_eq!(devtools.config.position, DevToolsPosition::Bottom);
778    }
779
780    #[test]
781    fn test_devtools_size() {
782        let devtools = DevTools::new().size(60);
783        assert_eq!(devtools.config.size, 60);
784    }
785
786    #[test]
787    fn test_devtools_set_visible() {
788        let mut devtools = DevTools::new();
789        assert!(!devtools.is_visible());
790
791        devtools.set_visible(true);
792        assert!(devtools.is_visible());
793
794        devtools.set_visible(false);
795        assert!(!devtools.is_visible());
796    }
797
798    #[test]
799    fn test_devtools_set_tab() {
800        let mut devtools = DevTools::new();
801        assert_eq!(devtools.config.active_tab, DevToolsTab::Inspector);
802
803        devtools.set_tab(DevToolsTab::Profiler);
804        assert_eq!(devtools.config.active_tab, DevToolsTab::Profiler);
805    }
806
807    #[test]
808    fn test_devtools_next_tab() {
809        let mut devtools = DevTools::new();
810        assert_eq!(devtools.config.active_tab, DevToolsTab::Inspector);
811
812        devtools.next_tab();
813        assert_eq!(devtools.config.active_tab, DevToolsTab::State);
814
815        devtools.next_tab();
816        assert_eq!(devtools.config.active_tab, DevToolsTab::Styles);
817    }
818
819    #[test]
820    fn test_devtools_prev_tab() {
821        let mut devtools = DevTools::new();
822        assert_eq!(devtools.config.active_tab, DevToolsTab::Inspector);
823
824        devtools.prev_tab();
825        assert_eq!(devtools.config.active_tab, DevToolsTab::TimeTravel);
826
827        devtools.prev_tab();
828        assert_eq!(devtools.config.active_tab, DevToolsTab::Profiler);
829    }
830
831    #[test]
832    fn test_devtools_getters() {
833        let devtools = DevTools::new();
834
835        // Test inspector getter - just check it returns a reference
836        let _inspector = devtools.inspector();
837        let _state = devtools.state();
838        let _styles = devtools.styles();
839        let _events = devtools.events();
840        let _profiler = devtools.profiler();
841        let _time_travel = devtools.time_travel();
842    }
843
844    #[test]
845    fn test_devtools_getters_mut() {
846        let mut devtools = DevTools::new();
847
848        // Test mutable getters
849        devtools.inspector_mut();
850        devtools.state_mut();
851        devtools.styles_mut();
852        devtools.events_mut();
853        devtools.profiler_mut();
854        devtools.time_travel_mut();
855    }
856
857    #[test]
858    fn test_devtools_tab_label() {
859        assert_eq!(DevToolsTab::Inspector.label(), "Inspector");
860        assert_eq!(DevToolsTab::State.label(), "State");
861        assert_eq!(DevToolsTab::Styles.label(), "Styles");
862        assert_eq!(DevToolsTab::Events.label(), "Events");
863        assert_eq!(DevToolsTab::Profiler.label(), "Profiler");
864        assert_eq!(DevToolsTab::TimeTravel.label(), "Travel");
865    }
866
867    #[test]
868    fn test_devtools_tab_all() {
869        let all = DevToolsTab::all();
870        assert_eq!(all.len(), 6);
871        assert_eq!(all[0], DevToolsTab::Inspector);
872        assert_eq!(all[5], DevToolsTab::TimeTravel);
873    }
874
875    #[test]
876    fn test_devtools_tab_next_cycle() {
877        assert_eq!(DevToolsTab::Inspector.next(), DevToolsTab::State);
878        assert_eq!(DevToolsTab::State.next(), DevToolsTab::Styles);
879        assert_eq!(DevToolsTab::Styles.next(), DevToolsTab::Events);
880        assert_eq!(DevToolsTab::Events.next(), DevToolsTab::Profiler);
881        assert_eq!(DevToolsTab::Profiler.next(), DevToolsTab::TimeTravel);
882        assert_eq!(DevToolsTab::TimeTravel.next(), DevToolsTab::Inspector);
883    }
884
885    #[test]
886    fn test_devtools_tab_prev_cycle() {
887        assert_eq!(DevToolsTab::Inspector.prev(), DevToolsTab::TimeTravel);
888        assert_eq!(DevToolsTab::TimeTravel.prev(), DevToolsTab::Profiler);
889        assert_eq!(DevToolsTab::Profiler.prev(), DevToolsTab::Events);
890        assert_eq!(DevToolsTab::Events.prev(), DevToolsTab::Styles);
891        assert_eq!(DevToolsTab::Styles.prev(), DevToolsTab::State);
892        assert_eq!(DevToolsTab::State.prev(), DevToolsTab::Inspector);
893    }
894
895    #[test]
896    fn test_devtools_position_default() {
897        assert_eq!(DevToolsPosition::default(), DevToolsPosition::Right);
898    }
899
900    #[test]
901    fn test_devtools_tab_default() {
902        assert_eq!(DevToolsTab::default(), DevToolsTab::Inspector);
903    }
904
905    #[test]
906    fn test_panel_rect_saturation() {
907        let mut devtools = DevTools::new().size(200);
908        devtools.set_visible(true);
909
910        let area = Rect::new(0, 0, 100, 50);
911        let panel = devtools.panel_rect(area).unwrap();
912
913        // Size should be capped at area size
914        assert_eq!(panel.width, 100);
915        assert_eq!(panel.height, 50);
916    }
917
918    #[test]
919    fn test_content_rect_saturation() {
920        let mut devtools = DevTools::new().size(200);
921        devtools.set_visible(true);
922
923        let area = Rect::new(0, 0, 100, 50);
924        let content = devtools.content_rect(area);
925
926        // Content should handle large size gracefully
927        assert_eq!(content.width, 0); // 100 - 100 = 0
928        assert_eq!(content.height, 50);
929    }
930
931    #[test]
932    fn test_global_enable_disable_devtools() {
933        // Use App methods instead of deprecated global functions
934        // Start with devtools explicitly disabled
935        let mut app = App::builder().devtools(false).build();
936        assert!(!app.is_devtools_enabled());
937
938        app.enable_devtools();
939        assert!(app.is_devtools_enabled());
940
941        app.disable_devtools();
942        assert!(!app.is_devtools_enabled());
943    }
944
945    #[test]
946    fn test_global_toggle_devtools() {
947        // Use App methods instead of deprecated global functions
948        // Start with devtools explicitly disabled
949        let mut app = App::builder().devtools(false).build();
950        assert!(!app.is_devtools_enabled());
951
952        // Toggle should enable
953        let result = app.toggle_devtools();
954        assert!(result); // Returns new state (enabled)
955        assert!(app.is_devtools_enabled());
956
957        // Toggle should disable
958        let result = app.toggle_devtools();
959        assert!(!result); // Returns new state (disabled)
960        assert!(!app.is_devtools_enabled());
961    }
962}