use std::time::Instant;
use super::*;
use crate::{
emulator::Emulator,
preview::{MARKER, PreviewState, SummaryAdapter},
protocol::{Preview, PreviewSource},
};
struct RowsScreen {
rows: Vec<String>,
}
fn rs(rows: &[&str]) -> RowsScreen {
RowsScreen {
rows: rows.iter().map(|s| s.to_string()).collect(),
}
}
impl ScreenFacts for RowsScreen {
fn revision(&self) -> u64 {
1
}
fn alt_epoch(&self) -> u64 {
0
}
fn alternate_screen(&self) -> bool {
false
}
fn title(&self) -> Option<&str> {
None
}
fn live_floor(&self) -> String {
self.rows
.iter()
.rev()
.find(|r| !r.is_empty())
.cloned()
.unwrap_or_default()
}
fn live_rows(&self) -> Vec<String> {
self.rows.clone()
}
fn alt_leave_floor(&self) -> Option<&str> {
None
}
}
fn resolve_corpus(bytes: &[u8], adapter: &dyn SummaryAdapter, rows: u16, cols: u16) -> Preview {
let mut emu = Emulator::new(rows, cols, 2000);
emu.process(bytes);
let mut st = PreviewState::new();
st.resolve(Instant::now(), &emu, Some(adapter)).clone()
}
fn anchor(text: &str, rule: &'static str) -> (String, PreviewSource, Option<&'static str>) {
(text.to_string(), PreviewSource::Anchor, Some(rule))
}
fn parts(p: &Preview) -> (String, PreviewSource, Option<&'static str>) {
(p.text.clone(), p.source, p.rule)
}
#[test]
fn select_matches_first_word_basenames_only() {
for cmd in [
"claude",
"claude --model opus",
"/usr/local/bin/claude --resume abc",
"codex resume 'not-checked-here'",
"grok",
] {
assert!(select(cmd).is_some(), "{cmd:?} must select an adapter");
}
for cmd in ["vim", "FOO=bar claude", "claude|tee log", "codex; ls", ""] {
assert!(select(cmd).is_none(), "{cmd:?} must select nothing");
}
}
#[test]
fn select_routes_to_the_matching_adapter() {
let sep = "─".repeat(80);
let claude = rs(&["✻ Hashing… (6s · ↓ 87 tokens)", &sep, "❯", &sep]);
assert_eq!(
select("claude").unwrap().live_preview(&claude).unwrap().1,
"claude:spinner"
);
let codex = rs(&[
"• Working (2s • esc to interrupt)",
"",
"› Write tests",
"",
" gpt-5.6-sol high · 0 in · 0 out",
]);
assert_eq!(
select("codex").unwrap().live_preview(&codex).unwrap().1,
"codex:working"
);
let grok = rs(&[
" ⠼ Sleep 5 seconds then echo ok… 1.5s 2.8s ⇣14.2k [↓][stop]",
"",
" ╭──────────────────────╮",
" │ ❯ │",
" ╰── Grok 4.5 (xhigh) · always-approve ─╯",
]);
assert_eq!(
select("grok").unwrap().live_preview(&grok).unwrap().1,
"grok:spinner"
);
}
#[test]
fn claude_spinner_and_action_row() {
let sep = "─".repeat(120);
let spin = rs(&["✻ Hashing… (6s · ↓ 87 tokens)", &sep, "❯", &sep, " status"]);
assert_eq!(
ClaudeSummary.live_preview(&spin),
Some(("Hashing…".to_string(), "claude:spinner"))
);
let action = rs(&[
"⏺ Running 1 shell command…",
"",
"· Hashing… (3s · ↓ 52 tokens)",
&sep,
"❯",
&sep,
]);
assert_eq!(
ClaudeSummary.live_preview(&action),
Some(("Running 1 shell command…".to_string(), "claude:action-row"))
);
let attach = rs(&[
" Running 1 shell command…",
" ⎿ $ sleep 5 && echo ok",
"✻ Hashing… (6s)",
&sep,
"❯",
&sep,
]);
assert_eq!(
ClaudeSummary.live_preview(&attach),
Some(("Hashing…".to_string(), "claude:spinner"))
);
let reply = rs(&["⏺ ok", "", "✻ Hashing… (2s)", &sep, "❯", &sep]);
assert_eq!(
ClaudeSummary.live_preview(&reply),
Some(("Hashing…".to_string(), "claude:spinner"))
);
}
#[test]
fn claude_spinner_extracts_task_derived_phrases() {
let sep = "─".repeat(120);
let s = rs(&[
"✳ Overseeing phase 4 (adapters)… (54s · almost done thinking with high effort)",
&sep,
"❯",
&sep,
]);
assert_eq!(
ClaudeSummary.live_preview(&s),
Some((
"Overseeing phase 4 (adapters)… · almost done thinking with high effort".to_string(),
"claude:spinner"
))
);
}
#[test]
fn claude_parenthetical_keeps_slow_segments_and_drops_tickers() {
let sep = "─".repeat(120);
let spin = |row: &str| {
let rows = [row, &sep, "❯", &sep];
ClaudeSummary.live_preview(&rs(&rows))
};
assert_eq!(
spin("✻ Envisioning… (1m 8s · ↓ 2.1k tokens · thinking with high effort)"),
Some((
"Envisioning… · thinking with high effort".to_string(),
"claude:spinner"
))
);
for ticker in [
"6s",
"1m 8s",
"2h 3m",
"8.7s",
"↓ 87 tokens",
"↑ 1.2k tokens",
"↓ 2.1k",
"2.1k tokens",
"esc to interrupt",
] {
assert_eq!(
spin(&format!("✻ Hashing… ({ticker})")),
Some(("Hashing…".to_string(), "claude:spinner")),
"{ticker:?} must drop"
);
assert_eq!(
spin(&format!("✻ Hashing… ({ticker} · thinking)")),
Some(("Hashing… · thinking".to_string(), "claude:spinner")),
"{ticker:?} must drop beside a kept segment"
);
}
assert_eq!(
spin("✽ Concocting…"),
Some(("Concocting…".to_string(), "claude:spinner")),
"a row with no parenthetical is unchanged"
);
}
#[test]
fn claude_action_row_carries_the_spinner_rows_semantic_tail() {
let sep = "─".repeat(120);
let rows = [
"⏺ Running 1 shell command…",
"",
"✻ Envisioning… (1m 8s · ↓ 2.1k tokens · thinking with high effort)",
&sep,
"❯",
&sep,
];
assert_eq!(
ClaudeSummary.live_preview(&rs(&rows)),
Some((
"Running 1 shell command… · thinking with high effort".to_string(),
"claude:action-row"
))
);
}
#[test]
fn claude_waiting_family_matches_the_skeleton_and_never_probes() {
let sep = "─".repeat(120);
let spin = |row: &str| {
let rows = [row, &sep, "❯", &sep];
ClaudeSummary.live_preview(&rs(&rows))
};
for row in [
"✻ Waiting for 1 background agent to finish",
"· Waiting for 1 background agent to finish",
"✻ Waiting for 3 background agents to finish",
"✻ Waiting for 1 dynamic workflow to finish",
"✽ Waiting for 3 dynamic workflows to finish",
"✻ Waiting for 2 tasks to finish",
] {
let want = row.chars().skip(2).collect::<String>();
assert_eq!(spin(row), Some((want, "claude:waiting")), "{row:?}");
}
for row in [
"✻ Waiting patiently",
"· Waiting for review comments to land",
"✻ Waiting for some agents to finish",
"✻ Waiting for 2 very long noun phrases here to finish",
"✻ Waiting for 2 agents to start",
"✻ Waiting for 3 to finish",
] {
assert_eq!(spin(row), None, "{row:?}");
}
let rows = [
"⏺ Running 1 shell command…",
"",
"✻ Waiting for 1 background agent to finish",
&sep,
"❯",
&sep,
];
assert_eq!(
ClaudeSummary.live_preview(&rs(&rows)),
Some((
"Waiting for 1 background agent to finish".to_string(),
"claude:waiting"
))
);
}
#[test]
fn claude_title_frames_canonicalize_to_constant_text() {
for frame in CLAUDE_SPINNER {
assert_eq!(
ClaudeSummary.normalize_title(&format!("{frame} Claude Code")),
Some("✻ Claude Code".to_string()),
"{frame:?}"
);
}
let a = ClaudeSummary.normalize_title("✢ Claude Code");
let b = ClaudeSummary.normalize_title("✽ Claude Code");
assert_eq!(a, b, "two frames must normalize identically");
assert_eq!(
ClaudeSummary.normalize_title("⠐ Review fleetcom preview design document"),
Some("✻ Review fleetcom preview design document".to_string())
);
assert_eq!(
ClaudeSummary.normalize_title("⠴ Review fleetcom preview design document"),
Some("✻ Review fleetcom preview design document".to_string()),
"mid-block braille frame"
);
assert_eq!(ClaudeSummary.normalize_title("zellij: main"), None);
assert_eq!(ClaudeSummary.normalize_title("✻"), None, "frame alone");
}
#[test]
fn title_tier_renders_the_normalized_title() {
let mut emu = Emulator::new(24, 80, 100);
emu.process(b"\x1b[?1049h\x1b]0;\xe2\x9c\xa2 Claude Code\x07conversation body");
let mut st = PreviewState::new();
let p = st
.resolve(Instant::now(), &emu, Some(&ClaudeSummary))
.clone();
assert_eq!(
(p.text.as_str(), p.source, p.rule),
("✻ Claude Code", PreviewSource::Title, None)
);
let mut st = PreviewState::new();
let p = st.resolve(Instant::now(), &emu, None).clone();
assert_eq!(
(p.text.as_str(), p.source),
("✢ Claude Code", PreviewSource::Title),
"no adapter: verbatim"
);
}
#[test]
fn claude_aborts_on_foreign_column_zero_rows() {
let sep = "─".repeat(120);
let wrapped = rs(&["✻ Hashing… (6s · ↓ 87 to", "kens)", &sep, "❯", &sep]);
assert_eq!(ClaudeSummary.live_preview(&wrapped), None);
let menu = rs(&["❯ 1. Yes", " 2. No", &sep, "❯", &sep]);
assert_eq!(ClaudeSummary.live_preview(&menu), None);
}
#[test]
fn claude_scan_crosses_task_list_gaps_within_the_window() {
let sep = "─".repeat(120);
let behind_gap = |status: &str, gap: usize| {
let mut rows = vec![status.to_string()];
rows.push(" ⎿ ✔ Phase 0: verify facts".to_string());
rows.extend((1..gap).map(|i| format!(" ◼ Phase {i}: generic step")));
rows.extend([sep.clone(), "❯".to_string(), sep.clone()]);
let refs: Vec<&str> = rows.iter().map(String::as_str).collect();
ClaudeSummary.live_preview(&rs(&refs))
};
for gap in [4, 15] {
assert_eq!(
behind_gap(
"✢ Running phase 1 (dashboard UI)… (4m 20s · ↓ 17.1k tokens)",
gap
),
Some((
"Running phase 1 (dashboard UI)…".to_string(),
"claude:spinner"
)),
"gap of {gap} indented rows"
);
}
for gap in [16, 17, 24] {
assert_eq!(
behind_gap(
"✢ Running phase 1 (dashboard UI)… (4m 20s · ↓ 17.1k tokens)",
gap
),
None,
"gap of {gap} indented rows must exhaust the window"
);
}
assert_eq!(
behind_gap("✻ Waiting for 2 background agents to finish", 5),
Some((
"Waiting for 2 background agents to finish".to_string(),
"claude:waiting"
))
);
let prose = rs(&[
"✢ Running phase 1 (dashboard UI)… (4m 20s · ↓ 17.1k tokens)",
"⏺ The phase list below is queued, not running.",
" ⎿ ✔ Phase 0: verify facts",
" ◼ Phase 1: dashboard polish",
&sep,
"❯",
&sep,
]);
assert_eq!(ClaudeSummary.live_preview(&prose), None);
}
#[test]
fn claude_blank_rows_do_not_consume_the_window() {
let sep = "─".repeat(120);
let resolve = |rows: Vec<String>| {
let refs: Vec<&str> = rows.iter().map(String::as_str).collect();
ClaudeSummary.live_preview(&rs(&refs))
};
let boxed = |sep: &str| [sep.to_string(), "❯ /workflows".to_string(), sep.to_string()];
let mut rows = vec!["✻ Waiting for 1 dynamic workflow to finish".to_string()];
rows.extend(std::iter::repeat_n(String::new(), 19));
rows.extend(boxed(&sep));
assert_eq!(
resolve(rows),
Some((
"Waiting for 1 dynamic workflow to finish".to_string(),
"claude:waiting"
))
);
let mut rows = vec!["✢ Running phase 1 (dashboard UI)… (4m 20s)".to_string()];
for i in 0..15 {
rows.push(String::new());
rows.push(format!(" ◼ Phase {i}: generic step"));
}
rows.extend(boxed(&sep));
assert_eq!(
resolve(rows),
Some((
"Running phase 1 (dashboard UI)…".to_string(),
"claude:spinner"
))
);
let mut rows = vec!["✢ Running phase 1 (dashboard UI)… (4m 20s)".to_string()];
for i in 0..16 {
rows.push(String::new());
rows.push(format!(" ◼ Phase {i}: generic step"));
}
rows.extend(boxed(&sep));
assert_eq!(resolve(rows), None);
let prose = rs(&[
"✻ Hashing… (6s · ↓ 87 tokens)",
"",
"",
"⏺ The workflow report lands below.",
"",
"",
&sep,
"❯ /workflows",
&sep,
]);
assert_eq!(ClaudeSummary.live_preview(&prose), None);
}
#[test]
fn claude_approval_requires_the_dialog_shape() {
let dialog = rs(&[
" Do you want to create word.txt?",
" ❯ 1. Yes",
" 2. Yes, allow all edits during this session (shift+tab)",
" 3. No",
"",
" Esc to cancel · Tab to amend",
]);
assert_eq!(
ClaudeSummary.live_preview(&dialog),
Some(("awaiting approval".to_string(), "claude:approval-menu"))
);
let lone = rs(&[" ❯ 1. Yes", "", " Esc to cancel"]);
assert_eq!(ClaudeSummary.live_preview(&lone), None);
}
#[test]
fn claude_label_reads_the_welcome_box() {
let boxed = rs(&[
"╭─── Claude Code v2.1.215 ────────────╮",
"│ Fable 5 with high effort · Claude Max · │ notes │",
"╰──────────────────────────────────────╯",
]);
assert_eq!(
ClaudeSummary.model_label(&boxed),
Some("Fable 5 (high)".to_string())
);
assert_eq!(ClaudeSummary.model_label(&rs(&["no box here"])), None);
}
#[test]
fn codex_working_normalization() {
let tail = [
"",
"› Write tests for @filename",
"",
" gpt-5.6-sol high · 0 in · 0 out",
];
let full = "• Working (7s • esc to interrupt) · 1 background terminal running · /ps to view · /stop to close";
for (row, want) in [
(full, "Working · 1 background terminal running"),
("• Working (2s • esc to interrupt)", "Working"),
("• Working (7s • esc to…", "Working"),
(
"• Working (7s • esc to interrupt) · 1 background termi…",
"Working · 1 background termi…",
),
] {
let mut rows = vec![row];
rows.extend(tail);
assert_eq!(
CodexSummary.live_preview(&rs(&rows)),
Some((want.to_string(), "codex:working")),
"{row:?}"
);
}
assert_eq!(
CodexSummary.model_label(&rs(&tail[1..])),
Some("gpt-5.6-sol high".to_string())
);
}
#[test]
fn codex_ran_stops_at_foreign_rows() {
let transient = rs(&[
"• Ran sleep 5 && echo ok",
" └ ok",
"",
"› ",
"",
" gpt-5.6-sol high · 1 in · 2 out",
]);
assert_eq!(
CodexSummary.live_preview(&transient),
Some(("Ran sleep 5 && echo ok".to_string(), "codex:ran"))
);
let sep = "─".repeat(120);
let behind_reply = rs(&[
"• Ran sleep 5 && echo ok",
" └ ok",
"",
&sep,
"",
"• ok",
"",
"› ",
"",
" gpt-5.6-sol high · 1 in · 2 out",
]);
assert_eq!(CodexSummary.live_preview(&behind_reply), None);
}
#[test]
fn codex_hint_row_layout_anchors_without_a_token_bar() {
let hinted = rs(&[
"• Running cargo test --test daemon_env",
"",
"",
"• Working (10m 26s • esc to interrupt)",
"",
"›",
"",
" tab to queue message",
]);
assert_eq!(
CodexSummary.live_preview(&hinted),
Some(("Working".to_string(), "codex:working"))
);
assert_eq!(CodexSummary.model_label(&hinted), None);
}
#[test]
fn codex_approval_modal_synthesizes_on_any_selection() {
let on_first = rs(&[
" Would you like to run the following command?",
"",
" $ cargo test --test daemon_env",
"",
"› 1. Yes, proceed (y)",
" 2. Yes, and don't ask again for commands that start with `cargo test` (p)",
" 3. No, and tell Codex what to do differently (esc)",
"",
" Press enter to confirm or esc to cancel",
]);
assert_eq!(
CodexSummary.live_preview(&on_first),
Some(("awaiting approval".to_string(), "codex:approval-menu"))
);
let on_second = rs(&[
" 1. Yes, proceed (y)",
"› 2. Yes, and don't ask again (p)",
" 3. No (esc)",
"",
" Press enter to confirm or esc to cancel",
]);
assert_eq!(
CodexSummary.live_preview(&on_second),
Some(("awaiting approval".to_string(), "codex:approval-menu"))
);
}
#[test]
fn codex_quoted_menu_with_a_live_composer_is_not_a_modal() {
let quoted = rs(&[
"• I found these options in the doc:",
"",
"› 1. Yes, proceed (y)",
" 2. No, cancel (esc)",
"",
"›",
"",
" gpt-5.6-sol high · 0 in · 0 out",
]);
assert_eq!(CodexSummary.live_preview("ed), None);
}
#[test]
fn codex_requires_the_composer_pin() {
let exited = rs(&[
"• Ran sleep 5 && echo ok",
"",
"Token usage: total=14,353 input=14,063",
"To continue this session, run codex resume 0199-fake",
]);
assert_eq!(CodexSummary.live_preview(&exited), None);
assert_eq!(CodexSummary.model_label(&exited), None);
}
#[test]
fn grok_status_shapes() {
let boxed = [
" ╭──────────────────────╮",
" │ ❯ │",
" ╰── Grok 4.5 (xhigh) · always-approve ─╯",
];
let probe = |status: &str| {
let mut rows = vec![status, ""];
rows.extend(boxed);
GrokSummary.live_preview(&rs(&rows))
};
assert_eq!(
probe(" ⠼ Sleep 5 seconds then echo ok… 1.5s 2.8s ⇣14.2k [↓][stop]"),
Some(("Sleep 5 seconds then echo ok…".to_string(), "grok:spinner"))
);
assert_eq!(
probe(" ⠋ Thinking… 0.2s"),
Some(("Thinking…".to_string(), "grok:spinner"))
);
assert_eq!(
probe(" Worked for 8.7s"),
Some(("Worked for 8.7s".to_string(), "grok:worked"))
);
assert_eq!(
probe(" Worked for 1m 24s"),
Some(("Worked for 1m 24s".to_string(), "grok:worked"))
);
assert_eq!(probe(" Worked for a while"), None);
assert_eq!(
probe(" Coming from Codex? Resume your session from 7m ago using ctrl+u"),
None
);
let mut rows = vec![" ⠋ Thinking… 0.2s", ""];
rows.extend(boxed);
assert_eq!(
GrokSummary.model_label(&rs(&rows)),
Some("Grok 4.5 (xhigh)".to_string())
);
let plain = rs(&[" ⠋ Thinking… 0.2s", "", "╭────╮", "│ ❯ │", "╰────╯"]);
assert_eq!(GrokSummary.model_label(&plain), None);
}
#[test]
fn corpus_positive_states_anchor_exactly() {
struct Case(
&'static str,
&'static [u8],
&'static dyn SummaryAdapter,
&'static str,
&'static str,
);
let cases = [
Case(
"preview_claude_working",
include_bytes!("../../tests/corpus/preview_claude_working.bin"),
&ClaudeSummary,
"Fable 5 (high) · Concocting…",
"claude:spinner",
),
Case(
"preview_claude_working_tool",
include_bytes!("../../tests/corpus/preview_claude_working_tool.bin"),
&ClaudeSummary,
"Fable 5 (high) · Hashing…",
"claude:spinner",
),
Case(
"preview_claude_action",
include_bytes!("../../tests/corpus/preview_claude_action.bin"),
&ClaudeSummary,
"Fable 5 (high) · Running 1 shell command…",
"claude:action-row",
),
Case(
"preview_claude_approval",
include_bytes!("../../tests/corpus/preview_claude_approval.bin"),
&ClaudeSummary,
"Fable 5 (high) · awaiting approval",
"claude:approval-menu",
),
Case(
"preview_claude_tasklist",
include_bytes!("../../tests/corpus/preview_claude_tasklist.bin"),
&ClaudeSummary,
"Running phase 1 (dashboard UI)…",
"claude:spinner",
),
Case(
"preview_codex_working",
include_bytes!("../../tests/corpus/preview_codex_working.bin"),
&CodexSummary,
"gpt-5.6-sol high · Working · 1 background terminal running",
"codex:working",
),
Case(
"preview_codex_ran",
include_bytes!("../../tests/corpus/preview_codex_ran.bin"),
&CodexSummary,
"gpt-5.6-sol high · Ran sleep 5 && echo ok",
"codex:ran",
),
Case(
"preview_claude_waiting",
include_bytes!("../../tests/corpus/preview_claude_waiting.bin"),
&ClaudeSummary,
"Waiting for 1 background agent to finish",
"claude:waiting",
),
Case(
"preview_claude_workflow_wait",
include_bytes!("../../tests/corpus/preview_claude_workflow_wait.bin"),
&ClaudeSummary,
"Waiting for 1 dynamic workflow to finish",
"claude:waiting",
),
Case(
"preview_codex_hint_row",
include_bytes!("../../tests/corpus/preview_codex_hint_row.bin"),
&CodexSummary,
"Working",
"codex:working",
),
Case(
"preview_codex_approval",
include_bytes!("../../tests/corpus/preview_codex_approval.bin"),
&CodexSummary,
"awaiting approval",
"codex:approval-menu",
),
Case(
"preview_grok_working",
include_bytes!("../../tests/corpus/preview_grok_working.bin"),
&GrokSummary,
"Grok 4.5 (xhigh) · Sleep 5 seconds then echo ok…",
"grok:spinner",
),
Case(
"preview_grok_worked",
include_bytes!("../../tests/corpus/preview_grok_worked.bin"),
&GrokSummary,
"Grok 4.5 (xhigh) · Worked for 8.7s",
"grok:worked",
),
];
for Case(name, bytes, adapter, text, rule) in cases {
let p = resolve_corpus(bytes, adapter, 40, 120);
assert_eq!(parts(&p), anchor(text, rule), "{name}");
}
}
#[test]
fn corpus_idle_states_fall_through() {
let cases: [(&str, &[u8], &dyn SummaryAdapter); 4] = [
(
"preview_claude_idle",
include_bytes!("../../tests/corpus/preview_claude_idle.bin"),
&ClaudeSummary,
),
(
"preview_claude_done",
include_bytes!("../../tests/corpus/preview_claude_done.bin"),
&ClaudeSummary,
),
(
"preview_grok_idle",
include_bytes!("../../tests/corpus/preview_grok_idle.bin"),
&GrokSummary,
),
(
"preview_grok_splash",
include_bytes!("../../tests/corpus/preview_grok_splash.bin"),
&GrokSummary,
),
];
for (name, bytes, adapter) in cases {
let p = resolve_corpus(bytes, adapter, 40, 120);
assert_eq!(
parts(&p),
(MARKER.to_string(), PreviewSource::Marker, None),
"{name}"
);
}
}
#[test]
fn corpus_body_shaped_text_never_extracts() {
let p = resolve_corpus(
include_bytes!("../../tests/corpus/preview_claude_body_menu.bin"),
&ClaudeSummary,
40,
120,
);
assert_eq!(
parts(&p),
anchor("Fable 5 (high) · Hashing…", "claude:spinner")
);
let p = resolve_corpus(
include_bytes!("../../tests/corpus/preview_claude_body_menu_idle.bin"),
&ClaudeSummary,
40,
120,
);
assert_eq!(parts(&p), (MARKER.to_string(), PreviewSource::Marker, None));
let p = resolve_corpus(
include_bytes!("../../tests/corpus/preview_claude_body_above_tasklist.bin"),
&ClaudeSummary,
40,
120,
);
assert_eq!(parts(&p), (MARKER.to_string(), PreviewSource::Marker, None));
let p = resolve_corpus(
include_bytes!("../../tests/corpus/preview_codex_scrollback.bin"),
&CodexSummary,
40,
120,
);
assert_eq!(
parts(&p),
(
"gpt-5.6-sol high · 5.26K used · 28.2K in · 78 out".to_string(),
PreviewSource::Floor,
None
)
);
let p = resolve_corpus(
include_bytes!("../../tests/corpus/preview_codex_body_menu.bin"),
&CodexSummary,
40,
120,
);
assert_eq!(
parts(&p),
(
"gpt-5.6-sol high · 0 in · 0 out".to_string(),
PreviewSource::Floor,
None
)
);
let p = resolve_corpus(
include_bytes!("../../tests/corpus/preview_codex_working_over_ran.bin"),
&CodexSummary,
40,
120,
);
assert_eq!(
parts(&p),
anchor("gpt-5.6-sol high · Working", "codex:working")
);
}
#[test]
fn corpus_truncated_rows_still_anchor() {
let p = resolve_corpus(
include_bytes!("../../tests/corpus/preview_trunc_claude.bin"),
&ClaudeSummary,
40,
80,
);
assert_eq!(parts(&p), anchor("Hashing…", "claude:spinner"));
let p = resolve_corpus(
include_bytes!("../../tests/corpus/preview_trunc_codex.bin"),
&CodexSummary,
40,
80,
);
assert_eq!(
parts(&p),
anchor(
"gpt-5.6-sol high · Working · 1 background terminal running",
"codex:working"
)
);
let p = resolve_corpus(
include_bytes!("../../tests/corpus/preview_trunc_grok.bin"),
&GrokSummary,
40,
80,
);
assert_eq!(
parts(&p),
anchor(
"Grok 4.5 (xhigh) · Sleep 5 seconds then echo…",
"grok:spinner"
)
);
}
#[test]
fn corpus_wrapped_ellipsis_falls_through() {
let p = resolve_corpus(
include_bytes!("../../tests/corpus/preview_wrap_grok.bin"),
&GrokSummary,
40,
30,
);
assert_eq!(parts(&p), (MARKER.to_string(), PreviewSource::Marker, None));
}
#[test]
fn corpus_non_agent_tuis_keep_their_tiers() {
for (name, bytes) in [
(
"vim_session",
&include_bytes!("../../tests/corpus/vim_session.bin")[..],
),
(
"less_altscreen",
&include_bytes!("../../tests/corpus/less_altscreen.bin")[..],
),
] {
let cut = bytes
.windows(8)
.rposition(|w| w == b"\x1b[?1049l")
.expect("fixture exits the alt screen");
let mut emu = Emulator::new(40, 120, 2000);
emu.process(&bytes[..cut]);
assert!(emu.alternate_screen(), "{name}: alt screen active at cut");
let mut st = PreviewState::new();
let with = st
.resolve(Instant::now(), &emu, Some(&ClaudeSummary))
.clone();
let mut st = PreviewState::new();
let without = st.resolve(Instant::now(), &emu, None).clone();
assert_eq!(with, without, "{name}: the adapter must change nothing");
assert_eq!(with.source, PreviewSource::Marker, "{name}");
}
}