use sparrow::tui::theme::{THEME_NAMES, by_name};
#[test]
fn three_built_in_themes_are_resolvable_by_name() {
assert_eq!(THEME_NAMES, &["captain", "midnight", "paper"]);
let cap = by_name("captain");
let mid = by_name("midnight");
let pap = by_name("paper");
assert_ne!(cap.bg, mid.bg);
assert_ne!(cap.bg, pap.bg);
assert_ne!(mid.bg, pap.bg);
}
#[test]
fn unknown_theme_name_falls_back_to_captain() {
let unknown = by_name("does-not-exist");
let captain = by_name("captain");
assert_eq!(unknown.bg, captain.bg);
assert_eq!(unknown.brand, captain.brand);
}
#[test]
fn theme_lookup_is_case_insensitive_and_trims() {
let a = by_name("Midnight");
let b = by_name(" MIDNIGHT ");
let c = by_name("midnight");
assert_eq!(a.bg, b.bg);
assert_eq!(b.bg, c.bg);
}
#[test]
fn keyboard_doc_lists_critical_shortcuts() {
let doc = std::fs::read_to_string("docs/keyboard.md")
.expect("docs/keyboard.md must ship with the TUI");
for shortcut in [
"Ctrl+I",
"Ctrl+L",
"Ctrl+O",
"Shift+Enter",
"Tab",
"Up",
"Down",
] {
assert!(
doc.contains(shortcut),
"docs/keyboard.md must document {}",
shortcut
);
}
for theme in ["captain", "midnight", "paper"] {
assert!(doc.contains(theme), "docs/keyboard.md must list {}", theme);
}
for shortcut in [
"Cmd/Ctrl+K",
"Cmd/Ctrl+Shift+L",
"Cmd/Ctrl+M",
"Drag files onto page",
"@",
] {
assert!(
doc.contains(shortcut),
"docs/keyboard.md must document WebView shortcut {}",
shortcut
);
}
}
#[test]
fn console_html_has_dynamic_swarm_hooks() {
let html =
std::fs::read_to_string("console.html").expect("console.html must ship with the WebView");
assert!(
html.contains("swarm-extras-anchor"),
"console.html must expose #swarm-extras-anchor for dynamic agent lanes"
);
assert!(
html.contains("swarm-more"),
"console.html must expose #swarm-more for the +N overflow chip"
);
assert!(
html.contains("loadSwarmAgents"),
"console.html must call loadSwarmAgents() on connect"
);
let lane_msg_block = html
.split(".lane .msg")
.nth(1)
.expect("expected `.lane .msg` CSS block");
let first_rule = lane_msg_block.split('}').next().unwrap_or("");
assert!(
!first_rule.contains("text-overflow:ellipsis"),
"`.lane .msg` must not truncate text in the new swarm row, got: {}",
first_rule
);
}
#[test]
fn console_html_has_drawer_with_seven_panels() {
let html =
std::fs::read_to_string("console.html").expect("console.html must ship with the WebView");
for panel in [
"sessions",
"memory",
"plugins",
"tools",
"permissions",
"security",
"artifacts",
] {
let rail_marker = format!("data-panel=\"{}\"", panel);
let body_marker = format!("data-panel-body=\"{}\"", panel);
assert!(
html.contains(&rail_marker),
"rail must expose button for {}",
panel
);
assert!(
html.contains(&body_marker),
"drawer must expose body for {}",
panel
);
}
assert!(
html.contains("PANEL_LOADERS"),
"panel loader registry must be present"
);
assert!(
html.contains("openPanel"),
"openPanel() switch handler must be present"
);
}
#[test]
fn console_html_has_paper_theme_and_chrome_chips() {
let html =
std::fs::read_to_string("console.html").expect("console.html must ship with the WebView");
assert!(
html.contains("[data-theme=\"paper\"]"),
"console.html must declare a paper theme block"
);
for chip in ["cmdkBtn", "replayBtn", "soundBtn", "themeBtn"] {
let marker = format!("id=\"{}\"", chip);
assert!(html.contains(&marker), "chrome must expose {} chip", chip);
}
assert!(
html.contains("injectHero") && html.contains(".hero{"),
"hero welcome must be injected at term boot"
);
assert!(
html.contains("runBootAnimation") && html.contains("bootOverlay"),
"boot overlay must be present and triggered"
);
assert!(
html.contains("function chirp(") && html.contains("'sparrow-muted'"),
"WebAudio chirp + mute persistence must be wired"
);
}
#[test]
fn console_html_has_slash_palette_and_agent_picker() {
let html =
std::fs::read_to_string("console.html").expect("console.html must ship with the WebView");
assert!(
html.contains("id=\"palette\""),
"console.html must declare the slash palette modal"
);
assert!(
html.contains("paletteOpen") && html.contains("paletteFilter"),
"palette open / filter functions must be present"
);
assert!(
html.contains("key.toLowerCase()==='k'"),
"Cmd/Ctrl+K shortcut must be wired"
);
assert!(
html.contains("id=\"agentPicker\""),
"console.html must declare the inline @-picker"
);
assert!(
html.contains("agentPickerState") && html.contains("agentPickerAccept"),
"@-picker state/accept helpers must be present"
);
assert!(
html.contains("loadCommandsCache") && html.contains("loadAgentsCache"),
"both /commands and /agents must be pre-fetched on connect"
);
assert!(
html.contains("cmd.usage") && html.contains("paletteSourceLabel"),
"slash palette and /help must render command usage plus readable sources"
);
assert!(
html.contains("runWebviewCliCommand") && html.contains("fetch('/cli'"),
"unknown slash commands must be executable through the WebView CLI bridge"
);
}
#[test]
fn console_html_has_sprint2_composer_hooks() {
let html =
std::fs::read_to_string("console.html").expect("console.html must ship with the WebView");
assert!(
html.contains("<textarea id=\"taskInput\""),
"composer must use a textarea for multi-line Shift+Enter input"
);
for marker in [
"loadHistoryCache",
"fetch('/history?limit=80')",
"composerKeydown",
"sparrow-composer-draft",
"composerPaste",
"dropZone",
"attachFiles",
"fetch('/upload'",
"MAX_ATTACHMENT_BYTES",
] {
assert!(
html.contains(marker),
"console.html must expose Sprint 2 composer hook `{}`",
marker
);
}
}
#[test]
fn console_html_has_sprint3_micro_animation_hooks() {
let html =
std::fs::read_to_string("console.html").expect("console.html must ship with the WebView");
for marker in [
"approvalModal",
"showApprovalModal",
"resolveApprovalFromModal",
"error-banner",
"showError",
"checkpoint-timeline",
"addCheckpointNode",
"prefers-reduced-motion: reduce",
"fold-in",
] {
assert!(
html.contains(marker),
"console.html must expose Sprint 3 hook `{}`",
marker
);
}
}
#[test]
fn console_html_matches_v0_3_visual_polish_contract() {
let html =
std::fs::read_to_string("console.html").expect("console.html must ship with the WebView");
for marker in [
"v0.3.6",
"get('boot')==='0'",
".boot-overlay[hidden]",
"route-step",
"class=\"arrow\"",
"drawer-in",
"drw-row.cur",
"_none yet_ · unable to load /sessions",
":root[data-theme=\"paper\"] .context-track",
] {
assert!(
html.contains(marker),
"console.html must keep v0.3 polish marker `{}`",
marker
);
}
}
#[test]
fn console_html_has_typed_event_renderers() {
let html =
std::fs::read_to_string("console.html").expect("console.html must ship with the WebView");
for needle in [
".tool-card",
".diff-card",
".compact-banner",
".skill-pop",
".streaming::after",
] {
assert!(
html.contains(needle),
"console.html must define the `{}` renderer block",
needle
);
}
for case in [
"case 'ToolUseProposed'",
"case 'ToolOutput'",
"case 'DiffProposed'",
"case 'Compacted'",
] {
assert!(
html.contains(case),
"handleEvent() must wire `{}` into the new renderers",
case
);
}
assert!(
html.contains("renderDiffCard") && html.contains("renderCompactBanner"),
"diff + compact renderers must be implemented"
);
}
#[test]
fn console_html_plan_mode_has_explicit_accept_edit_reject() {
let html =
std::fs::read_to_string("console.html").expect("console.html must ship with the WebView");
for marker in [
"function renderPlan",
"data-plan-action=\"run\"",
"data-plan-action=\"edit\"",
"data-plan-action=\"reject\"",
"function rejectPlan",
"plan rejected",
] {
assert!(html.contains(marker), "plan mode must expose `{}`", marker);
}
}
#[test]
fn console_html_diff_view_exposes_per_hunk_controls() {
let html =
std::fs::read_to_string("console.html").expect("console.html must ship with the WebView");
for marker in [
"function parseDiffHunks",
"function renderHunkedDiff",
"function bindHunkControls",
"data-hunk-action=\"accept\"",
"data-hunk-action=\"reject\"",
"data-state=\"pending\"",
] {
assert!(
html.contains(marker),
"diff view must expose per-hunk marker `{}`",
marker
);
}
}
#[test]
fn console_html_styles_streamed_code_cards() {
let html =
std::fs::read_to_string("console.html").expect("console.html must ship with the WebView");
assert!(
html.contains("d.className='code-card streaming-code'"),
"streaming fenced code must render through the code-card component"
);
for marker in [
".code-card,.code-block",
".code-card summary",
".code-card summary::-webkit-details-marker",
".code-card .cc-copy",
".code-card pre",
".code-card code",
".syn-key",
"function highlightCode",
"applyCodeHighlight(card)",
"if(!raw.trim())",
] {
assert!(
html.contains(marker),
"console.html must style code card marker `{}`",
marker
);
}
}
#[test]
fn console_html_keeps_cost_updates_out_of_the_transcript() {
let html =
std::fs::read_to_string("console.html").expect("console.html must ship with the WebView");
let cost_case = html
.split("case 'CostUpdate':")
.nth(1)
.expect("CostUpdate handler must exist")
.split("case 'TokenUsageEstimated':")
.next()
.expect("CostUpdate handler must be followed by TokenUsageEstimated");
assert!(
cost_case.contains("setCost(ev.usd)") && !cost_case.contains("verboseLine"),
"CostUpdate must update meters only, without adding transcript spam: {}",
cost_case
);
}
#[test]
fn console_html_uses_system_ui_typography() {
let html =
std::fs::read_to_string("console.html").expect("console.html must ship with the WebView");
for marker in [
"--ui-font:-apple-system",
"--mono-font:\"SF Mono\"",
"body{font-family:var(--ui-font)",
".code-card code,.code-block .cb-body code{font-family:var(--mono-font)",
] {
assert!(
html.contains(marker),
"console.html must keep Apple-style typography marker `{}`",
marker
);
}
}
#[test]
fn classify_agent_color_falls_back_to_steel() {
use sparrow::console::classify_agent_color;
assert_eq!(classify_agent_color("blue"), "planner");
assert_eq!(classify_agent_color("TEAL"), "coder");
assert_eq!(classify_agent_color("gold"), "gold");
assert_eq!(classify_agent_color("coral"), "coral");
assert_eq!(classify_agent_color("something-unknown"), "steel");
assert_eq!(classify_agent_color(""), "steel");
}
#[tokio::test]
async fn webview_app_builds_with_phase13_routes() {
use std::net::SocketAddr;
use tokio::sync::broadcast;
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let (tx, _rx) = broadcast::channel(16);
let server = sparrow::console::WebViewServer::new(addr, tx, None, None, None, None, None, None);
drop(server);
}