返回 CodeWhale
guarded_tests.rs
根目录 / crates / tui / src / tui / ocean / guarded_tests.rs
1 use super::*;
2 use crate::tui::color_compat::ColorCompatBackend;
3 use codewhale_ratatui::ocean::{OceanPaintFacts, ocean_semantic_surfaces};
4 use ratatui::{
5 style::{Modifier, Style},
6 text::{Line, Span},
7 };
8
9 fn caps(depth: codewhale_palette::ColorDepth) -> codewhale_ratatui::Caps {
10 ColorCompatBackend::new(std::io::sink(), depth, codewhale_palette::PaletteMode::Dark)
11 .native_ocean_caps()
12 }
13 fn column(area: Rect, phase: ShellPhase, elapsed: u128) -> OceanColumn {
14 OceanColumn::new(
15 OceanRamp::for_theme(&codewhale_palette::UNDERWATER_UI_THEME).unwrap(),
16 area,
17 elapsed,
18 None,
19 phase,
20 true,
21 750,
22 30,
23 )
24 .with_paint_caps(Some(caps(codewhale_palette::ColorDepth::TrueColor)))
25 }
26 fn ordinary(area: Rect) -> Buffer {
27 let mut buf = Buffer::empty(area);
28 for cell in &mut buf.content {
29 cell.set_bg(codewhale_palette::UNDERWATER_UI_THEME.surface_bg)
30 .set_fg(Color::Rgb(255, 255, 255));
31 }
32 buf
33 }
34 #[test]
35 fn guarded_ocean_backend_depth_is_forwarded_and_explicit_pane_supplies_ground_evidence() {
36 use codewhale_ratatui::{color::ColorDepth as Depth, detect::Appearance};
37 for (host, native) in [
38 (codewhale_palette::ColorDepth::TrueColor, Depth::TrueColor),
39 (codewhale_palette::ColorDepth::Ansi256, Depth::Ansi256),
40 (codewhale_palette::ColorDepth::Ansi16, Depth::Ansi16),
41 (codewhale_palette::ColorDepth::Monochrome, Depth::Monochrome),
42 ] {
43 let reported = caps(host);
44 assert_eq!(reported.depth, native);
45 assert_eq!(
46 reported.appearance,
47 Appearance::Unknown,
48 "OS-dark mode is not measured ground"
49 );
50 let area = Rect::new(7, 5, 12, 4);
51 let mut buf = ordinary(area);
52 let before = buf.clone();
53 let column = column(area, ShellPhase::Idle, 0).with_paint_caps(Some(reported));
54 column.paint_matching_native(
55 area,
56 &mut buf,
57 codewhale_palette::UNDERWATER_UI_THEME.surface_bg,
58 &codewhale_palette::UNDERWATER_UI_THEME,
59 &[],
60 );
61 if native == Depth::TrueColor {
62 assert_ne!(buf, before, "actual dark pane supplies its own evidence");
63 } else {
64 assert_eq!(
65 buf, before,
66 "fallback retains the selected flat pane ground"
67 );
68 }
69 }
70 let area = Rect::new(7, 5, 12, 4);
71 let col = column(area, ShellPhase::Idle, 0);
72 for ground in [Color::Reset, Color::White, Color::Rgb(250, 250, 250)] {
73 let mut buf = ordinary(area);
74 buf.set_style(area, Style::default().bg(ground));
75 let before = buf.clone();
76 col.paint_matching_native(
77 area,
78 &mut buf,
79 ground,
80 &codewhale_palette::UNDERWATER_UI_THEME,
81 &[],
82 );
83 assert_eq!(
84 buf, before,
85 "unknown or known-light explicit grounds cannot earn a dark field"
86 );
87 }
88 }
89 #[test]
90 fn guarded_ocean_whole_buffer_matches_frozen_safe_painter_across_phase_size_and_cache_rows() {
91 let theme = codewhale_palette::UNDERWATER_UI_THEME;
92 for phase in [
93 ShellPhase::Idle,
94 ShellPhase::Typing,
95 ShellPhase::Working,
96 ShellPhase::Verifying,
97 ShellPhase::Waiting,
98 ShellPhase::Approval,
99 ShellPhase::Done,
100 ShellPhase::Failed,
101 ] {
102 for (width, height) in [(4, 3), (40, 10), (80, 24)] {
103 for elapsed in [0, 320, 22500, 89999] {
104 let area = Rect::new(7, 5, width, height);
105 let col = column(area, phase, elapsed);
106 let mut actual = ordinary(area);
107 for x in area.x..area.right() {
108 actual[(x, area.y)].set_symbol("x");
109 }
110 let mut expected = actual.clone();
111 guarded_legacy::LegacyColumn(col).paint_matching(
112 area,
113 &mut expected,
114 theme.surface_bg,
115 );
116 col.paint_matching_native(area, &mut actual, theme.surface_bg, &theme, &[]);
117 assert_eq!(
118 actual, expected,
119 "phase={phase:?} area={area:?} elapsed={elapsed}"
120 );
121 // Actual transcript path preserves the same frame ramp cache receipts.
122 let ramp = crate::tui::ambient_life::frame_ocean_ramp(
123 &col,
124 area.height,
125 area.y,
126 elapsed,
127 col.phase_tag(),
128 col.ramp_fingerprint(),
129 );
130 let facts = OceanPaintFacts {
131 ground: theme.surface_bg,
132 sample_top: area.y,
133 samples: &ramp,
134 protected: &[],
135 };
136 let mut cached = ordinary(area);
137 let mut old = cached.clone();
138 guarded_legacy::LegacyWater {
139 ocean_column: Some(col),
140 lines: vec![],
141 background: theme.surface_bg,
142 ocean_elapsed_ms: elapsed,
143 }
144 .paint(area, &mut old);
145 col.paint_native(area, &mut cached, &theme, &facts);
146 assert_eq!(cached, old);
147 }
148 }
149 }
150 }
151 #[test]
152 fn guarded_ocean_semantic_alias_reverse_unknown_ink_and_style_facts_survive_final_shell_pass() {
153 let theme = codewhale_palette::UNDERWATER_UI_THEME;
154 let area = Rect::new(7, 5, 20, 4);
155 let col = column(area, ShellPhase::Idle, 0);
156 let rows = vec![Line::from(vec![
157 Span::raw("plain "),
158 Span::styled(
159 "selected",
160 Style::default()
161 .bg(theme.surface_bg)
162 .add_modifier(Modifier::ITALIC),
163 ),
164 ])];
165 let protected =
166 ocean_semantic_surfaces(&rows, area, crate::tui::ui_text::grapheme_display_width);
167 assert_eq!(protected, vec![Rect::new(13, 5, 8, 1)]);
168 let mut buf = ordinary(area);
169 buf[(7, 6)].set_symbol("x").set_fg(Color::Reset);
170 buf[(8, 6)].set_symbol("x").set_fg(Color::Magenta);
171 buf[(9, 6)].set_symbol("x").set_fg(col.color_at_y(6));
172 buf[(10, 6)].set_bg(theme.selection_bg);
173 buf[(11, 6)]
174 .modifier
175 .insert(Modifier::REVERSED | Modifier::BOLD);
176 let before = buf.clone();
177 col.paint_matching_native(area, &mut buf, theme.surface_bg, &theme, &protected);
178 for x in [7, 8, 10, 11] {
179 assert_eq!(buf[(x, 6)], before[(x, 6)]);
180 }
181 // The terminal backend lifts water-coloured glyphs to visible body ink;
182 // the guard projects that same adaptation before admitting the backdrop.
183 assert_eq!(buf[(9, 6)].fg, before[(9, 6)].fg);
184 assert_eq!(buf[(9, 6)].modifier, before[(9, 6)].modifier);
185 assert_eq!(buf[(9, 6)].bg, col.color_at_y(6));
186 for x in 13..21 {
187 assert_eq!(
188 buf[(x, 5)],
189 before[(x, 5)],
190 "explicit style aliases remain semantic"
191 );
192 }
193 assert_eq!(buf[(7, 5)].bg, col.color_at_y(5));
194 }
195 #[test]
196 fn guarded_ocean_uses_backend_projected_custom_ink_and_live_supporting_floor() {
197 let mut theme = codewhale_palette::UNDERWATER_UI_THEME;
198 theme.text_body = Color::Rgb(255, 255, 255);
199 theme.border = Color::Rgb(111, 149, 181);
200 let area = Rect::new(7, 5, 8, 4);
201 let col = column(area, ShellPhase::Idle, 0);
202 let mut buf = ordinary(area);
203 buf[(7, 5)]
204 .set_symbol("x")
205 .set_fg(codewhale_palette::TEXT_BODY);
206 buf[(8, 5)]
207 .set_symbol("|")
208 .set_fg(theme.border)
209 .modifier
210 .insert(Modifier::ITALIC);
211 let before = buf.clone();
212 let projected = crate::tui::color_compat::project_ocean_ink(
213 &buf[(7, 5)],
214 col.color_at_y(5),
215 caps(codewhale_palette::ColorDepth::TrueColor).depth,
216 &theme,
217 );
218 // Underwater retains its body token; custom supporting ink still follows
219 // the live theme and the backend's contrast projection.
220 assert_eq!(projected, codewhale_palette::TEXT_BODY);
221 col.paint_matching_native(area, &mut buf, theme.surface_bg, &theme, &[]);
222 assert_eq!(buf[(7, 5)].bg, col.color_at_y(5));
223 assert_eq!(
224 buf[(7, 5)].fg,
225 before[(7, 5)].fg,
226 "guard never rewrites source ink"
227 );
228 assert_eq!(buf[(8, 5)].modifier, before[(8, 5)].modifier);
229 assert_eq!(buf[(8, 5)].bg, col.color_at_y(5));
230 }
231 #[test]
232 fn guarded_ocean_caustic_safe_pixels_match_frozen_math_and_spare_semantic_blank_cells() {
233 let area = Rect::new(7, 5, 80, 24);
234 let col = column(area, ShellPhase::Working, 480);
235 let mut actual = ordinary(area);
236 col.paint_matching_native(
237 area,
238 &mut actual,
239 codewhale_palette::UNDERWATER_UI_THEME.surface_bg,
240 &codewhale_palette::UNDERWATER_UI_THEME,
241 &[],
242 );
243 let mut expected = actual.clone();
244 guarded_legacy::apply_caustic_shimmer(area, &mut expected, &col, 480, true, &[]);
245 crate::tui::ambient_life::apply_caustic_shimmer(area, &mut actual, &col, 480, true, &[]);
246 assert_eq!(actual, expected);
247 let rows = [Line::styled(
248 "",
249 Style::default().bg(col.color_at_y(area.y)),
250 )];
251 let mut buf = ordinary(area);
252 col.paint_matching_native(
253 area,
254 &mut buf,
255 codewhale_palette::UNDERWATER_UI_THEME.surface_bg,
256 &codewhale_palette::UNDERWATER_UI_THEME,
257 &[],
258 );
259 buf[(10, 6)].set_bg(Color::Rgb(73, 89, 107));
260 buf[(13, 6)].modifier.insert(Modifier::REVERSED);
261 let before = buf.clone();
262 crate::tui::ambient_life::apply_caustic_shimmer(area, &mut buf, &col, 480, true, &rows);
263 for x in area.x..area.right() {
264 assert_eq!(buf[(x, area.y)], before[(x, area.y)]);
265 }
266 assert_eq!(buf[(10, 6)], before[(10, 6)]);
267 assert_eq!(buf[(13, 6)], before[(13, 6)]);
268 }
269 #[test]
270 fn guarded_ocean_missing_capability_still_motion_and_clip_cannot_modify_guards() {
271 let area = Rect::new(7, 5, 80, 24);
272 let col = column(area, ShellPhase::Working, 480);
273 for col in [
274 col.with_paint_caps(None),
275 OceanColumn::new(
276 OceanRamp::for_theme(&codewhale_palette::UNDERWATER_UI_THEME).unwrap(),
277 area,
278 480,
279 None,
280 ShellPhase::Working,
281 false,
282 0,
283 0,
284 )
285 .with_paint_caps(Some(caps(codewhale_palette::ColorDepth::TrueColor))),
286 ] {
287 let mut buf = ordinary(area);
288 // Establish real ordinary water first, so missing caps and Still
289 // must stop a pass that would otherwise have eligible pixels.
290 column(area, ShellPhase::Working, 480).paint_matching_native(
291 area,
292 &mut buf,
293 codewhale_palette::UNDERWATER_UI_THEME.surface_bg,
294 &codewhale_palette::UNDERWATER_UI_THEME,
295 &[],
296 );
297 let before = buf.clone();
298 crate::tui::ambient_life::apply_caustic_shimmer(area, &mut buf, &col, 480, true, &[]);
299 assert_eq!(buf, before);
300 }
301 let mut buf = ordinary(area);
302 let before = buf.clone();
303 let requested = Rect::new(9, 7, 8, 3);
304 col.paint_matching_native(
305 requested,
306 &mut buf,
307 codewhale_palette::UNDERWATER_UI_THEME.surface_bg,
308 &codewhale_palette::UNDERWATER_UI_THEME,
309 &[],
310 );
311 for y in area.y..area.bottom() {
312 for x in area.x..area.right() {
313 if !requested.contains((x, y).into()) {
314 assert_eq!(buf[(x, y)], before[(x, y)]);
315 }
316 }
317 }
318 }
319
319 lines RUST