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