返回 CodeWhale
posture_tests.rs
根目录 / crates / tui / src / tui / phase_strip / posture_tests.rs
1 //! Host contracts around the shared native posture renderer.
2
3 use codewhale_palette::{ChromeInk, UI_THEME, UiTheme};
4 use ratatui::{
5 buffer::Buffer,
6 layout::Rect,
7 style::{Color, Modifier, Style},
8 };
9
10 use super::{TidelineFooter, render_tideline_footer};
11
12 fn custom_theme() -> UiTheme {
13 UiTheme {
14 status_working: Color::Rgb(1, 2, 3),
15 permission_ask: Color::Rgb(2, 3, 4),
16 permission_auto_review: Color::Rgb(3, 4, 5),
17 permission_full_access: Color::Rgb(4, 5, 6),
18 accent_action: Color::Rgb(5, 6, 7),
19 warning: Color::Rgb(6, 7, 8),
20 accent_primary: Color::Rgb(7, 8, 9),
21 text_soft: Color::Rgb(8, 9, 10),
22 text_muted: Color::Rgb(9, 10, 11),
23 text_hint: Color::Rgb(10, 11, 12),
24 text_dim: Color::Rgb(11, 12, 13),
25 error_fg: Color::Rgb(12, 13, 14),
26 ..UI_THEME
27 }
28 }
29
30 #[test]
31 fn posture_retains_every_host_ink_including_the_pinned_right_fact() {
32 let theme = custom_theme();
33 for ink in [
34 ChromeInk::Outcome,
35 ChromeInk::PermissionAsk,
36 ChromeInk::PermissionAutoReview,
37 ChromeInk::PermissionFullAccess,
38 ChromeInk::Waiting,
39 ChromeInk::Attention,
40 ChromeInk::Active,
41 ChromeInk::PolicyAct,
42 ChromeInk::PolicyPlan,
43 ChromeInk::PolicyOperate,
44 ChromeInk::Identity,
45 ChromeInk::Info,
46 ChromeInk::MetadataValue,
47 ChromeInk::Metadata,
48 ChromeInk::MetadataHint,
49 ChromeInk::MetadataDim,
50 ChromeInk::Failure,
51 ] {
52 let counts = [("C (Ctrl+])".into(), ink)];
53 let footer = TidelineFooter::new(&theme, ("P", ink))
54 .permission_key(Some("K"))
55 .mode_chip(Some(("M", ink)))
56 .mode_key(Some("Tab"))
57 .turn_clock(Some(("W", ink)))
58 .counts(&counts)
59 .session_clock(Some(("S", ink)))
60 .hint(Some(("H", ink)))
61 .right(Some(("R", ink)));
62 let area = Rect::new(3, 2, 100, 2);
63 let mut buf = Buffer::empty(Rect::new(0, 0, 110, 5));
64 buf.set_style(
65 buf.area,
66 Style::default()
67 .fg(crate::tui::infoline::source_theme(false)
68 .color(codewhale_ratatui::Role::Primary)
69 .unwrap())
70 .bg(Color::Rgb(30, 40, 50))
71 .add_modifier(Modifier::ITALIC),
72 );
73 let before = buf.clone();
74 let targets = render_tideline_footer(area, &mut buf, &footer);
75 let column = |symbol: &str| {
76 (area.left()..area.right())
77 .find(|x| buf[(*x, area.y)].symbol() == symbol)
78 .unwrap_or_else(|| panic!("{ink:?}: {symbol} did not paint"))
79 };
80 for symbol in ["P", "M", "W", "C", "S", "H", "R"] {
81 let cell = &buf[(column(symbol), area.y)];
82 assert_eq!(cell.fg, ink.color(&theme), "{ink:?}: {symbol}");
83 assert_eq!(cell.bg, Color::Rgb(30, 40, 50));
84 assert!(cell.modifier.contains(Modifier::ITALIC));
85 assert_eq!(cell.modifier.contains(Modifier::BOLD), symbol == "P");
86 }
87 assert_eq!(buf[(column("K"), area.y)].fg, theme.text_hint);
88 assert_eq!(buf[(column("M") + 2, area.y)].fg, theme.text_hint);
89 assert_eq!(targets, [(0, Rect::new(column("C"), area.y, 10, 1))]);
90 assert_eq!(buf[(column("C") + 2, area.y)].fg, theme.text_hint);
91 assert_eq!(buf[(column("M") - 1, area.y)].fg, theme.text_dim);
92 for position in [(3, 2), (102, 2), (60, 2), (3, 3), (109, 4)] {
93 assert_eq!(buf[position], before[position], "{ink:?}: {position:?}");
94 }
95 }
96 }
97
98 #[test]
99 fn posture_counts_keep_cjk_combining_and_ascii_pointer_geometry_at_every_width() {
100 let counts = [
101 ("模型e\u{301}↓\0 (Ctrl+])".into(), ChromeInk::Active),
102 ("作業 2".into(), ChromeInk::Waiting),
103 ];
104 for ascii in [false, true] {
105 for width in 0..=45 {
106 let area = Rect::new(3, 2, width, 2);
107 let mut buf = Buffer::empty(Rect::new(0, 0, 55, 5));
108 buf.set_style(buf.area, Style::default().bg(Color::Rgb(30, 40, 50)));
109 let before = buf.clone();
110 let footer = TidelineFooter::new(&UI_THEME, ("許可\u{202e}", ChromeInk::PermissionAsk))
111 .counts(&counts)
112 .ascii_safe(ascii);
113 let targets = render_tideline_footer(area, &mut buf, &footer);
114 assert_eq!(buf[(2, 2)], before[(2, 2)]);
115 assert_eq!(buf[(area.right(), 2)], before[(area.right(), 2)]);
116 assert_eq!(buf[(3, 3)], before[(3, 3)], "one reserved row");
117 if width >= 34 {
118 assert_eq!(
119 targets,
120 [(0, Rect::new(13, 2, 15, 1)), (1, Rect::new(30, 2, 6, 1))]
121 );
122 for (x, symbol) in [
123 (13, "模"),
124 (15, "型"),
125 (17, "e\u{301}"),
126 (20, "("),
127 (30, "作"),
128 (32, "業"),
129 (35, "2"),
130 ] {
131 assert_eq!(buf[(x, 2)].symbol(), symbol, "{width}: {symbol}");
132 }
133 assert_eq!(buf[(18, 2)].symbol(), if ascii { "v" } else { "↓" });
134 } else {
135 assert!(targets.is_empty(), "whole count group sheds at {width}");
136 }
137 if width >= 8 {
138 assert_eq!(buf[(4, 2)].symbol(), if ascii { "." } else { "●" });
139 assert_eq!(buf[(6, 2)].symbol(), "許");
140 assert_eq!(buf[(8, 2)].symbol(), "可");
141 }
142 }
143 }
144 }
145
146 #[test]
147 fn posture_cap_warning_keeps_its_live_attention_ink_and_priority() {
148 let theme = custom_theme();
149 for percent in [79, 80, 100, 255] {
150 let area = Rect::new(0, 0, 100, 1);
151 let mut buf = Buffer::empty(area);
152 let footer = TidelineFooter::new(&theme, ("full access", ChromeInk::PermissionFullAccess))
153 .hint(Some(("hint", ChromeInk::Failure)))
154 .context_percent(percent)
155 .right(Some(("failed", ChromeInk::Failure)));
156 render_tideline_footer(area, &mut buf, &footer);
157 let row: String = buf.content().iter().map(|cell| cell.symbol()).collect();
158 if percent < 80 {
159 assert!(row.contains("hint"));
160 assert!(!row.contains("surface soon"));
161 } else {
162 assert!(!row.contains("hint"));
163 assert!(row.contains("▲ surface soon — /compact"));
164 let warning = (0..area.width)
165 .find(|x| buf[(*x, 0)].symbol() == "▲")
166 .unwrap();
167 assert_eq!(buf[(warning, 0)].fg, theme.warning);
168 }
169 assert_eq!(buf[(1, 0)].fg, theme.permission_full_access);
170 assert_eq!(buf[(area.right() - 2, 0)].fg, theme.error_fg);
171 }
172 }
173
173 lines RUST