1use std::io::Write;
7
8use base64::prelude::*;
9use crossterm::event::{KeyCode, KeyEvent, MouseButton, MouseEventKind};
10use ratatui::buffer::Buffer;
11use ratatui::layout::Rect;
12use ratatui::style::Style;
13use ratatui::widgets::{Block, Borders, Clear};
14use ratatui::Frame;
15
16use super::action_logger::{ActionLog, ActionLogConfig};
17use super::actions::{DebugAction, DebugSideEffect};
18use super::cell::inspect_cell;
19use super::config::DebugStyle;
20use super::state::DebugState;
21use super::table::{ActionLogOverlay, DebugOverlay, DebugTableBuilder, DebugTableOverlay};
22use super::widgets::{
23 dim_buffer, paint_snapshot, ActionLogWidget, BannerItem, CellPreviewWidget, DebugBanner,
24 DebugTableWidget,
25};
26use super::DebugFreeze;
27#[cfg(feature = "subscriptions")]
28use crate::subscriptions::SubPauseHandle;
29#[cfg(feature = "tasks")]
30use crate::tasks::TaskPauseHandle;
31use crate::Action;
32
33pub struct DebugLayer<A> {
63 toggle_key: KeyCode,
65 freeze: DebugFreeze<A>,
67 style: DebugStyle,
69 active: bool,
71 action_log: ActionLog,
73 #[cfg(feature = "tasks")]
75 task_handle: Option<TaskPauseHandle<A>>,
76 #[cfg(feature = "subscriptions")]
78 sub_handle: Option<SubPauseHandle>,
79}
80
81impl<A> std::fmt::Debug for DebugLayer<A> {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 f.debug_struct("DebugLayer")
84 .field("toggle_key", &self.toggle_key)
85 .field("active", &self.active)
86 .field("enabled", &self.freeze.enabled)
87 .field("has_snapshot", &self.freeze.snapshot.is_some())
88 .field("queued_actions", &self.freeze.queued_actions.len())
89 .finish()
90 }
91}
92
93impl<A: Action> DebugLayer<A> {
94 pub fn new(toggle_key: KeyCode) -> Self {
104 Self {
105 toggle_key,
106 freeze: DebugFreeze::new(),
107 style: DebugStyle::default(),
108 active: true,
109 action_log: ActionLog::new(ActionLogConfig::with_capacity(100)),
110 #[cfg(feature = "tasks")]
111 task_handle: None,
112 #[cfg(feature = "subscriptions")]
113 sub_handle: None,
114 }
115 }
116
117 pub fn active(mut self, active: bool) -> Self {
121 self.active = active;
122 self
123 }
124
125 #[cfg(feature = "tasks")]
130 pub fn with_task_manager(mut self, tasks: &crate::tasks::TaskManager<A>) -> Self {
131 self.task_handle = Some(tasks.pause_handle());
132 self
133 }
134
135 #[cfg(feature = "subscriptions")]
139 pub fn with_subscriptions(mut self, subs: &crate::subscriptions::Subscriptions<A>) -> Self {
140 self.sub_handle = Some(subs.pause_handle());
141 self
142 }
143
144 pub fn with_action_log_capacity(mut self, capacity: usize) -> Self {
146 self.action_log = ActionLog::new(ActionLogConfig::with_capacity(capacity));
147 self
148 }
149
150 pub fn with_style(mut self, style: DebugStyle) -> Self {
152 self.style = style;
153 self
154 }
155
156 pub fn is_active(&self) -> bool {
158 self.active
159 }
160
161 pub fn is_enabled(&self) -> bool {
163 self.active && self.freeze.enabled
164 }
165
166 pub fn is_state_overlay_visible(&self) -> bool {
168 matches!(self.freeze.overlay, Some(DebugOverlay::State(_)))
169 }
170
171 pub fn freeze(&self) -> &DebugFreeze<A> {
173 &self.freeze
174 }
175
176 pub fn freeze_mut(&mut self) -> &mut DebugFreeze<A> {
178 &mut self.freeze
179 }
180
181 pub fn log_action<T: crate::ActionParams>(&mut self, action: &T) {
185 if self.active {
186 self.action_log.log(action);
187 }
188 }
189
190 pub fn action_log(&self) -> &ActionLog {
192 &self.action_log
193 }
194
195 pub fn render<F>(&mut self, frame: &mut Frame, render_fn: F)
200 where
201 F: FnOnce(&mut Frame, Rect),
202 {
203 let screen = frame.area();
204
205 if !self.active || !self.freeze.enabled {
207 render_fn(frame, screen);
208 return;
209 }
210
211 let (app_area, banner_area) = self.split_for_banner(screen);
213
214 if self.freeze.pending_capture || self.freeze.snapshot.is_none() {
215 render_fn(frame, app_area);
217 let buffer_clone = frame.buffer_mut().clone();
218 self.freeze.capture(&buffer_clone);
219 } else if let Some(ref snapshot) = self.freeze.snapshot {
220 paint_snapshot(frame, snapshot);
222 }
223
224 self.render_debug_overlay(frame, app_area, banner_area);
226 }
227
228 pub fn split_area(&self, area: Rect) -> (Rect, Rect) {
230 if !self.freeze.enabled {
231 return (area, Rect::ZERO);
232 }
233 self.split_for_banner(area)
234 }
235
236 pub fn intercepts(&mut self, event: &crate::EventKind) -> bool {
250 self.intercepts_with_effects(event).is_some()
251 }
252
253 pub fn intercepts_with_effects(
257 &mut self,
258 event: &crate::EventKind,
259 ) -> Option<Vec<DebugSideEffect<A>>> {
260 if !self.active {
261 return None;
262 }
263
264 use crate::EventKind;
265
266 match event {
267 EventKind::Key(key) => self.handle_key_event(*key),
268 EventKind::Mouse(mouse) => {
269 if !self.freeze.enabled {
270 return None;
271 }
272
273 if !self.freeze.mouse_capture_enabled {
276 return None;
277 }
278
279 if matches!(mouse.kind, MouseEventKind::Down(MouseButton::Left)) {
281 let effect = self.handle_action(DebugAction::InspectCell {
282 column: mouse.column,
283 row: mouse.row,
284 });
285 return Some(effect.into_iter().collect());
286 }
287
288 Some(vec![])
290 }
291 EventKind::Scroll { delta, .. } => {
292 if !self.freeze.enabled {
293 return None;
294 }
295
296 if let Some(DebugOverlay::ActionLog(_)) = self.freeze.overlay {
298 let action = if *delta > 0 {
299 DebugAction::ActionLogScrollUp
300 } else {
301 DebugAction::ActionLogScrollDown
302 };
303 self.handle_action(action);
304 }
305
306 Some(vec![])
307 }
308 EventKind::Resize(_, _) | EventKind::Tick => None,
310 }
311 }
312
313 pub fn show_state_overlay<S: DebugState>(&mut self, state: &S) {
315 let table = state.build_debug_table("Application State");
316 self.freeze.set_overlay(DebugOverlay::State(table));
317 }
318
319 pub fn show_action_log(&mut self) {
321 let overlay = ActionLogOverlay::from_log(&self.action_log, "Action Log");
322 self.freeze.set_overlay(DebugOverlay::ActionLog(overlay));
323 }
324
325 pub fn queue_action(&mut self, action: A) {
327 self.freeze.queue(action);
328 }
329
330 pub fn take_queued_actions(&mut self) -> Vec<A> {
335 std::mem::take(&mut self.freeze.queued_actions)
336 }
337
338 fn handle_key_event(&mut self, key: KeyEvent) -> Option<Vec<DebugSideEffect<A>>> {
343 if key.code == self.toggle_key && key.modifiers.is_empty() {
345 let effect = self.toggle();
346 return Some(effect.into_iter().collect());
347 }
348
349 if self.freeze.enabled && key.code == KeyCode::Esc {
351 let effect = self.toggle();
352 return Some(effect.into_iter().collect());
353 }
354
355 if !self.freeze.enabled {
357 return None;
358 }
359
360 let action = match key.code {
362 KeyCode::Char('s') | KeyCode::Char('S') => Some(DebugAction::ToggleState),
363 KeyCode::Char('a') | KeyCode::Char('A') => Some(DebugAction::ToggleActionLog),
364 KeyCode::Char('y') | KeyCode::Char('Y') => Some(DebugAction::CopyFrame),
365 KeyCode::Char('i') | KeyCode::Char('I') => Some(DebugAction::ToggleMouseCapture),
366 KeyCode::Char('q') | KeyCode::Char('Q') => Some(DebugAction::CloseOverlay),
367 _ => None,
368 };
369
370 if let Some(action) = action {
371 let effect = self.handle_action(action);
372 return Some(effect.into_iter().collect());
373 }
374
375 match &self.freeze.overlay {
377 Some(DebugOverlay::ActionLog(_)) => {
378 let action = match key.code {
379 KeyCode::Char('j') | KeyCode::Down => Some(DebugAction::ActionLogScrollDown),
380 KeyCode::Char('k') | KeyCode::Up => Some(DebugAction::ActionLogScrollUp),
381 KeyCode::Char('g') => Some(DebugAction::ActionLogScrollTop),
382 KeyCode::Char('G') => Some(DebugAction::ActionLogScrollBottom),
383 KeyCode::PageDown => Some(DebugAction::ActionLogPageDown),
384 KeyCode::PageUp => Some(DebugAction::ActionLogPageUp),
385 KeyCode::Enter => Some(DebugAction::ActionLogShowDetail),
386 _ => None,
387 };
388 if let Some(action) = action {
389 self.handle_action(action);
390 return Some(vec![]);
391 }
392 }
393 Some(DebugOverlay::ActionDetail(_)) => {
394 if matches!(key.code, KeyCode::Esc | KeyCode::Backspace | KeyCode::Enter) {
396 self.handle_action(DebugAction::ActionLogBackToList);
397 return Some(vec![]);
398 }
399 }
400 _ => {}
401 }
402
403 Some(vec![])
405 }
406
407 fn toggle(&mut self) -> Option<DebugSideEffect<A>> {
408 if self.freeze.enabled {
409 #[cfg(feature = "subscriptions")]
411 if let Some(ref handle) = self.sub_handle {
412 handle.resume();
413 }
414
415 #[cfg(feature = "tasks")]
416 let task_queued = if let Some(ref handle) = self.task_handle {
417 handle.resume()
418 } else {
419 vec![]
420 };
421 #[cfg(not(feature = "tasks"))]
422 let task_queued: Vec<A> = vec![];
423
424 let queued = self.freeze.take_queued();
425 self.freeze.disable();
426
427 let mut all_queued = queued;
429 all_queued.extend(task_queued);
430
431 if all_queued.is_empty() {
432 None
433 } else {
434 Some(DebugSideEffect::ProcessQueuedActions(all_queued))
435 }
436 } else {
437 #[cfg(feature = "tasks")]
439 if let Some(ref handle) = self.task_handle {
440 handle.pause();
441 }
442 #[cfg(feature = "subscriptions")]
443 if let Some(ref handle) = self.sub_handle {
444 handle.pause();
445 }
446 self.freeze.enable();
447 None
448 }
449 }
450
451 fn handle_action(&mut self, action: DebugAction) -> Option<DebugSideEffect<A>> {
452 match action {
453 DebugAction::Toggle => self.toggle(),
454 DebugAction::CopyFrame => {
455 let text = &self.freeze.snapshot_text;
456 let encoded = BASE64_STANDARD.encode(text);
458 print!("\x1b]52;c;{}\x07", encoded);
459 std::io::stdout().flush().ok();
460 self.freeze.set_message("Copied to clipboard");
461 None
462 }
463 DebugAction::ToggleState => {
464 if matches!(self.freeze.overlay, Some(DebugOverlay::State(_))) {
465 self.freeze.clear_overlay();
466 } else {
467 let table = DebugTableBuilder::new()
469 .section("State")
470 .entry("hint", "Press 's' after calling show_state_overlay()")
471 .finish("Application State");
472 self.freeze.set_overlay(DebugOverlay::State(table));
473 }
474 None
475 }
476 DebugAction::ToggleActionLog => {
477 if matches!(self.freeze.overlay, Some(DebugOverlay::ActionLog(_))) {
478 self.freeze.clear_overlay();
479 } else {
480 self.show_action_log();
481 }
482 None
483 }
484 DebugAction::ActionLogScrollUp => {
485 if let Some(DebugOverlay::ActionLog(ref mut log)) = self.freeze.overlay {
486 log.scroll_up();
487 }
488 None
489 }
490 DebugAction::ActionLogScrollDown => {
491 if let Some(DebugOverlay::ActionLog(ref mut log)) = self.freeze.overlay {
492 log.scroll_down();
493 }
494 None
495 }
496 DebugAction::ActionLogScrollTop => {
497 if let Some(DebugOverlay::ActionLog(ref mut log)) = self.freeze.overlay {
498 log.scroll_to_top();
499 }
500 None
501 }
502 DebugAction::ActionLogScrollBottom => {
503 if let Some(DebugOverlay::ActionLog(ref mut log)) = self.freeze.overlay {
504 log.scroll_to_bottom();
505 }
506 None
507 }
508 DebugAction::ActionLogPageUp => {
509 if let Some(DebugOverlay::ActionLog(ref mut log)) = self.freeze.overlay {
510 log.page_up(10);
511 }
512 None
513 }
514 DebugAction::ActionLogPageDown => {
515 if let Some(DebugOverlay::ActionLog(ref mut log)) = self.freeze.overlay {
516 log.page_down(10);
517 }
518 None
519 }
520 DebugAction::ActionLogShowDetail => {
521 if let Some(DebugOverlay::ActionLog(ref log)) = self.freeze.overlay {
522 if let Some(detail) = log.selected_detail() {
523 self.freeze.set_overlay(DebugOverlay::ActionDetail(detail));
524 }
525 }
526 None
527 }
528 DebugAction::ActionLogBackToList => {
529 if matches!(self.freeze.overlay, Some(DebugOverlay::ActionDetail(_))) {
531 self.show_action_log();
532 }
533 None
534 }
535 DebugAction::ToggleMouseCapture => {
536 self.freeze.toggle_mouse_capture();
537 None
538 }
539 DebugAction::InspectCell { column, row } => {
540 if let Some(ref snapshot) = self.freeze.snapshot {
541 let overlay = self.build_inspect_overlay(column, row, snapshot);
542 self.freeze.set_overlay(DebugOverlay::Inspect(overlay));
543 }
544 self.freeze.mouse_capture_enabled = false;
545 None
546 }
547 DebugAction::CloseOverlay => {
548 self.freeze.clear_overlay();
549 None
550 }
551 DebugAction::RequestCapture => {
552 self.freeze.request_capture();
553 None
554 }
555 }
556 }
557
558 fn split_for_banner(&self, area: Rect) -> (Rect, Rect) {
559 let banner_height = 1;
560 let app_area = Rect {
561 height: area.height.saturating_sub(banner_height),
562 ..area
563 };
564 let banner_area = Rect {
565 y: area.y.saturating_add(app_area.height),
566 height: banner_height.min(area.height),
567 ..area
568 };
569 (app_area, banner_area)
570 }
571
572 fn render_debug_overlay(&self, frame: &mut Frame, app_area: Rect, banner_area: Rect) {
573 if let Some(ref overlay) = self.freeze.overlay {
575 dim_buffer(frame.buffer_mut(), self.style.dim_factor);
576
577 match overlay {
578 DebugOverlay::Inspect(table) | DebugOverlay::State(table) => {
579 self.render_table_modal(frame, app_area, table);
580 }
581 DebugOverlay::ActionLog(log) => {
582 self.render_action_log_modal(frame, app_area, log);
583 }
584 DebugOverlay::ActionDetail(detail) => {
585 self.render_action_detail_modal(frame, app_area, detail);
586 }
587 }
588 }
589
590 self.render_banner(frame, banner_area);
592 }
593
594 fn render_banner(&self, frame: &mut Frame, banner_area: Rect) {
595 if banner_area.height == 0 {
596 return;
597 }
598
599 let keys = &self.style.key_styles;
600 let toggle_key_str = format_key(self.toggle_key);
601 let mut banner = DebugBanner::new()
602 .title("DEBUG")
603 .title_style(self.style.title_style)
604 .label_style(self.style.label_style)
605 .background(self.style.banner_bg);
606
607 banner = banner.item(BannerItem::new(&toggle_key_str, "resume", keys.toggle));
609 banner = banner.item(BannerItem::new("a", "actions", keys.actions));
610 banner = banner.item(BannerItem::new("s", "state", keys.state));
611 banner = banner.item(BannerItem::new("y", "copy", keys.copy));
612
613 if self.freeze.mouse_capture_enabled {
614 banner = banner.item(BannerItem::new("click", "inspect", keys.mouse));
615 } else {
616 banner = banner.item(BannerItem::new("i", "mouse", keys.mouse));
617 }
618
619 if let Some(ref msg) = self.freeze.message {
621 banner = banner.item(BannerItem::new("", msg, self.style.value_style));
622 }
623
624 frame.render_widget(banner, banner_area);
625 }
626
627 fn render_table_modal(&self, frame: &mut Frame, app_area: Rect, table: &DebugTableOverlay) {
628 let modal_width = (app_area.width * 80 / 100)
629 .clamp(30, 120)
630 .min(app_area.width);
631 let modal_height = (app_area.height * 60 / 100)
632 .clamp(8, 40)
633 .min(app_area.height);
634
635 let modal_x = app_area.x + (app_area.width.saturating_sub(modal_width)) / 2;
636 let modal_y = app_area.y + (app_area.height.saturating_sub(modal_height)) / 2;
637
638 let modal_area = Rect::new(modal_x, modal_y, modal_width, modal_height);
639
640 frame.render_widget(Clear, modal_area);
641
642 let block = Block::default()
643 .borders(Borders::ALL)
644 .title(format!(" {} ", table.title))
645 .style(self.style.banner_bg);
646
647 let inner = block.inner(modal_area);
648 frame.render_widget(block, modal_area);
649
650 if let Some(ref preview) = table.cell_preview {
652 if inner.height > 3 {
653 let preview_height = 2u16;
654 let preview_area = Rect {
655 x: inner.x,
656 y: inner.y,
657 width: inner.width,
658 height: 1,
659 };
660 let table_area = Rect {
661 x: inner.x,
662 y: inner.y.saturating_add(preview_height),
663 width: inner.width,
664 height: inner.height.saturating_sub(preview_height),
665 };
666
667 let preview_widget = CellPreviewWidget::new(preview)
668 .label_style(Style::default().fg(DebugStyle::text_secondary()))
669 .value_style(Style::default().fg(DebugStyle::text_primary()));
670 frame.render_widget(preview_widget, preview_area);
671
672 let table_widget = DebugTableWidget::new(table);
673 frame.render_widget(table_widget, table_area);
674 return;
675 }
676 }
677
678 let table_widget = DebugTableWidget::new(table);
679 frame.render_widget(table_widget, inner);
680 }
681
682 fn render_action_log_modal(&self, frame: &mut Frame, app_area: Rect, log: &ActionLogOverlay) {
683 let modal_width = (app_area.width * 90 / 100)
684 .clamp(40, 140)
685 .min(app_area.width);
686 let modal_height = (app_area.height * 70 / 100)
687 .clamp(10, 50)
688 .min(app_area.height);
689
690 let modal_x = app_area.x + (app_area.width.saturating_sub(modal_width)) / 2;
691 let modal_y = app_area.y + (app_area.height.saturating_sub(modal_height)) / 2;
692
693 let modal_area = Rect::new(modal_x, modal_y, modal_width, modal_height);
694
695 frame.render_widget(Clear, modal_area);
696
697 let entry_count = log.entries.len();
698 let title = if entry_count > 0 {
699 format!(" {} ({} entries) ", log.title, entry_count)
700 } else {
701 format!(" {} (empty) ", log.title)
702 };
703
704 let block = Block::default()
705 .borders(Borders::ALL)
706 .title(title)
707 .style(self.style.banner_bg);
708
709 let inner = block.inner(modal_area);
710 frame.render_widget(block, modal_area);
711
712 let widget = ActionLogWidget::new(log);
713 frame.render_widget(widget, inner);
714 }
715
716 fn render_action_detail_modal(
717 &self,
718 frame: &mut Frame,
719 app_area: Rect,
720 detail: &super::table::ActionDetailOverlay,
721 ) {
722 let modal_width = (app_area.width * 80 / 100)
723 .clamp(40, 120)
724 .min(app_area.width);
725 let modal_height = (app_area.height * 50 / 100)
726 .clamp(8, 30)
727 .min(app_area.height);
728
729 let modal_x = app_area.x + (app_area.width.saturating_sub(modal_width)) / 2;
730 let modal_y = app_area.y + (app_area.height.saturating_sub(modal_height)) / 2;
731
732 let modal_area = Rect::new(modal_x, modal_y, modal_width, modal_height);
733
734 frame.render_widget(Clear, modal_area);
735
736 let title = format!(" Action #{} - {} ", detail.sequence, detail.name);
737
738 let block = Block::default()
739 .borders(Borders::ALL)
740 .title(title)
741 .style(self.style.banner_bg);
742
743 let inner = block.inner(modal_area);
744 frame.render_widget(block, modal_area);
745
746 use ratatui::text::{Line, Span};
748 use ratatui::widgets::Paragraph;
749
750 let label_style = Style::default().fg(DebugStyle::text_secondary());
751 let value_style = Style::default().fg(DebugStyle::text_primary());
752
753 let mut lines = vec![
754 Line::from(vec![
756 Span::styled("Name: ", label_style),
757 Span::styled(&detail.name, value_style),
758 ]),
759 Line::from(vec![
761 Span::styled("Sequence: ", label_style),
762 Span::styled(detail.sequence.to_string(), value_style),
763 ]),
764 Line::from(vec![
766 Span::styled("Elapsed: ", label_style),
767 Span::styled(&detail.elapsed, value_style),
768 ]),
769 Line::from(""),
771 Line::from(Span::styled("Parameters:", label_style)),
773 ];
774
775 if detail.params.is_empty() {
777 lines.push(Line::from(Span::styled(" (none)", value_style)));
778 } else {
779 for param_line in detail.params.lines() {
780 lines.push(Line::from(Span::styled(
781 format!(" {}", param_line),
782 value_style,
783 )));
784 }
785 }
786
787 lines.push(Line::from(""));
789 lines.push(Line::from(Span::styled(
790 "Press Enter/Esc/Backspace to go back",
791 label_style,
792 )));
793
794 let paragraph = Paragraph::new(lines);
795 frame.render_widget(paragraph, inner);
796 }
797
798 fn build_inspect_overlay(&self, column: u16, row: u16, snapshot: &Buffer) -> DebugTableOverlay {
799 let mut builder = DebugTableBuilder::new();
800
801 builder.push_section("Position");
802 builder.push_entry("column", column.to_string());
803 builder.push_entry("row", row.to_string());
804
805 if let Some(preview) = inspect_cell(snapshot, column, row) {
806 builder.set_cell_preview(preview);
807 }
808
809 builder.finish(format!("Inspect ({column}, {row})"))
810 }
811}
812
813fn format_key(key: KeyCode) -> String {
815 match key {
816 KeyCode::F(n) => format!("F{}", n),
817 KeyCode::Char(c) => c.to_string(),
818 KeyCode::Esc => "Esc".to_string(),
819 KeyCode::Enter => "Enter".to_string(),
820 KeyCode::Tab => "Tab".to_string(),
821 KeyCode::Backspace => "Bksp".to_string(),
822 KeyCode::Delete => "Del".to_string(),
823 KeyCode::Up => "↑".to_string(),
824 KeyCode::Down => "↓".to_string(),
825 KeyCode::Left => "←".to_string(),
826 KeyCode::Right => "→".to_string(),
827 _ => format!("{:?}", key),
828 }
829}
830
831#[cfg(test)]
832mod tests {
833 use super::*;
834
835 #[derive(Debug, Clone)]
836 enum TestAction {
837 Foo,
838 Bar,
839 }
840
841 impl crate::Action for TestAction {
842 fn name(&self) -> &'static str {
843 match self {
844 TestAction::Foo => "Foo",
845 TestAction::Bar => "Bar",
846 }
847 }
848 }
849
850 impl crate::ActionParams for TestAction {
851 fn params(&self) -> String {
852 String::new()
853 }
854 }
855
856 #[test]
857 fn test_debug_layer_creation() {
858 let layer: DebugLayer<TestAction> = DebugLayer::new(KeyCode::F(12));
859 assert!(!layer.is_enabled());
860 assert!(layer.freeze().snapshot.is_none());
861 }
862
863 #[test]
864 fn test_toggle() {
865 let mut layer: DebugLayer<TestAction> = DebugLayer::new(KeyCode::F(12));
866
867 let effect = layer.toggle();
869 assert!(effect.is_none());
870 assert!(layer.is_enabled());
871
872 let effect = layer.toggle();
874 assert!(effect.is_none()); assert!(!layer.is_enabled());
876 }
877
878 #[test]
879 fn test_queued_actions_returned_on_disable() {
880 let mut layer: DebugLayer<TestAction> = DebugLayer::new(KeyCode::F(12));
881
882 layer.toggle(); layer.queue_action(TestAction::Foo);
884 layer.queue_action(TestAction::Bar);
885
886 let effect = layer.toggle(); match effect {
889 Some(DebugSideEffect::ProcessQueuedActions(actions)) => {
890 assert_eq!(actions.len(), 2);
891 }
892 _ => panic!("Expected ProcessQueuedActions"),
893 }
894 }
895
896 #[test]
897 fn test_split_area() {
898 let layer: DebugLayer<TestAction> = DebugLayer::new(KeyCode::F(12));
899
900 let area = Rect::new(0, 0, 80, 24);
902 let (app, banner) = layer.split_area(area);
903 assert_eq!(app, area);
904 assert_eq!(banner, Rect::ZERO);
905 }
906
907 #[test]
908 fn test_split_area_enabled() {
909 let mut layer: DebugLayer<TestAction> = DebugLayer::new(KeyCode::F(12));
910 layer.toggle();
911
912 let area = Rect::new(0, 0, 80, 24);
913 let (app, banner) = layer.split_area(area);
914
915 assert_eq!(app.height, 23);
916 assert_eq!(banner.height, 1);
917 assert_eq!(banner.y, 23);
918 }
919
920 #[test]
921 fn test_inactive_layer() {
922 let layer: DebugLayer<TestAction> = DebugLayer::new(KeyCode::F(12)).active(false);
923
924 assert!(!layer.is_active());
925 assert!(!layer.is_enabled());
926 }
927
928 #[test]
929 fn test_action_log() {
930 let mut layer: DebugLayer<TestAction> = DebugLayer::new(KeyCode::F(12));
931
932 layer.log_action(&TestAction::Foo);
933 layer.log_action(&TestAction::Bar);
934
935 assert_eq!(layer.action_log().entries().count(), 2);
936 }
937}