mod keys;
mod refund;
use super::{ASKING, HINT, NOWHERE, render, start};
use crate::paint_probe::frame::Window;
use crate::test_support::window::{pane, seated};
use crate::ui::Model;
use crate::ui::theme::glyph;
use serde_json::json;
#[test]
fn what_the_composer_is_for_follows_from_what_is_selected() {
for (model, expected) in [
(Model::default(), NOWHERE),
(
Model {
aim: None,
..seated()
},
NOWHERE,
),
(
Model {
conversation: None,
..seated()
},
start::goal("home", false).as_str(),
),
(seated(), HINT),
] {
let mut model = model;
let painted = pane(|ui| render(ui, &mut model));
assert!(
painted.lines().any(|line| line.starts_with(expected)),
"{expected:?}:\n{painted}"
);
}
}
fn enter(window: &Window, model: &mut Model) {
window.frame(Vec::new(), |ctx| {
egui::CentralPanel::default().show(ctx, |ui| render(ui, model));
});
window.focus(egui::Id::new(crate::ui::keys::BOX_ID));
window.frame(
vec![crate::paint_probe::frame::press(egui::Key::Enter)],
|ctx| {
egui::CentralPanel::default().show(ctx, |ui| render(ui, model));
},
);
}
#[test]
fn sending_composes_the_deposit_the_command_line_would_have_and_posts_nothing() {
let mut model = seated();
model.draft = "ship it".to_owned();
let window = Window::new();
enter(&window, &mut model);
assert_eq!(
model.outbox,
vec![crate::ui::Posted::act(
json!({"op": "message", "workspace": "home",
"agent": "20260830T051200Z-a1b2", "content": "ship it"})
)]
);
assert_eq!(model.draft, "", "what was sent is no longer a draft");
}
#[test]
fn cutting_composes_the_interrupt_off_the_slash_line() {
let mut model = seated();
assert!(crate::ui::slash::fire(
&mut model,
crate::verbs::INTERRUPT.word,
"no, this"
));
assert_eq!(
model.outbox,
vec![crate::ui::Posted::act(
json!({"op": "interrupt", "workspace": "home",
"agent": "20260830T051200Z-a1b2", "content": "no, this"})
)]
);
}
#[test]
fn an_empty_cut_fires_nothing_because_that_gesture_has_its_own_word() {
let mut model = seated();
assert!(!crate::ui::slash::fire(
&mut model,
crate::verbs::INTERRUPT.word,
""
));
assert!(model.outbox.is_empty(), "{:?}", model.outbox);
}
#[test]
fn an_empty_draft_fires_nothing_and_costs_nothing() {
let mut model = seated();
model.draft = " ".to_owned();
let window = Window::new();
enter(&window, &mut model);
assert!(model.outbox.is_empty(), "{:?}", model.outbox);
assert_eq!(model.draft, " ", "the draft is still the operator's");
}
#[test]
fn the_deposit_carries_the_address_the_channel_resolves() {
let mut model = seated();
model.aim = Some(crate::ui::Aim {
channel: "home".to_owned(),
address: "home".to_owned(),
});
model.draft = "ship it".to_owned();
let window = Window::new();
enter(&window, &mut model);
assert_eq!(model.outbox[0].envelope["workspace"], json!("home"));
}
#[test]
fn the_advance_composes_its_own_gesture_and_takes_no_draft() {
let mut model = seated();
model.draft = "not this".to_owned();
assert!(crate::ui::slash::fire(
&mut model,
crate::verbs::NUDGE.word,
""
));
assert_eq!(
model.outbox,
vec![crate::ui::Posted::act(
json!({"op": "nudge", "workspace": "home",
"agent": "20260830T051200Z-a1b2"})
)]
);
assert_eq!(model.draft, "not this", "the draft is untouched");
}
#[test]
fn a_started_conversation_the_engine_cannot_resolve_yet_has_no_box() {
let mut model = Model {
conversation: Some("brisk-otter".to_owned()),
start: Some(crate::ui::model::Start {
address: "home".to_owned(),
goal: "port it".to_owned(),
phase: crate::ui::model::Phase::Started("brisk-otter".to_owned()),
spread: None,
}),
..seated()
};
let painted = pane(|ui| render(ui, &mut model));
assert!(
painted.contains("started «brisk-otter» in home"),
"{painted}"
);
for gone in [HINT, glyph::PROMPT] {
assert!(
!painted.lines().any(|line| line == gone),
"{gone:?} is still on the glass: {painted}"
);
}
assert!(model.outbox.is_empty());
}
#[test]
fn the_field_glows_while_the_conversation_is_asking_and_the_glyph_wears_the_brand() {
let glow = crate::ui::theme::tint(crate::ui::theme::State::Attention);
for (attention, glows) in [(0, false), (2, true)] {
let mut model = seated();
model.convs[0].attention = attention;
let window = Window::new();
let output = window.frame(Vec::new(), |ctx| {
egui::CentralPanel::default().show(ctx, |ui| render(ui, &mut model));
});
let glowing = crate::paint_probe::fills_of(&output)
.iter()
.any(|(_, ink)| *ink == glow);
assert_eq!(glowing, glows, "attention {attention}");
let hint = if glows { ASKING } else { HINT };
assert!(
crate::paint_probe::seen_of(&output)
.iter()
.any(|run| run.text.starts_with(hint)),
"the hint reads {hint:?}"
);
let lead = crate::paint_probe::seen_of(&output)
.into_iter()
.rfind(|run| run.text == glyph::PROMPT)
.expect("the glyph is on the glass");
assert_eq!(lead.ink, crate::ui::theme::BRAND);
}
}
#[test]
fn the_hint_names_the_ways_in_and_drops_them_from_the_right() {
let mut wide = String::new();
let mut narrow = String::new();
let window = crate::paint_probe::frame::Window::new();
window.frame(Vec::new(), |ctx| {
egui::CentralPanel::default().show(ctx, |ui| {
wide = super::hint(ui, HINT, &super::parts());
ui.allocate_ui(egui::vec2(40.0, 20.0), |ui| {
narrow = super::hint(ui, HINT, &super::parts());
});
});
});
for way in [
"Ctrl+X for a conversation",
"/roles for the model",
"/login for a provider",
] {
assert!(wide.contains(way), "{way:?}: {wide}");
}
assert_eq!(narrow, HINT);
}