1pub mod adapter;
10pub mod alternate;
11pub mod cursor;
12pub mod git;
13pub mod highlight;
14pub mod layout;
15pub mod live;
16pub mod path_display;
17pub mod row;
18pub mod style;
19pub mod transcript;
20pub mod view;
21
22#[cfg(test)]
23mod tests {
24 use super::cursor;
25 use super::cursor::prompt_cursor;
26 use super::layout::{content_width, wrap_text};
27 use super::row::{CursorCoord, Frame, Row};
28 use super::style::{CellStyle, Color, Span};
29
30 fn padded_rows(text: &str, style: CellStyle, width: usize, pad_style: CellStyle) -> Vec<Row> {
32 let content_w = content_width(width);
33 let wrapped = wrap_text(text, content_w);
34 wrapped
35 .into_iter()
36 .map(|line| {
37 let spans = if line.is_empty() { Vec::new() } else { vec![Span::styled(line, style)] };
38 Row::padded(spans, width, pad_style)
39 })
40 .collect()
41 }
42
43 #[test]
44 fn row_model_narrow_width() {
45 let rows = padded_rows(
46 "hello world",
47 CellStyle::new().fg(Color::Green),
48 10,
49 CellStyle::default(),
50 );
51
52 assert_eq!(rows.len(), 2);
53 assert_eq!(rows[0].text_width(), 10);
54 assert!(rows[0].text().contains("hello"));
55 assert!(rows[1].text().contains("world"));
56 }
57
58 #[test]
59 fn row_model_normal_width() {
60 let rows = padded_rows(
61 "hello world this is a test",
62 CellStyle::default(),
63 80,
64 CellStyle::default(),
65 );
66
67 assert_eq!(rows.len(), 1);
68 assert_eq!(rows[0].text_width(), 80);
69 assert!(rows[0].text().contains("hello world this is a test"));
70 }
71
72 #[test]
73 fn row_model_wide_width() {
74 let rows = padded_rows("hello world", CellStyle::default(), 200, CellStyle::default());
75 assert_eq!(rows.len(), 1);
76 assert_eq!(rows[0].text_width(), 200);
77 }
78
79 #[test]
80 fn row_model_tiny_width_no_panic() {
81 let rows = padded_rows("hello", CellStyle::default(), 1, CellStyle::default());
82 assert!(!rows.is_empty());
83 }
84
85 #[test]
86 fn row_model_zero_width_does_not_panic() {
87 let rows = padded_rows("hello", CellStyle::default(), 0, CellStyle::default());
88 assert!(!rows.is_empty());
89 assert!(rows.iter().all(|r| r.width == 0));
90 }
91
92 #[test]
93 fn prompt_cursor_single_line() {
94 let coord = prompt_cursor("hello world", 5, 76, 3);
95 assert_eq!(coord, CursorCoord { row: 0, col: 8 });
96 }
97
98 #[test]
99 fn prompt_cursor_wrapped_line() {
100 let coord = prompt_cursor("abcdefghij", 7, 3, 0);
101 assert_eq!(coord, CursorCoord { row: 2, col: 1 });
102 }
103
104 #[test]
105 fn prompt_cursor_multiline_explicit_newline() {
106 let coord = prompt_cursor("line1\nline2\nline3", 8, 76, 0);
107 assert_eq!(coord, CursorCoord { row: 1, col: 2 });
108 }
109
110 #[test]
111 fn prompt_cursor_indented_multiline() {
112 let coord = prompt_cursor("ab\ncd", 4, 76, 3);
113 assert_eq!(coord, CursorCoord { row: 1, col: 4 });
114 }
115
116 #[test]
117 fn prompt_cursor_multibyte() {
118 let coord = prompt_cursor("héllo wörld", 6, 76, 0);
119 assert_eq!(coord, CursorCoord { row: 0, col: 6 });
120 }
121
122 #[test]
123 fn prompt_cursor_multibyte_wrapped() {
124 let coord = prompt_cursor("ééééé", 4, 2, 0);
125 assert_eq!(coord, CursorCoord { row: 1, col: 2 });
126 }
127
128 #[test]
129 fn snapshot_simple_styled_frame() {
130 let mut frame = Frame::new(20);
131 let blue = CellStyle::new().fg(Color::Blue).bg(Color::Rgb { r: 30, g: 33, b: 50 });
132 frame.push(Row::padded(vec![Span::styled("Assistant", blue.bold())], 20, blue));
133 frame.push(Row::padded(
134 vec![Span::styled("hello world", CellStyle::default().bg(blue.bg))],
135 20,
136 blue,
137 ));
138 frame.set_cursor(CursorCoord::new(2, 3));
139 insta::assert_snapshot!(frame.render_styled());
140 }
141
142 #[test]
143 fn snapshot_transcript_block() {
144 let mut frame = Frame::new(40);
145 let bg = Color::Rgb { r: 30, g: 33, b: 50 };
146 let surface = CellStyle::default().bg(bg);
147
148 let rows = vec![
149 Row::padded(
150 vec![Span::styled("User", CellStyle::new().fg(Color::Blue).bg(bg).bold())],
151 40,
152 surface,
153 ),
154 Row::padded(
155 vec![Span::styled("hello there", CellStyle::default().bg(bg))],
156 40,
157 surface,
158 ),
159 ];
160 frame.rows.extend(rows);
161 insta::assert_snapshot!(frame.render_styled());
162 }
163
164 #[test]
165 fn snapshot_prompt_with_cursor() {
166 let mut frame = Frame::new(30);
167 let prompt_style = CellStyle::new().fg(Color::Yellow);
168 frame.push(Row::padded(
169 vec![Span::styled("❯ hello", prompt_style)],
170 30,
171 CellStyle::default(),
172 ));
173 frame.set_cursor(prompt_cursor("hello", 5, 27, 3));
174 insta::assert_snapshot!(frame.render_styled());
175 }
176
177 #[test]
178 fn snapshot_multiline_prompt() {
179 let text = "line one\nline two";
180 let mut frame = Frame::new(30);
181 let style = CellStyle::new().fg(Color::Yellow);
182 let indent = 3;
183 let body_width = 27;
184 for line in cursor::prompt_rows(text, body_width) {
185 let spans = vec![Span::styled("❯ ", style), Span::styled(line, CellStyle::default())];
186 frame.push(Row::padded(spans, 30, CellStyle::default()));
187 }
188 frame.set_cursor(prompt_cursor(text, 9, body_width, indent));
189 insta::assert_snapshot!(frame.render_styled());
190 }
191
192 #[test]
193 fn snapshot_narrow_width_block() {
194 let mut frame = Frame::new(10);
195 let bg = Color::Rgb { r: 30, g: 33, b: 50 };
196 let surface = CellStyle::default().bg(bg);
197 frame.push(Row::padded(
198 vec![Span::styled("Err", CellStyle::new().fg(Color::Red).bg(bg).bold())],
199 10,
200 surface,
201 ));
202 frame.push(Row::padded(
203 vec![Span::styled("bad input value here", CellStyle::default().bg(bg))],
204 10,
205 surface,
206 ));
207 insta::assert_snapshot!(frame.render_styled());
208 }
209}