1use alloc::string::String;
5use alloc::vec;
6use core::fmt::{self, Write};
7use core::iter;
8
9use unicode_width::UnicodeWidthStr;
10
11use crate::backend::{Backend, ClearType, WindowSize};
12use crate::buffer::{Buffer, Cell};
13use crate::layout::{Position, Rect, Size};
14
15#[derive(Debug, Clone, Eq, PartialEq, Hash)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34pub struct TestBackend {
35 buffer: Buffer,
36 scrollback: Buffer,
37 cursor: bool,
38 pos: (u16, u16),
39}
40
41fn buffer_view(buffer: &Buffer) -> String {
48 let mut view = String::with_capacity(buffer.content.len() + buffer.area.height as usize * 3);
49 for cells in buffer.content.chunks(buffer.area.width as usize) {
50 let mut overwritten = vec![];
51 let mut skip: usize = 0;
52 view.push('"');
53 for (x, c) in cells.iter().enumerate() {
54 if skip == 0 {
55 view.push_str(c.symbol());
56 } else {
57 overwritten.push((x, c.symbol()));
58 }
59 skip = core::cmp::max(skip, c.symbol().width()).saturating_sub(1);
60 }
61 view.push('"');
62 if !overwritten.is_empty() {
63 write!(&mut view, " Hidden by multi-width symbols: {overwritten:?}").unwrap();
64 }
65 view.push('\n');
66 }
67 view
68}
69
70impl TestBackend {
71 pub fn new(width: u16, height: u16) -> Self {
73 Self {
74 buffer: Buffer::empty(Rect::new(0, 0, width, height)),
75 scrollback: Buffer::empty(Rect::new(0, 0, width, 0)),
76 cursor: false,
77 pos: (0, 0),
78 }
79 }
80
81 #[must_use]
85 pub fn with_lines<'line, Lines>(lines: Lines) -> Self
86 where
87 Lines: IntoIterator,
88 Lines::Item: Into<crate::text::Line<'line>>,
89 {
90 let buffer = Buffer::with_lines(lines);
91 let scrollback = Buffer::empty(Rect {
92 width: buffer.area.width,
93 ..Rect::ZERO
94 });
95 Self {
96 buffer,
97 scrollback,
98 cursor: false,
99 pos: (0, 0),
100 }
101 }
102
103 pub const fn buffer(&self) -> &Buffer {
105 &self.buffer
106 }
107
108 pub const fn scrollback(&self) -> &Buffer {
122 &self.scrollback
123 }
124
125 pub fn resize(&mut self, width: u16, height: u16) {
127 self.buffer.resize(Rect::new(0, 0, width, height));
128 let scrollback_height = self.scrollback.area.height;
129 self.scrollback
130 .resize(Rect::new(0, 0, width, scrollback_height));
131 }
132
133 #[expect(deprecated)]
142 #[track_caller]
143 pub fn assert_buffer(&self, expected: &Buffer) {
144 crate::assert_buffer_eq!(&self.buffer, expected);
146 }
147
148 #[track_caller]
157 pub fn assert_scrollback(&self, expected: &Buffer) {
158 assert_eq!(&self.scrollback, expected);
159 }
160
161 pub fn assert_scrollback_empty(&self) {
168 let expected = Buffer {
169 area: Rect {
170 width: self.scrollback.area.width,
171 ..Rect::ZERO
172 },
173 content: vec![],
174 };
175 self.assert_scrollback(&expected);
176 }
177
178 #[track_caller]
187 pub fn assert_buffer_lines<'line, Lines>(&self, expected: Lines)
188 where
189 Lines: IntoIterator,
190 Lines::Item: Into<crate::text::Line<'line>>,
191 {
192 self.assert_buffer(&Buffer::with_lines(expected));
193 }
194
195 #[track_caller]
204 pub fn assert_scrollback_lines<'line, Lines>(&self, expected: Lines)
205 where
206 Lines: IntoIterator,
207 Lines::Item: Into<crate::text::Line<'line>>,
208 {
209 self.assert_scrollback(&Buffer::with_lines(expected));
210 }
211
212 #[track_caller]
221 pub fn assert_cursor_position<P: Into<Position>>(&mut self, position: P) {
222 let actual = self.get_cursor_position().unwrap();
223 assert_eq!(actual, position.into());
224 }
225}
226
227impl fmt::Display for TestBackend {
228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 write!(f, "{}", buffer_view(&self.buffer))
232 }
233}
234
235type Result<T, E = core::convert::Infallible> = core::result::Result<T, E>;
236
237impl Backend for TestBackend {
238 type Error = core::convert::Infallible;
239
240 fn draw<'a, I>(&mut self, content: I) -> Result<()>
241 where
242 I: Iterator<Item = (u16, u16, &'a Cell)>,
243 {
244 for (x, y, c) in content {
245 self.buffer[(x, y)] = c.clone();
246 }
247 Ok(())
248 }
249
250 fn hide_cursor(&mut self) -> Result<()> {
251 self.cursor = false;
252 Ok(())
253 }
254
255 fn show_cursor(&mut self) -> Result<()> {
256 self.cursor = true;
257 Ok(())
258 }
259
260 fn get_cursor_position(&mut self) -> Result<Position> {
261 Ok(self.pos.into())
262 }
263
264 fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> Result<()> {
265 self.pos = position.into().into();
266 Ok(())
267 }
268
269 fn clear(&mut self) -> Result<()> {
270 self.buffer.reset();
271 Ok(())
272 }
273
274 fn clear_region(&mut self, clear_type: ClearType) -> Result<()> {
275 let region = match clear_type {
276 ClearType::All => return self.clear(),
277 ClearType::AfterCursor => {
278 let index = self.buffer.index_of(self.pos.0, self.pos.1) + 1;
279 &mut self.buffer.content[index..]
280 }
281 ClearType::BeforeCursor => {
282 let index = self.buffer.index_of(self.pos.0, self.pos.1);
283 &mut self.buffer.content[..index]
284 }
285 ClearType::CurrentLine => {
286 let line_start_index = self.buffer.index_of(0, self.pos.1);
287 let line_end_index = self.buffer.index_of(self.buffer.area.width - 1, self.pos.1);
288 &mut self.buffer.content[line_start_index..=line_end_index]
289 }
290 ClearType::UntilNewLine => {
291 let index = self.buffer.index_of(self.pos.0, self.pos.1);
292 let line_end_index = self.buffer.index_of(self.buffer.area.width - 1, self.pos.1);
293 &mut self.buffer.content[index..=line_end_index]
294 }
295 };
296 for cell in region {
297 cell.reset();
298 }
299 Ok(())
300 }
301
302 fn append_lines(&mut self, line_count: u16) -> Result<()> {
315 let Position { x: cur_x, y: cur_y } = self.get_cursor_position()?;
316 let Rect { width, height, .. } = self.buffer.area;
317
318 let new_cursor_x = cur_x.saturating_add(1).min(width.saturating_sub(1));
320
321 let max_y = height.saturating_sub(1);
322 let lines_after_cursor = max_y.saturating_sub(cur_y);
323
324 if line_count > lines_after_cursor {
325 let scroll_by: usize = (line_count - lines_after_cursor).into();
328 let width: usize = self.buffer.area.width.into();
329 let cells_to_scrollback = self.buffer.content.len().min(width * scroll_by);
330
331 append_to_scrollback(
332 &mut self.scrollback,
333 self.buffer.content.splice(
334 0..cells_to_scrollback,
335 iter::repeat_with(Default::default).take(cells_to_scrollback),
336 ),
337 );
338 self.buffer.content.rotate_left(cells_to_scrollback);
339 append_to_scrollback(
340 &mut self.scrollback,
341 iter::repeat_with(Default::default).take(width * scroll_by - cells_to_scrollback),
342 );
343 }
344
345 let new_cursor_y = cur_y.saturating_add(line_count).min(max_y);
346 self.set_cursor_position(Position::new(new_cursor_x, new_cursor_y))?;
347
348 Ok(())
349 }
350
351 fn size(&self) -> Result<Size> {
352 Ok(self.buffer.area.as_size())
353 }
354
355 fn window_size(&mut self) -> Result<WindowSize> {
356 const WINDOW_PIXEL_SIZE: Size = Size {
358 width: 640,
359 height: 480,
360 };
361 Ok(WindowSize {
362 columns_rows: self.buffer.area.as_size(),
363 pixels: WINDOW_PIXEL_SIZE,
364 })
365 }
366
367 fn flush(&mut self) -> Result<()> {
368 Ok(())
369 }
370
371 #[cfg(feature = "scrolling-regions")]
372 fn scroll_region_up(&mut self, region: core::ops::Range<u16>, scroll_by: u16) -> Result<()> {
373 let width: usize = self.buffer.area.width.into();
374 let cell_region_start = width * region.start.min(self.buffer.area.height) as usize;
375 let cell_region_end = width * region.end.min(self.buffer.area.height) as usize;
376 let cell_region_len = cell_region_end - cell_region_start;
377 let cells_to_scroll_by = width * scroll_by as usize;
378
379 if cell_region_start > 0 {
381 if cells_to_scroll_by >= cell_region_len {
382 self.buffer.content[cell_region_start..cell_region_end].fill_with(Default::default);
384 } else {
385 self.buffer.content[cell_region_start..cell_region_end]
387 .rotate_left(cells_to_scroll_by);
388 self.buffer.content[cell_region_end - cells_to_scroll_by..cell_region_end]
389 .fill_with(Default::default);
390 }
391 return Ok(());
392 }
393
394 let cells_from_region = cell_region_len.min(cells_to_scroll_by);
397 append_to_scrollback(
398 &mut self.scrollback,
399 self.buffer.content.splice(
400 0..cells_from_region,
401 iter::repeat_with(Default::default).take(cells_from_region),
402 ),
403 );
404 if cells_to_scroll_by < cell_region_len {
405 self.buffer.content[cell_region_start..cell_region_end].rotate_left(cells_from_region);
407 } else {
408 append_to_scrollback(
410 &mut self.scrollback,
411 iter::repeat_with(Default::default).take(cells_to_scroll_by - cell_region_len),
412 );
413 }
414 Ok(())
415 }
416
417 #[cfg(feature = "scrolling-regions")]
418 fn scroll_region_down(&mut self, region: core::ops::Range<u16>, scroll_by: u16) -> Result<()> {
419 let width: usize = self.buffer.area.width.into();
420 let cell_region_start = width * region.start.min(self.buffer.area.height) as usize;
421 let cell_region_end = width * region.end.min(self.buffer.area.height) as usize;
422 let cell_region_len = cell_region_end - cell_region_start;
423 let cells_to_scroll_by = width * scroll_by as usize;
424
425 if cells_to_scroll_by >= cell_region_len {
426 self.buffer.content[cell_region_start..cell_region_end].fill_with(Default::default);
428 } else {
429 self.buffer.content[cell_region_start..cell_region_end]
431 .rotate_right(cells_to_scroll_by);
432 self.buffer.content[cell_region_start..cell_region_start + cells_to_scroll_by]
433 .fill_with(Default::default);
434 }
435 Ok(())
436 }
437}
438
439fn append_to_scrollback(scrollback: &mut Buffer, cells: impl IntoIterator<Item = Cell>) {
443 scrollback.content.extend(cells);
444 let width = scrollback.area.width as usize;
445 let new_height = (scrollback.content.len() / width).min(u16::MAX as usize);
446 let keep_from = scrollback
447 .content
448 .len()
449 .saturating_sub(width * u16::MAX as usize);
450 scrollback.content.drain(0..keep_from);
451 scrollback.area.height = new_height as u16;
452}
453
454#[cfg(test)]
455mod tests {
456 use alloc::format;
457
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() -> 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: core::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: core::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}