1use std::{
5 fmt::{self, Write},
6 io, iter,
7};
8
9use unicode_width::UnicodeWidthStr;
10
11use crate::{
12 backend::{Backend, ClearType, WindowSize},
13 buffer::{Buffer, Cell},
14 layout::{Position, Rect, Size},
15};
16
17#[derive(Debug, Clone, Eq, PartialEq, Hash)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36pub struct TestBackend {
37 buffer: Buffer,
38 scrollback: Buffer,
39 cursor: bool,
40 pos: (u16, u16),
41}
42
43fn buffer_view(buffer: &Buffer) -> String {
50 let mut view = String::with_capacity(buffer.content.len() + buffer.area.height as usize * 3);
51 for cells in buffer.content.chunks(buffer.area.width as usize) {
52 let mut overwritten = vec![];
53 let mut skip: usize = 0;
54 view.push('"');
55 for (x, c) in cells.iter().enumerate() {
56 if skip == 0 {
57 view.push_str(c.symbol());
58 } else {
59 overwritten.push((x, c.symbol()));
60 }
61 skip = std::cmp::max(skip, c.symbol().width()).saturating_sub(1);
62 }
63 view.push('"');
64 if !overwritten.is_empty() {
65 write!(&mut view, " Hidden by multi-width symbols: {overwritten:?}").unwrap();
66 }
67 view.push('\n');
68 }
69 view
70}
71
72impl TestBackend {
73 pub fn new(width: u16, height: u16) -> Self {
75 Self {
76 buffer: Buffer::empty(Rect::new(0, 0, width, height)),
77 scrollback: Buffer::empty(Rect::new(0, 0, width, 0)),
78 cursor: false,
79 pos: (0, 0),
80 }
81 }
82
83 #[must_use]
87 pub fn with_lines<'line, Lines>(lines: Lines) -> Self
88 where
89 Lines: IntoIterator,
90 Lines::Item: Into<crate::text::Line<'line>>,
91 {
92 let buffer = Buffer::with_lines(lines);
93 let scrollback = Buffer::empty(Rect {
94 width: buffer.area.width,
95 ..Rect::ZERO
96 });
97 Self {
98 buffer,
99 scrollback,
100 cursor: false,
101 pos: (0, 0),
102 }
103 }
104
105 pub const fn buffer(&self) -> &Buffer {
107 &self.buffer
108 }
109
110 pub const fn scrollback(&self) -> &Buffer {
124 &self.scrollback
125 }
126
127 pub fn resize(&mut self, width: u16, height: u16) {
129 self.buffer.resize(Rect::new(0, 0, width, height));
130 let scrollback_height = self.scrollback.area.height;
131 self.scrollback
132 .resize(Rect::new(0, 0, width, scrollback_height));
133 }
134
135 #[allow(deprecated)]
144 #[track_caller]
145 pub fn assert_buffer(&self, expected: &Buffer) {
146 crate::assert_buffer_eq!(&self.buffer, expected);
148 }
149
150 #[track_caller]
159 pub fn assert_scrollback(&self, expected: &Buffer) {
160 assert_eq!(&self.scrollback, expected);
161 }
162
163 pub fn assert_scrollback_empty(&self) {
170 let expected = Buffer {
171 area: Rect {
172 width: self.scrollback.area.width,
173 ..Rect::ZERO
174 },
175 content: vec![],
176 };
177 self.assert_scrollback(&expected);
178 }
179
180 #[track_caller]
189 pub fn assert_buffer_lines<'line, Lines>(&self, expected: Lines)
190 where
191 Lines: IntoIterator,
192 Lines::Item: Into<crate::text::Line<'line>>,
193 {
194 self.assert_buffer(&Buffer::with_lines(expected));
195 }
196
197 #[track_caller]
206 pub fn assert_scrollback_lines<'line, Lines>(&self, expected: Lines)
207 where
208 Lines: IntoIterator,
209 Lines::Item: Into<crate::text::Line<'line>>,
210 {
211 self.assert_scrollback(&Buffer::with_lines(expected));
212 }
213
214 #[track_caller]
223 pub fn assert_cursor_position<P: Into<Position>>(&mut self, position: P) {
224 let actual = self.get_cursor_position().unwrap();
225 assert_eq!(actual, position.into());
226 }
227}
228
229impl fmt::Display for TestBackend {
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 write!(f, "{}", buffer_view(&self.buffer))
234 }
235}
236
237impl Backend for TestBackend {
238 fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
239 where
240 I: Iterator<Item = (u16, u16, &'a Cell)>,
241 {
242 for (x, y, c) in content {
243 self.buffer[(x, y)] = c.clone();
244 }
245 Ok(())
246 }
247
248 fn hide_cursor(&mut self) -> io::Result<()> {
249 self.cursor = false;
250 Ok(())
251 }
252
253 fn show_cursor(&mut self) -> io::Result<()> {
254 self.cursor = true;
255 Ok(())
256 }
257
258 fn get_cursor_position(&mut self) -> io::Result<Position> {
259 Ok(self.pos.into())
260 }
261
262 fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()> {
263 self.pos = position.into().into();
264 Ok(())
265 }
266
267 fn clear(&mut self) -> io::Result<()> {
268 self.buffer.reset();
269 Ok(())
270 }
271
272 fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
273 let region = match clear_type {
274 ClearType::All => return self.clear(),
275 ClearType::AfterCursor => {
276 let index = self.buffer.index_of(self.pos.0, self.pos.1) + 1;
277 &mut self.buffer.content[index..]
278 }
279 ClearType::BeforeCursor => {
280 let index = self.buffer.index_of(self.pos.0, self.pos.1);
281 &mut self.buffer.content[..index]
282 }
283 ClearType::CurrentLine => {
284 let line_start_index = self.buffer.index_of(0, self.pos.1);
285 let line_end_index = self.buffer.index_of(self.buffer.area.width - 1, self.pos.1);
286 &mut self.buffer.content[line_start_index..=line_end_index]
287 }
288 ClearType::UntilNewLine => {
289 let index = self.buffer.index_of(self.pos.0, self.pos.1);
290 let line_end_index = self.buffer.index_of(self.buffer.area.width - 1, self.pos.1);
291 &mut self.buffer.content[index..=line_end_index]
292 }
293 };
294 for cell in region {
295 cell.reset();
296 }
297 Ok(())
298 }
299
300 fn append_lines(&mut self, line_count: u16) -> io::Result<()> {
313 let Position { x: cur_x, y: cur_y } = self.get_cursor_position()?;
314 let Rect { width, height, .. } = self.buffer.area;
315
316 let new_cursor_x = cur_x.saturating_add(1).min(width.saturating_sub(1));
318
319 let max_y = height.saturating_sub(1);
320 let lines_after_cursor = max_y.saturating_sub(cur_y);
321
322 if line_count > lines_after_cursor {
323 let scroll_by: usize = (line_count - lines_after_cursor).into();
326 let width: usize = self.buffer.area.width.into();
327 let cells_to_scrollback = self.buffer.content.len().min(width * scroll_by);
328
329 append_to_scrollback(
330 &mut self.scrollback,
331 self.buffer.content.splice(
332 0..cells_to_scrollback,
333 iter::repeat_with(Default::default).take(cells_to_scrollback),
334 ),
335 );
336 self.buffer.content.rotate_left(cells_to_scrollback);
337 append_to_scrollback(
338 &mut self.scrollback,
339 iter::repeat_with(Default::default).take(width * scroll_by - cells_to_scrollback),
340 );
341 }
342
343 let new_cursor_y = cur_y.saturating_add(line_count).min(max_y);
344 self.set_cursor_position(Position::new(new_cursor_x, new_cursor_y))?;
345
346 Ok(())
347 }
348
349 fn size(&self) -> io::Result<Size> {
350 Ok(self.buffer.area.as_size())
351 }
352
353 fn window_size(&mut self) -> io::Result<WindowSize> {
354 const WINDOW_PIXEL_SIZE: Size = Size {
356 width: 640,
357 height: 480,
358 };
359 Ok(WindowSize {
360 columns_rows: self.buffer.area.as_size(),
361 pixels: WINDOW_PIXEL_SIZE,
362 })
363 }
364
365 fn flush(&mut self) -> io::Result<()> {
366 Ok(())
367 }
368
369 #[cfg(feature = "scrolling-regions")]
370 fn scroll_region_up(&mut self, region: std::ops::Range<u16>, scroll_by: u16) -> io::Result<()> {
371 let width: usize = self.buffer.area.width.into();
372 let cell_region_start = width * region.start.min(self.buffer.area.height) as usize;
373 let cell_region_end = width * region.end.min(self.buffer.area.height) as usize;
374 let cell_region_len = cell_region_end - cell_region_start;
375 let cells_to_scroll_by = width * scroll_by as usize;
376
377 if cell_region_start > 0 {
379 if cells_to_scroll_by >= cell_region_len {
380 self.buffer.content[cell_region_start..cell_region_end].fill_with(Default::default);
382 } else {
383 self.buffer.content[cell_region_start..cell_region_end]
385 .rotate_left(cells_to_scroll_by);
386 self.buffer.content[cell_region_end - cells_to_scroll_by..cell_region_end]
387 .fill_with(Default::default);
388 }
389 return Ok(());
390 }
391
392 let cells_from_region = cell_region_len.min(cells_to_scroll_by);
395 append_to_scrollback(
396 &mut self.scrollback,
397 self.buffer.content.splice(
398 0..cells_from_region,
399 iter::repeat_with(Default::default).take(cells_from_region),
400 ),
401 );
402 if cells_to_scroll_by < cell_region_len {
403 self.buffer.content[cell_region_start..cell_region_end].rotate_left(cells_from_region);
405 } else {
406 append_to_scrollback(
408 &mut self.scrollback,
409 iter::repeat_with(Default::default).take(cells_to_scroll_by - cell_region_len),
410 );
411 }
412 Ok(())
413 }
414
415 #[cfg(feature = "scrolling-regions")]
416 fn scroll_region_down(
417 &mut self,
418 region: std::ops::Range<u16>,
419 scroll_by: u16,
420 ) -> io::Result<()> {
421 let width: usize = self.buffer.area.width.into();
422 let cell_region_start = width * region.start.min(self.buffer.area.height) as usize;
423 let cell_region_end = width * region.end.min(self.buffer.area.height) as usize;
424 let cell_region_len = cell_region_end - cell_region_start;
425 let cells_to_scroll_by = width * scroll_by as usize;
426
427 if cells_to_scroll_by >= cell_region_len {
428 self.buffer.content[cell_region_start..cell_region_end].fill_with(Default::default);
430 } else {
431 self.buffer.content[cell_region_start..cell_region_end]
433 .rotate_right(cells_to_scroll_by);
434 self.buffer.content[cell_region_start..cell_region_start + cells_to_scroll_by]
435 .fill_with(Default::default);
436 }
437 Ok(())
438 }
439}
440
441fn append_to_scrollback(scrollback: &mut Buffer, cells: impl IntoIterator<Item = Cell>) {
445 scrollback.content.extend(cells);
446 let width = scrollback.area.width as usize;
447 let new_height = (scrollback.content.len() / width).min(u16::MAX as usize);
448 let keep_from = scrollback
449 .content
450 .len()
451 .saturating_sub(width * u16::MAX as usize);
452 scrollback.content.drain(0..keep_from);
453 scrollback.area.height = new_height as u16;
454}
455
456#[cfg(test)]
457mod tests {
458 use itertools::Itertools as _;
459
460 use super::*;
461
462 #[test]
463 fn new() {
464 assert_eq!(
465 TestBackend::new(10, 2),
466 TestBackend {
467 buffer: Buffer::with_lines([" "; 2]),
468 scrollback: Buffer::empty(Rect::new(0, 0, 10, 0)),
469 cursor: false,
470 pos: (0, 0),
471 }
472 );
473 }
474 #[test]
475 fn test_buffer_view() {
476 let buffer = Buffer::with_lines(["aaaa"; 2]);
477 assert_eq!(buffer_view(&buffer), "\"aaaa\"\n\"aaaa\"\n");
478 }
479
480 #[test]
481 fn buffer_view_with_overwrites() {
482 let multi_byte_char = "๐จโ๐ฉโ๐งโ๐ฆ"; let buffer = Buffer::with_lines([multi_byte_char]);
484 assert_eq!(
485 buffer_view(&buffer),
486 format!(
487 r#""{multi_byte_char}" Hidden by multi-width symbols: [(1, " ")]
488"#,
489 )
490 );
491 }
492
493 #[test]
494 fn buffer() {
495 let backend = TestBackend::new(10, 2);
496 backend.assert_buffer_lines([" "; 2]);
497 }
498
499 #[test]
500 fn resize() {
501 let mut backend = TestBackend::new(10, 2);
502 backend.resize(5, 5);
503 backend.assert_buffer_lines([" "; 5]);
504 }
505
506 #[test]
507 fn assert_buffer() {
508 let backend = TestBackend::new(10, 2);
509 backend.assert_buffer_lines([" "; 2]);
510 }
511
512 #[test]
513 #[should_panic = "buffer contents not equal"]
514 fn assert_buffer_panics() {
515 let backend = TestBackend::new(10, 2);
516 backend.assert_buffer_lines(["aaaaaaaaaa"; 2]);
517 }
518
519 #[test]
520 #[should_panic = "assertion `left == right` failed"]
521 fn assert_scrollback_panics() {
522 let backend = TestBackend::new(10, 2);
523 backend.assert_scrollback_lines(["aaaaaaaaaa"; 2]);
524 }
525
526 #[test]
527 fn display() {
528 let backend = TestBackend::new(10, 2);
529 assert_eq!(format!("{backend}"), "\" \"\n\" \"\n");
530 }
531
532 #[test]
533 fn draw() {
534 let mut backend = TestBackend::new(10, 2);
535 let cell = Cell::new("a");
536 backend.draw([(0, 0, &cell)].into_iter()).unwrap();
537 backend.draw([(0, 1, &cell)].into_iter()).unwrap();
538 backend.assert_buffer_lines(["a "; 2]);
539 }
540
541 #[test]
542 fn hide_cursor() {
543 let mut backend = TestBackend::new(10, 2);
544 backend.hide_cursor().unwrap();
545 assert!(!backend.cursor);
546 }
547
548 #[test]
549 fn show_cursor() {
550 let mut backend = TestBackend::new(10, 2);
551 backend.show_cursor().unwrap();
552 assert!(backend.cursor);
553 }
554
555 #[test]
556 fn get_cursor_position() {
557 let mut backend = TestBackend::new(10, 2);
558 assert_eq!(backend.get_cursor_position().unwrap(), Position::ORIGIN);
559 }
560
561 #[test]
562 fn assert_cursor_position() {
563 let mut backend = TestBackend::new(10, 2);
564 backend.assert_cursor_position(Position::ORIGIN);
565 }
566
567 #[test]
568 fn set_cursor_position() {
569 let mut backend = TestBackend::new(10, 10);
570 backend
571 .set_cursor_position(Position { x: 5, y: 5 })
572 .unwrap();
573 assert_eq!(backend.pos, (5, 5));
574 }
575
576 #[test]
577 fn clear() {
578 let mut backend = TestBackend::new(4, 2);
579 let cell = Cell::new("a");
580 backend.draw([(0, 0, &cell)].into_iter()).unwrap();
581 backend.draw([(0, 1, &cell)].into_iter()).unwrap();
582 backend.clear().unwrap();
583 backend.assert_buffer_lines([" ", " "]);
584 }
585
586 #[test]
587 fn clear_region_all() {
588 let mut backend = TestBackend::with_lines([
589 "aaaaaaaaaa",
590 "aaaaaaaaaa",
591 "aaaaaaaaaa",
592 "aaaaaaaaaa",
593 "aaaaaaaaaa",
594 ]);
595
596 backend.clear_region(ClearType::All).unwrap();
597 backend.assert_buffer_lines([
598 " ",
599 " ",
600 " ",
601 " ",
602 " ",
603 ]);
604 }
605
606 #[test]
607 fn clear_region_after_cursor() {
608 let mut backend = TestBackend::with_lines([
609 "aaaaaaaaaa",
610 "aaaaaaaaaa",
611 "aaaaaaaaaa",
612 "aaaaaaaaaa",
613 "aaaaaaaaaa",
614 ]);
615
616 backend
617 .set_cursor_position(Position { x: 3, y: 2 })
618 .unwrap();
619 backend.clear_region(ClearType::AfterCursor).unwrap();
620 backend.assert_buffer_lines([
621 "aaaaaaaaaa",
622 "aaaaaaaaaa",
623 "aaaa ",
624 " ",
625 " ",
626 ]);
627 }
628
629 #[test]
630 fn clear_region_before_cursor() {
631 let mut backend = TestBackend::with_lines([
632 "aaaaaaaaaa",
633 "aaaaaaaaaa",
634 "aaaaaaaaaa",
635 "aaaaaaaaaa",
636 "aaaaaaaaaa",
637 ]);
638
639 backend
640 .set_cursor_position(Position { x: 5, y: 3 })
641 .unwrap();
642 backend.clear_region(ClearType::BeforeCursor).unwrap();
643 backend.assert_buffer_lines([
644 " ",
645 " ",
646 " ",
647 " aaaaa",
648 "aaaaaaaaaa",
649 ]);
650 }
651
652 #[test]
653 fn clear_region_current_line() {
654 let mut backend = TestBackend::with_lines([
655 "aaaaaaaaaa",
656 "aaaaaaaaaa",
657 "aaaaaaaaaa",
658 "aaaaaaaaaa",
659 "aaaaaaaaaa",
660 ]);
661
662 backend
663 .set_cursor_position(Position { x: 3, y: 1 })
664 .unwrap();
665 backend.clear_region(ClearType::CurrentLine).unwrap();
666 backend.assert_buffer_lines([
667 "aaaaaaaaaa",
668 " ",
669 "aaaaaaaaaa",
670 "aaaaaaaaaa",
671 "aaaaaaaaaa",
672 ]);
673 }
674
675 #[test]
676 fn clear_region_until_new_line() {
677 let mut backend = TestBackend::with_lines([
678 "aaaaaaaaaa",
679 "aaaaaaaaaa",
680 "aaaaaaaaaa",
681 "aaaaaaaaaa",
682 "aaaaaaaaaa",
683 ]);
684
685 backend
686 .set_cursor_position(Position { x: 3, y: 0 })
687 .unwrap();
688 backend.clear_region(ClearType::UntilNewLine).unwrap();
689 backend.assert_buffer_lines([
690 "aaa ",
691 "aaaaaaaaaa",
692 "aaaaaaaaaa",
693 "aaaaaaaaaa",
694 "aaaaaaaaaa",
695 ]);
696 }
697
698 #[test]
699 fn append_lines_not_at_last_line() {
700 let mut backend = TestBackend::with_lines([
701 "aaaaaaaaaa",
702 "bbbbbbbbbb",
703 "cccccccccc",
704 "dddddddddd",
705 "eeeeeeeeee",
706 ]);
707
708 backend.set_cursor_position(Position::ORIGIN).unwrap();
709
710 backend.append_lines(1).unwrap();
714 backend.assert_cursor_position(Position { x: 1, y: 1 });
715
716 backend.append_lines(1).unwrap();
717 backend.assert_cursor_position(Position { x: 2, y: 2 });
718
719 backend.append_lines(1).unwrap();
720 backend.assert_cursor_position(Position { x: 3, y: 3 });
721
722 backend.append_lines(1).unwrap();
723 backend.assert_cursor_position(Position { x: 4, y: 4 });
724
725 backend.assert_buffer_lines([
727 "aaaaaaaaaa",
728 "bbbbbbbbbb",
729 "cccccccccc",
730 "dddddddddd",
731 "eeeeeeeeee",
732 ]);
733 backend.assert_scrollback_empty();
734 }
735
736 #[test]
737 fn append_lines_at_last_line() {
738 let mut backend = TestBackend::with_lines([
739 "aaaaaaaaaa",
740 "bbbbbbbbbb",
741 "cccccccccc",
742 "dddddddddd",
743 "eeeeeeeeee",
744 ]);
745
746 backend
749 .set_cursor_position(Position { x: 0, y: 4 })
750 .unwrap();
751
752 backend.append_lines(1).unwrap();
753
754 backend.assert_buffer_lines([
755 "bbbbbbbbbb",
756 "cccccccccc",
757 "dddddddddd",
758 "eeeeeeeeee",
759 " ",
760 ]);
761 backend.assert_scrollback_lines(["aaaaaaaaaa"]);
762
763 backend.assert_cursor_position(Position { x: 1, y: 4 });
766 }
767
768 #[test]
769 fn append_multiple_lines_not_at_last_line() {
770 let mut backend = TestBackend::with_lines([
771 "aaaaaaaaaa",
772 "bbbbbbbbbb",
773 "cccccccccc",
774 "dddddddddd",
775 "eeeeeeeeee",
776 ]);
777
778 backend.set_cursor_position(Position::ORIGIN).unwrap();
779
780 backend.append_lines(4).unwrap();
784 backend.assert_cursor_position(Position { x: 1, y: 4 });
785
786 backend.assert_buffer_lines([
788 "aaaaaaaaaa",
789 "bbbbbbbbbb",
790 "cccccccccc",
791 "dddddddddd",
792 "eeeeeeeeee",
793 ]);
794 backend.assert_scrollback_empty();
795 }
796
797 #[test]
798 fn append_multiple_lines_past_last_line() {
799 let mut backend = TestBackend::with_lines([
800 "aaaaaaaaaa",
801 "bbbbbbbbbb",
802 "cccccccccc",
803 "dddddddddd",
804 "eeeeeeeeee",
805 ]);
806
807 backend
808 .set_cursor_position(Position { x: 0, y: 3 })
809 .unwrap();
810
811 backend.append_lines(3).unwrap();
812 backend.assert_cursor_position(Position { x: 1, y: 4 });
813
814 backend.assert_buffer_lines([
815 "cccccccccc",
816 "dddddddddd",
817 "eeeeeeeeee",
818 " ",
819 " ",
820 ]);
821 backend.assert_scrollback_lines(["aaaaaaaaaa", "bbbbbbbbbb"]);
822 }
823
824 #[test]
825 fn append_multiple_lines_where_cursor_at_end_appends_height_lines() {
826 let mut backend = TestBackend::with_lines([
827 "aaaaaaaaaa",
828 "bbbbbbbbbb",
829 "cccccccccc",
830 "dddddddddd",
831 "eeeeeeeeee",
832 ]);
833
834 backend
835 .set_cursor_position(Position { x: 0, y: 4 })
836 .unwrap();
837
838 backend.append_lines(5).unwrap();
839 backend.assert_cursor_position(Position { x: 1, y: 4 });
840
841 backend.assert_buffer_lines([
842 " ",
843 " ",
844 " ",
845 " ",
846 " ",
847 ]);
848 backend.assert_scrollback_lines([
849 "aaaaaaaaaa",
850 "bbbbbbbbbb",
851 "cccccccccc",
852 "dddddddddd",
853 "eeeeeeeeee",
854 ]);
855 }
856
857 #[test]
858 fn append_multiple_lines_where_cursor_appends_height_lines() {
859 let mut backend = TestBackend::with_lines([
860 "aaaaaaaaaa",
861 "bbbbbbbbbb",
862 "cccccccccc",
863 "dddddddddd",
864 "eeeeeeeeee",
865 ]);
866
867 backend.set_cursor_position(Position::ORIGIN).unwrap();
868
869 backend.append_lines(5).unwrap();
870 backend.assert_cursor_position(Position { x: 1, y: 4 });
871
872 backend.assert_buffer_lines([
873 "bbbbbbbbbb",
874 "cccccccccc",
875 "dddddddddd",
876 "eeeeeeeeee",
877 " ",
878 ]);
879 backend.assert_scrollback_lines(["aaaaaaaaaa"]);
880 }
881
882 #[test]
883 fn append_multiple_lines_where_cursor_at_end_appends_more_than_height_lines() {
884 let mut backend = TestBackend::with_lines([
885 "aaaaaaaaaa",
886 "bbbbbbbbbb",
887 "cccccccccc",
888 "dddddddddd",
889 "eeeeeeeeee",
890 ]);
891
892 backend
893 .set_cursor_position(Position { x: 0, y: 4 })
894 .unwrap();
895
896 backend.append_lines(8).unwrap();
897 backend.assert_cursor_position(Position { x: 1, y: 4 });
898
899 backend.assert_buffer_lines([
900 " ",
901 " ",
902 " ",
903 " ",
904 " ",
905 ]);
906 backend.assert_scrollback_lines([
907 "aaaaaaaaaa",
908 "bbbbbbbbbb",
909 "cccccccccc",
910 "dddddddddd",
911 "eeeeeeeeee",
912 " ",
913 " ",
914 " ",
915 ]);
916 }
917
918 #[test]
919 fn append_lines_truncates_beyond_u16_max() -> io::Result<()> {
920 let mut backend = TestBackend::new(10, 5);
921
922 let row_count = u16::MAX as usize + 10;
924 for row in 0..=row_count {
925 if row > 4 {
926 backend.set_cursor_position(Position { x: 0, y: 4 })?;
927 backend.append_lines(1)?;
928 }
929 let cells = format!("{row:>10}").chars().map(Cell::from).collect_vec();
930 let content = cells
931 .iter()
932 .enumerate()
933 .map(|(column, cell)| (column as u16, 4.min(row) as u16, cell));
934 backend.draw(content)?;
935 }
936
937 backend.assert_buffer_lines([
939 " 65541",
940 " 65542",
941 " 65543",
942 " 65544",
943 " 65545",
944 ]);
945
946 assert_eq!(
951 Buffer {
952 area: Rect::new(0, 0, 10, 5),
953 content: backend.scrollback.content[0..10 * 5].to_vec(),
954 },
955 Buffer::with_lines([
956 " 6",
957 " 7",
958 " 8",
959 " 9",
960 " 10",
961 ]),
962 "first 5 lines of scrollback should have been truncated"
963 );
964
965 assert_eq!(
966 Buffer {
967 area: Rect::new(0, 0, 10, 5),
968 content: backend.scrollback.content[10 * 65530..10 * 65535].to_vec(),
969 },
970 Buffer::with_lines([
971 " 65536",
972 " 65537",
973 " 65538",
974 " 65539",
975 " 65540",
976 ]),
977 "last 5 lines of scrollback should have been appended"
978 );
979
980 assert_eq!(backend.scrollback.area.width, 10);
984 assert_eq!(backend.scrollback.area.height, 65535);
985 assert_eq!(backend.scrollback.content.len(), 10 * 65535);
986 Ok(())
987 }
988
989 #[test]
990 fn size() {
991 let backend = TestBackend::new(10, 2);
992 assert_eq!(backend.size().unwrap(), Size::new(10, 2));
993 }
994
995 #[test]
996 fn flush() {
997 let mut backend = TestBackend::new(10, 2);
998 backend.flush().unwrap();
999 }
1000
1001 #[cfg(feature = "scrolling-regions")]
1002 mod scrolling_regions {
1003 use rstest::rstest;
1004
1005 use super::*;
1006
1007 const A: &str = "aaaa";
1008 const B: &str = "bbbb";
1009 const C: &str = "cccc";
1010 const D: &str = "dddd";
1011 const E: &str = "eeee";
1012 const S: &str = " ";
1013
1014 #[rstest]
1015 #[case([A, B, C, D, E], 0..5, 0, [], [A, B, C, D, E])]
1016 #[case([A, B, C, D, E], 0..5, 2, [A, B], [C, D, E, S, S])]
1017 #[case([A, B, C, D, E], 0..5, 5, [A, B, C, D, E], [S, S, S, S, S])]
1018 #[case([A, B, C, D, E], 0..5, 7, [A, B, C, D, E, S, S], [S, S, S, S, S])]
1019 #[case([A, B, C, D, E], 0..3, 0, [], [A, B, C, D, E])]
1020 #[case([A, B, C, D, E], 0..3, 2, [A, B], [C, S, S, D, E])]
1021 #[case([A, B, C, D, E], 0..3, 3, [A, B, C], [S, S, S, D, E])]
1022 #[case([A, B, C, D, E], 0..3, 4, [A, B, C, S], [S, S, S, D, E])]
1023 #[case([A, B, C, D, E], 1..4, 0, [], [A, B, C, D, E])]
1024 #[case([A, B, C, D, E], 1..4, 2, [], [A, D, S, S, E])]
1025 #[case([A, B, C, D, E], 1..4, 3, [], [A, S, S, S, E])]
1026 #[case([A, B, C, D, E], 1..4, 4, [], [A, S, S, S, E])]
1027 #[case([A, B, C, D, E], 0..0, 0, [], [A, B, C, D, E])]
1028 #[case([A, B, C, D, E], 0..0, 2, [S, S], [A, B, C, D, E])]
1029 #[case([A, B, C, D, E], 2..2, 0, [], [A, B, C, D, E])]
1030 #[case([A, B, C, D, E], 2..2, 2, [], [A, B, C, D, E])]
1031 fn scroll_region_up<const L: usize, const M: usize, const N: usize>(
1032 #[case] initial_screen: [&'static str; L],
1033 #[case] range: std::ops::Range<u16>,
1034 #[case] scroll_by: u16,
1035 #[case] expected_scrollback: [&'static str; M],
1036 #[case] expected_buffer: [&'static str; N],
1037 ) {
1038 let mut backend = TestBackend::with_lines(initial_screen);
1039 backend.scroll_region_up(range, scroll_by).unwrap();
1040 if expected_scrollback.is_empty() {
1041 backend.assert_scrollback_empty();
1042 } else {
1043 backend.assert_scrollback_lines(expected_scrollback);
1044 }
1045 backend.assert_buffer_lines(expected_buffer);
1046 }
1047
1048 #[rstest]
1049 #[case([A, B, C, D, E], 0..5, 0, [A, B, C, D, E])]
1050 #[case([A, B, C, D, E], 0..5, 2, [S, S, A, B, C])]
1051 #[case([A, B, C, D, E], 0..5, 5, [S, S, S, S, S])]
1052 #[case([A, B, C, D, E], 0..5, 7, [S, S, S, S, S])]
1053 #[case([A, B, C, D, E], 0..3, 0, [A, B, C, D, E])]
1054 #[case([A, B, C, D, E], 0..3, 2, [S, S, A, D, E])]
1055 #[case([A, B, C, D, E], 0..3, 3, [S, S, S, D, E])]
1056 #[case([A, B, C, D, E], 0..3, 4, [S, S, S, D, E])]
1057 #[case([A, B, C, D, E], 1..4, 0, [A, B, C, D, E])]
1058 #[case([A, B, C, D, E], 1..4, 2, [A, S, S, B, E])]
1059 #[case([A, B, C, D, E], 1..4, 3, [A, S, S, S, E])]
1060 #[case([A, B, C, D, E], 1..4, 4, [A, S, S, S, E])]
1061 #[case([A, B, C, D, E], 0..0, 0, [A, B, C, D, E])]
1062 #[case([A, B, C, D, E], 0..0, 2, [A, B, C, D, E])]
1063 #[case([A, B, C, D, E], 2..2, 0, [A, B, C, D, E])]
1064 #[case([A, B, C, D, E], 2..2, 2, [A, B, C, D, E])]
1065 fn scroll_region_down<const M: usize, const N: usize>(
1066 #[case] initial_screen: [&'static str; M],
1067 #[case] range: std::ops::Range<u16>,
1068 #[case] scroll_by: u16,
1069 #[case] expected_buffer: [&'static str; N],
1070 ) {
1071 let mut backend = TestBackend::with_lines(initial_screen);
1072 backend.scroll_region_down(range, scroll_by).unwrap();
1073 backend.assert_scrollback_empty();
1074 backend.assert_buffer_lines(expected_buffer);
1075 }
1076 }
1077}