1use std::collections::VecDeque;
7use std::io::{self, Write};
8use std::time::Duration;
9
10use crossterm::event::Event;
11
12#[derive(Debug)]
19pub struct MockTty {
20 output: Vec<u8>,
21 size: (u16, u16),
22 raw_mode: bool,
23 alternate_screen: bool,
24 cursor_visible: bool,
25 mouse_captured: bool,
26 events: VecDeque<Event>,
27 poll_results: VecDeque<bool>,
28}
29
30impl MockTty {
31 pub fn new(width: u16, height: u16) -> Self {
33 Self {
34 output: Vec::new(),
35 size: (width, height),
36 raw_mode: false,
37 alternate_screen: false,
38 cursor_visible: true,
39 mouse_captured: false,
40 events: VecDeque::new(),
41 poll_results: VecDeque::new(),
42 }
43 }
44
45 pub fn with_events(mut self, events: Vec<Event>) -> Self {
47 self.events = events.into_iter().collect();
48 self
49 }
50
51 pub fn with_polls(mut self, polls: Vec<bool>) -> Self {
53 self.poll_results = polls.into_iter().collect();
54 self
55 }
56
57 pub fn size(&self) -> (u16, u16) {
59 self.size
60 }
61
62 pub fn set_size(&mut self, width: u16, height: u16) {
64 self.size = (width, height);
65 }
66
67 pub fn is_raw_mode(&self) -> bool {
69 self.raw_mode
70 }
71
72 pub fn enable_raw_mode(&mut self) {
74 self.raw_mode = true;
75 }
76
77 pub fn disable_raw_mode(&mut self) {
79 self.raw_mode = false;
80 }
81
82 pub fn is_alternate_screen(&self) -> bool {
84 self.alternate_screen
85 }
86
87 pub fn enter_alternate_screen(&mut self) {
89 self.alternate_screen = true;
90 let _ = self.output.write_all(b"\x1b[?1049h");
92 }
93
94 pub fn leave_alternate_screen(&mut self) {
96 self.alternate_screen = false;
97 let _ = self.output.write_all(b"\x1b[?1049l");
98 }
99
100 pub fn is_cursor_visible(&self) -> bool {
102 self.cursor_visible
103 }
104
105 pub fn hide_cursor(&mut self) {
107 self.cursor_visible = false;
108 let _ = self.output.write_all(b"\x1b[?25l");
109 }
110
111 pub fn show_cursor(&mut self) {
113 self.cursor_visible = true;
114 let _ = self.output.write_all(b"\x1b[?25h");
115 }
116
117 pub fn is_mouse_captured(&self) -> bool {
119 self.mouse_captured
120 }
121
122 pub fn enable_mouse_capture(&mut self) {
124 self.mouse_captured = true;
125 let _ = self
126 .output
127 .write_all(b"\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h");
128 }
129
130 pub fn disable_mouse_capture(&mut self) {
132 self.mouse_captured = false;
133 let _ = self
134 .output
135 .write_all(b"\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l");
136 }
137
138 pub fn poll(&mut self, _timeout: Duration) -> io::Result<bool> {
140 Ok(self.poll_results.pop_front().unwrap_or(false))
141 }
142
143 pub fn read_event(&mut self) -> io::Result<Event> {
145 self.events
146 .pop_front()
147 .ok_or_else(|| io::Error::new(io::ErrorKind::WouldBlock, "no events available"))
148 }
149
150 pub fn output(&self) -> &[u8] {
152 &self.output
153 }
154
155 pub fn output_str(&self) -> String {
157 String::from_utf8_lossy(&self.output).into_owned()
158 }
159
160 pub fn clear_output(&mut self) {
162 self.output.clear();
163 }
164
165 pub fn output_contains(&self, needle: &[u8]) -> bool {
168 if needle.is_empty() {
169 return false;
170 }
171 self.output
172 .windows(needle.len())
173 .any(|window| window == needle)
174 }
175
176 pub fn output_contains_str(&self, needle: &str) -> bool {
178 self.output_contains(needle.as_bytes())
179 }
180
181 pub fn contains_escape(&self, seq: &str) -> bool {
183 let escape_seq = format!("\x1b[{}", seq);
184 self.output_contains_str(&escape_seq)
185 }
186
187 pub fn parsed_commands(&self) -> Vec<AnsiCommand> {
189 parse_ansi_commands(&self.output)
190 }
191
192 pub fn queued_events(&self) -> usize {
194 self.events.len()
195 }
196
197 pub fn push_event(&mut self, event: Event) {
199 self.events.push_back(event);
200 }
201
202 pub fn push_poll(&mut self, result: bool) {
204 self.poll_results.push_back(result);
205 }
206}
207
208impl Write for MockTty {
209 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
210 self.output.extend_from_slice(buf);
211 Ok(buf.len())
212 }
213
214 fn flush(&mut self) -> io::Result<()> {
215 Ok(())
216 }
217}
218
219impl Default for MockTty {
220 fn default() -> Self {
221 Self::new(80, 24)
222 }
223}
224
225#[derive(Debug, Clone, PartialEq, Eq)]
227pub enum AnsiCommand {
228 CursorMove {
230 row: u16,
232 col: u16,
234 },
235 ClearScreen(ClearMode),
237 ClearLine(ClearMode),
239 SetAttribute(Vec<u8>),
241 EnterAlternateScreen,
243 LeaveAlternateScreen,
245 HideCursor,
247 ShowCursor,
249 EnableMouse,
251 DisableMouse,
253 Text(String),
255 Unknown(Vec<u8>),
257}
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
261pub enum ClearMode {
262 ToEnd,
264 ToBeginning,
266 All,
268}
269
270fn parse_ansi_commands(output: &[u8]) -> Vec<AnsiCommand> {
272 let mut commands = Vec::new();
273 let mut i = 0;
274 let mut text_start = 0;
275
276 while i < output.len() {
277 if output[i] == 0x1b && i + 1 < output.len() && output[i + 1] == b'[' {
278 if text_start < i {
280 if let Ok(text) = std::str::from_utf8(&output[text_start..i]) {
281 if !text.is_empty() {
282 commands.push(AnsiCommand::Text(text.to_string()));
283 }
284 }
285 }
286
287 let seq_start = i;
289 i += 2; let params_start = i;
293 while i < output.len() && (0x30..=0x3F).contains(&output[i]) {
294 i += 1;
295 }
296 let params = &output[params_start..i];
297
298 while i < output.len() && (0x20..=0x2F).contains(&output[i]) {
300 i += 1;
301 }
302
303 if i < output.len() && (0x40..=0x7E).contains(&output[i]) {
305 let final_byte = output[i];
306 i += 1;
307
308 let cmd = parse_csi_command(params, final_byte);
309 commands.push(cmd);
310 } else {
311 commands.push(AnsiCommand::Unknown(output[seq_start..i].to_vec()));
313 }
314
315 text_start = i;
316 } else {
317 i += 1;
318 }
319 }
320
321 if text_start < output.len() {
323 if let Ok(text) = std::str::from_utf8(&output[text_start..]) {
324 if !text.is_empty() {
325 commands.push(AnsiCommand::Text(text.to_string()));
326 }
327 }
328 }
329
330 commands
331}
332
333fn parse_csi_command(params: &[u8], final_byte: u8) -> AnsiCommand {
335 let params_str = std::str::from_utf8(params).unwrap_or("");
336
337 match final_byte {
338 b'H' | b'f' => {
339 let parts: Vec<u16> = params_str
341 .split(';')
342 .filter_map(|s| s.parse().ok())
343 .collect();
344 let row = parts.first().copied().unwrap_or(1);
345 let col = parts.get(1).copied().unwrap_or(1);
346 AnsiCommand::CursorMove { row, col }
347 }
348 b'J' => {
349 let mode = match params_str {
351 "" | "0" => ClearMode::ToEnd,
352 "1" => ClearMode::ToBeginning,
353 "2" | "3" => ClearMode::All,
354 _ => ClearMode::ToEnd,
355 };
356 AnsiCommand::ClearScreen(mode)
357 }
358 b'K' => {
359 let mode = match params_str {
361 "" | "0" => ClearMode::ToEnd,
362 "1" => ClearMode::ToBeginning,
363 "2" => ClearMode::All,
364 _ => ClearMode::ToEnd,
365 };
366 AnsiCommand::ClearLine(mode)
367 }
368 b'm' => {
369 let attrs: Vec<u8> = params_str
371 .split(';')
372 .filter_map(|s| s.parse().ok())
373 .collect();
374 AnsiCommand::SetAttribute(attrs)
375 }
376 b'h' => {
377 if params_str == "?1049" {
379 AnsiCommand::EnterAlternateScreen
380 } else if params_str == "?25" {
381 AnsiCommand::ShowCursor
382 } else if params_str.starts_with("?1000") || params_str.starts_with("?1002") {
383 AnsiCommand::EnableMouse
384 } else {
385 AnsiCommand::Unknown(format!("\x1b[{}h", params_str).into_bytes())
386 }
387 }
388 b'l' => {
389 if params_str == "?1049" {
391 AnsiCommand::LeaveAlternateScreen
392 } else if params_str == "?25" {
393 AnsiCommand::HideCursor
394 } else if params_str.starts_with("?1000") || params_str.starts_with("?1006") {
395 AnsiCommand::DisableMouse
396 } else {
397 AnsiCommand::Unknown(format!("\x1b[{}l", params_str).into_bytes())
398 }
399 }
400 _ => {
401 AnsiCommand::Unknown(format!("\x1b[{}{}", params_str, final_byte as char).into_bytes())
403 }
404 }
405}
406
407#[cfg(test)]
408mod tests {
409 use super::*;
410 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
411
412 #[test]
413 fn test_new() {
414 let tty = MockTty::new(120, 40);
415 assert_eq!(tty.size(), (120, 40));
416 assert!(!tty.is_raw_mode());
417 assert!(!tty.is_alternate_screen());
418 assert!(tty.is_cursor_visible());
419 assert!(!tty.is_mouse_captured());
420 }
421
422 #[test]
423 fn test_default() {
424 let tty = MockTty::default();
425 assert_eq!(tty.size(), (80, 24));
426 }
427
428 #[test]
429 fn test_raw_mode() {
430 let mut tty = MockTty::new(80, 24);
431 assert!(!tty.is_raw_mode());
432 tty.enable_raw_mode();
433 assert!(tty.is_raw_mode());
434 tty.disable_raw_mode();
435 assert!(!tty.is_raw_mode());
436 }
437
438 #[test]
439 fn test_alternate_screen() {
440 let mut tty = MockTty::new(80, 24);
441 assert!(!tty.is_alternate_screen());
442 tty.enter_alternate_screen();
443 assert!(tty.is_alternate_screen());
444 assert!(tty.output_contains_str("\x1b[?1049h"));
445 tty.leave_alternate_screen();
446 assert!(!tty.is_alternate_screen());
447 assert!(tty.output_contains_str("\x1b[?1049l"));
448 }
449
450 #[test]
451 fn test_cursor_visibility() {
452 let mut tty = MockTty::new(80, 24);
453 assert!(tty.is_cursor_visible());
454 tty.hide_cursor();
455 assert!(!tty.is_cursor_visible());
456 assert!(tty.output_contains_str("\x1b[?25l"));
457 tty.show_cursor();
458 assert!(tty.is_cursor_visible());
459 assert!(tty.output_contains_str("\x1b[?25h"));
460 }
461
462 #[test]
463 fn test_mouse_capture() {
464 let mut tty = MockTty::new(80, 24);
465 assert!(!tty.is_mouse_captured());
466 tty.enable_mouse_capture();
467 assert!(tty.is_mouse_captured());
468 tty.disable_mouse_capture();
469 assert!(!tty.is_mouse_captured());
470 }
471
472 #[test]
473 fn test_write() {
474 let mut tty = MockTty::new(80, 24);
475 tty.write_all(b"Hello, World!").unwrap();
476 assert_eq!(tty.output(), b"Hello, World!");
477 assert_eq!(tty.output_str(), "Hello, World!");
478 }
479
480 #[test]
481 fn test_output_contains() {
482 let mut tty = MockTty::new(80, 24);
483 tty.write_all(b"Hello, World!").unwrap();
484 assert!(tty.output_contains(b"World"));
485 assert!(tty.output_contains_str("Hello"));
486 assert!(!tty.output_contains_str("Goodbye"));
487 }
488
489 #[test]
490 fn test_clear_output() {
491 let mut tty = MockTty::new(80, 24);
492 tty.write_all(b"Hello").unwrap();
493 assert!(!tty.output().is_empty());
494 tty.clear_output();
495 assert!(tty.output().is_empty());
496 }
497
498 #[test]
499 fn test_events() {
500 let tty = MockTty::new(80, 24).with_events(vec![
501 Event::Key(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE)),
502 Event::Key(KeyEvent::new(KeyCode::Char('b'), KeyModifiers::NONE)),
503 ]);
504 assert_eq!(tty.queued_events(), 2);
505 }
506
507 #[test]
508 fn test_read_event() {
509 let mut tty = MockTty::new(80, 24).with_events(vec![Event::Key(KeyEvent::new(
510 KeyCode::Char('x'),
511 KeyModifiers::NONE,
512 ))]);
513 let event = tty.read_event().unwrap();
514 assert!(matches!(event, Event::Key(_)));
515 assert!(tty.read_event().is_err()); }
517
518 #[test]
519 fn test_poll() {
520 let mut tty = MockTty::new(80, 24).with_polls(vec![true, false, true]);
521 assert!(tty.poll(Duration::from_millis(100)).unwrap());
522 assert!(!tty.poll(Duration::from_millis(100)).unwrap());
523 assert!(tty.poll(Duration::from_millis(100)).unwrap());
524 assert!(!tty.poll(Duration::from_millis(100)).unwrap()); }
526
527 #[test]
528 fn test_push_event() {
529 let mut tty = MockTty::new(80, 24);
530 assert_eq!(tty.queued_events(), 0);
531 tty.push_event(Event::Key(KeyEvent::new(
532 KeyCode::Enter,
533 KeyModifiers::NONE,
534 )));
535 assert_eq!(tty.queued_events(), 1);
536 }
537
538 #[test]
539 fn test_push_poll() {
540 let mut tty = MockTty::new(80, 24);
541 tty.push_poll(true);
542 assert!(tty.poll(Duration::ZERO).unwrap());
543 }
544
545 #[test]
546 fn test_set_size() {
547 let mut tty = MockTty::new(80, 24);
548 tty.set_size(120, 40);
549 assert_eq!(tty.size(), (120, 40));
550 }
551
552 #[test]
553 fn test_contains_escape() {
554 let mut tty = MockTty::new(80, 24);
555 tty.write_all(b"\x1b[2J").unwrap(); assert!(tty.contains_escape("2J"));
557 assert!(!tty.contains_escape("0J"));
558 }
559
560 #[test]
561 fn test_parsed_commands_cursor_move() {
562 let mut tty = MockTty::new(80, 24);
563 tty.write_all(b"\x1b[10;20H").unwrap();
564 let commands = tty.parsed_commands();
565 assert_eq!(commands.len(), 1);
566 assert_eq!(commands[0], AnsiCommand::CursorMove { row: 10, col: 20 });
567 }
568
569 #[test]
570 fn test_parsed_commands_clear_screen() {
571 let mut tty = MockTty::new(80, 24);
572 tty.write_all(b"\x1b[2J").unwrap();
573 let commands = tty.parsed_commands();
574 assert_eq!(commands.len(), 1);
575 assert_eq!(commands[0], AnsiCommand::ClearScreen(ClearMode::All));
576 }
577
578 #[test]
579 fn test_parsed_commands_sgr() {
580 let mut tty = MockTty::new(80, 24);
581 tty.write_all(b"\x1b[1;31m").unwrap(); let commands = tty.parsed_commands();
583 assert_eq!(commands.len(), 1);
584 assert_eq!(commands[0], AnsiCommand::SetAttribute(vec![1, 31]));
585 }
586
587 #[test]
588 fn test_parsed_commands_text() {
589 let mut tty = MockTty::new(80, 24);
590 tty.write_all(b"Hello\x1b[2JWorld").unwrap();
591 let commands = tty.parsed_commands();
592 assert_eq!(commands.len(), 3);
593 assert_eq!(commands[0], AnsiCommand::Text("Hello".to_string()));
594 assert_eq!(commands[1], AnsiCommand::ClearScreen(ClearMode::All));
595 assert_eq!(commands[2], AnsiCommand::Text("World".to_string()));
596 }
597
598 #[test]
599 fn test_parsed_commands_alternate_screen() {
600 let mut tty = MockTty::new(80, 24);
601 tty.enter_alternate_screen();
602 tty.leave_alternate_screen();
603 let commands = tty.parsed_commands();
604 assert!(commands.contains(&AnsiCommand::EnterAlternateScreen));
605 assert!(commands.contains(&AnsiCommand::LeaveAlternateScreen));
606 }
607
608 #[test]
609 fn test_parsed_commands_cursor_visibility() {
610 let mut tty = MockTty::new(80, 24);
611 tty.hide_cursor();
612 tty.show_cursor();
613 let commands = tty.parsed_commands();
614 assert!(commands.contains(&AnsiCommand::HideCursor));
615 assert!(commands.contains(&AnsiCommand::ShowCursor));
616 }
617
618 #[test]
619 fn test_cursor_position_f_variant() {
620 let mut tty = MockTty::new(80, 24);
622 tty.write_all(b"\x1b[5;10f").unwrap();
623 let commands = tty.parsed_commands();
624 assert_eq!(commands.len(), 1);
625 assert_eq!(commands[0], AnsiCommand::CursorMove { row: 5, col: 10 });
626 }
627
628 #[test]
629 fn test_cursor_position_defaults() {
630 let mut tty = MockTty::new(80, 24);
632 tty.write_all(b"\x1b[H").unwrap();
633 let commands = tty.parsed_commands();
634 assert_eq!(commands.len(), 1);
635 assert_eq!(commands[0], AnsiCommand::CursorMove { row: 1, col: 1 });
636 }
637
638 #[test]
639 fn test_cursor_position_row_only() {
640 let mut tty = MockTty::new(80, 24);
642 tty.write_all(b"\x1b[15H").unwrap();
643 let commands = tty.parsed_commands();
644 assert_eq!(commands.len(), 1);
645 assert_eq!(commands[0], AnsiCommand::CursorMove { row: 15, col: 1 });
646 }
647
648 #[test]
649 fn test_clear_screen_modes() {
650 let mut tty = MockTty::new(80, 24);
651 tty.write_all(b"\x1b[J").unwrap();
653 tty.write_all(b"\x1b[0J").unwrap();
654 tty.write_all(b"\x1b[1J").unwrap();
656 tty.write_all(b"\x1b[2J").unwrap();
658 tty.write_all(b"\x1b[3J").unwrap();
659 tty.write_all(b"\x1b[9J").unwrap();
661
662 let commands = tty.parsed_commands();
663 assert_eq!(commands.len(), 6);
664 assert_eq!(commands[0], AnsiCommand::ClearScreen(ClearMode::ToEnd));
665 assert_eq!(commands[1], AnsiCommand::ClearScreen(ClearMode::ToEnd));
666 assert_eq!(
667 commands[2],
668 AnsiCommand::ClearScreen(ClearMode::ToBeginning)
669 );
670 assert_eq!(commands[3], AnsiCommand::ClearScreen(ClearMode::All));
671 assert_eq!(commands[4], AnsiCommand::ClearScreen(ClearMode::All));
672 assert_eq!(commands[5], AnsiCommand::ClearScreen(ClearMode::ToEnd));
673 }
674
675 #[test]
676 fn test_clear_line_modes() {
677 let mut tty = MockTty::new(80, 24);
678 tty.write_all(b"\x1b[K").unwrap();
680 tty.write_all(b"\x1b[0K").unwrap();
681 tty.write_all(b"\x1b[1K").unwrap();
683 tty.write_all(b"\x1b[2K").unwrap();
685 tty.write_all(b"\x1b[9K").unwrap();
687
688 let commands = tty.parsed_commands();
689 assert_eq!(commands.len(), 5);
690 assert_eq!(commands[0], AnsiCommand::ClearLine(ClearMode::ToEnd));
691 assert_eq!(commands[1], AnsiCommand::ClearLine(ClearMode::ToEnd));
692 assert_eq!(commands[2], AnsiCommand::ClearLine(ClearMode::ToBeginning));
693 assert_eq!(commands[3], AnsiCommand::ClearLine(ClearMode::All));
694 assert_eq!(commands[4], AnsiCommand::ClearLine(ClearMode::ToEnd));
695 }
696
697 #[test]
698 fn test_sgr_empty_params() {
699 let mut tty = MockTty::new(80, 24);
700 tty.write_all(b"\x1b[m").unwrap(); let commands = tty.parsed_commands();
702 assert_eq!(commands.len(), 1);
703 assert_eq!(commands[0], AnsiCommand::SetAttribute(vec![]));
704 }
705
706 #[test]
707 fn test_unknown_h_mode() {
708 let mut tty = MockTty::new(80, 24);
709 tty.write_all(b"\x1b[?9999h").unwrap();
710 let commands = tty.parsed_commands();
711 assert_eq!(commands.len(), 1);
712 match &commands[0] {
713 AnsiCommand::Unknown(bytes) => {
714 assert_eq!(bytes, b"\x1b[?9999h");
715 }
716 _ => panic!("Expected Unknown command"),
717 }
718 }
719
720 #[test]
721 fn test_unknown_l_mode() {
722 let mut tty = MockTty::new(80, 24);
723 tty.write_all(b"\x1b[?9999l").unwrap();
724 let commands = tty.parsed_commands();
725 assert_eq!(commands.len(), 1);
726 match &commands[0] {
727 AnsiCommand::Unknown(bytes) => {
728 assert_eq!(bytes, b"\x1b[?9999l");
729 }
730 _ => panic!("Expected Unknown command"),
731 }
732 }
733
734 #[test]
735 fn test_unknown_final_byte() {
736 let mut tty = MockTty::new(80, 24);
737 tty.write_all(b"\x1b[5Z").unwrap();
739 let commands = tty.parsed_commands();
740 assert_eq!(commands.len(), 1);
741 match &commands[0] {
742 AnsiCommand::Unknown(bytes) => {
743 assert_eq!(bytes, b"\x1b[5Z");
744 }
745 _ => panic!("Expected Unknown command"),
746 }
747 }
748
749 #[test]
750 fn test_mouse_enable_via_parsing() {
751 let mut tty = MockTty::new(80, 24);
752 tty.enable_mouse_capture();
753 let commands = tty.parsed_commands();
754 assert!(commands
756 .iter()
757 .any(|c| matches!(c, AnsiCommand::EnableMouse)));
758 }
759
760 #[test]
761 fn test_mouse_disable_via_parsing() {
762 let mut tty = MockTty::new(80, 24);
763 tty.disable_mouse_capture();
764 let commands = tty.parsed_commands();
765 assert!(commands
767 .iter()
768 .any(|c| matches!(c, AnsiCommand::DisableMouse)));
769 }
770
771 #[test]
772 fn test_mouse_1002_enable() {
773 let mut tty = MockTty::new(80, 24);
774 tty.write_all(b"\x1b[?1002h").unwrap();
775 let commands = tty.parsed_commands();
776 assert_eq!(commands.len(), 1);
777 assert_eq!(commands[0], AnsiCommand::EnableMouse);
778 }
779
780 #[test]
781 fn test_mouse_1000_disable() {
782 let mut tty = MockTty::new(80, 24);
783 tty.write_all(b"\x1b[?1000l").unwrap();
784 let commands = tty.parsed_commands();
785 assert_eq!(commands.len(), 1);
786 assert_eq!(commands[0], AnsiCommand::DisableMouse);
787 }
788
789 #[test]
790 fn test_incomplete_escape_sequence() {
791 let mut tty = MockTty::new(80, 24);
792 tty.write_all(b"text\x1b[123").unwrap();
794 let commands = tty.parsed_commands();
795 assert_eq!(commands.len(), 2);
797 assert_eq!(commands[0], AnsiCommand::Text("text".to_string()));
798 match &commands[1] {
799 AnsiCommand::Unknown(_) => {}
800 _ => panic!("Expected Unknown for incomplete sequence"),
801 }
802 }
803
804 #[test]
805 fn test_write_flush() {
806 let mut tty = MockTty::new(80, 24);
807 tty.write_all(b"test").unwrap();
808 assert!(tty.flush().is_ok());
810 }
811
812 #[test]
813 fn test_output_contains_empty_needle() {
814 let mut tty = MockTty::new(80, 24);
815 tty.write_all(b"Hello").unwrap();
816 assert!(!tty.output_contains(b""));
818 }
819
820 #[test]
821 fn test_intermediate_bytes_in_sequence() {
822 let mut tty = MockTty::new(80, 24);
824 tty.write_all(b"\x1b[0 q").unwrap(); let commands = tty.parsed_commands();
827 assert_eq!(commands.len(), 1);
828 match &commands[0] {
830 AnsiCommand::Unknown(_) => {}
831 _ => panic!("Expected Unknown command for DECSCUSR"),
832 }
833 }
834
835 #[test]
836 fn test_multiple_escape_sequences() {
837 let mut tty = MockTty::new(80, 24);
838 tty.write_all(b"\x1b[2J\x1b[1;1H\x1b[?25l").unwrap();
839 let commands = tty.parsed_commands();
840 assert_eq!(commands.len(), 3);
841 assert_eq!(commands[0], AnsiCommand::ClearScreen(ClearMode::All));
842 assert_eq!(commands[1], AnsiCommand::CursorMove { row: 1, col: 1 });
843 assert_eq!(commands[2], AnsiCommand::HideCursor);
844 }
845
846 #[test]
847 fn test_text_only_output() {
848 let mut tty = MockTty::new(80, 24);
849 tty.write_all(b"Just plain text").unwrap();
850 let commands = tty.parsed_commands();
851 assert_eq!(commands.len(), 1);
852 assert_eq!(
853 commands[0],
854 AnsiCommand::Text("Just plain text".to_string())
855 );
856 }
857
858 #[test]
859 fn test_escape_at_end() {
860 let mut tty = MockTty::new(80, 24);
861 tty.write_all(b"text\x1b").unwrap();
863 let commands = tty.parsed_commands();
864 assert_eq!(commands.len(), 1);
866 assert_eq!(commands[0], AnsiCommand::Text("text\x1b".to_string()));
867 }
868
869 #[test]
870 fn test_debug_impl() {
871 let tty = MockTty::new(80, 24);
872 let debug_str = format!("{:?}", tty);
873 assert!(debug_str.contains("MockTty"));
874 assert!(debug_str.contains("size"));
875 }
876
877 #[test]
878 fn test_ansi_command_debug_and_clone() {
879 let cmd = AnsiCommand::CursorMove { row: 5, col: 10 };
880 let cloned = cmd.clone();
881 assert_eq!(cmd, cloned);
882 let debug_str = format!("{:?}", cmd);
883 assert!(debug_str.contains("CursorMove"));
884 }
885
886 #[test]
887 fn test_clear_mode_debug_and_clone() {
888 let mode = ClearMode::All;
889 let cloned = mode;
890 assert_eq!(mode, cloned);
891 let debug_str = format!("{:?}", mode);
892 assert!(debug_str.contains("All"));
893 }
894
895 #[test]
896 fn test_empty_output_parsing() {
897 let tty = MockTty::new(80, 24);
898 let commands = tty.parsed_commands();
899 assert!(commands.is_empty());
900 }
901
902 #[test]
903 fn test_read_event_error_kind() {
904 let mut tty = MockTty::new(80, 24);
905 let err = tty.read_event().unwrap_err();
906 assert_eq!(err.kind(), io::ErrorKind::WouldBlock);
907 }
908}