use super::{Bar, DESK, EMPTY, LINGER, NOTHING_AIMED, SHUT, cut, open_width};
use crate::paint_probe::{Seen, frame::Window, seen_of};
use crate::test_support::window::{conv, seated};
use crate::ui::{Model, theme};
mod foot;
pub(super) fn tick(
ctx: &egui::Context,
model: &mut Model,
time: f64,
at: Option<egui::Pos2>,
) -> egui::FullOutput {
let mut input = crate::paint_probe::frame::screen_sized(DESK, 900.0);
input.time = Some(time);
input.events = vec![at.map_or(egui::Event::PointerGone, egui::Event::PointerMoved)];
ctx.run(input, |ctx| crate::ui::render(ctx, model))
}
pub(super) fn bar(output: &egui::FullOutput) -> Vec<Seen> {
seen_of(output)
.into_iter()
.filter(|run| run.shown.min.x < open_width(DESK))
.collect()
}
const ROW: &str = "✔ port the paint probe";
pub(super) fn click_bar(window: &Window, model: &mut Model, text: &str) {
let mut body = |ctx: &egui::Context| crate::ui::render(ctx, model);
let at = bar(&window.frame(Vec::new(), &mut body))
.into_iter()
.find(|run| run.text == text)
.unwrap_or_else(|| panic!("the bar says {text:?}"))
.shown
.center();
crate::paint_probe::frame::click(window, at, &mut body);
}
pub(super) fn said(output: &egui::FullOutput) -> Vec<String> {
bar(output).into_iter().map(|run| run.text).collect()
}
#[test]
fn the_linger_runs_from_when_the_pointer_left() {
let mut bar = Bar::default();
assert!(!bar.open(0.0), "shut at rest");
assert_eq!(bar.heard(0.0, false), None);
assert_eq!(bar.heard(1.0, true), None);
assert!(bar.open(1.0));
assert_eq!(bar.heard(2.0, false), Some(0.5), "half a second left");
assert!(bar.open(2.0), "lingering");
assert!(!bar.open(1.0 + LINGER));
assert_eq!(bar.heard(1.0 + LINGER, false), None);
assert_eq!(bar.left_at, None, "forgotten once lapsed");
bar.pinned = true;
assert!(bar.open(9.0), "pinned");
bar.left_at = Some(9.0);
assert_eq!(bar.heard(9.5, false), None, "a pinned bar owes no repaint");
}
#[test]
fn the_widths_are_a_quarter_of_the_desk_and_a_glyph_column() {
let sizes = crate::snapshot::SIZES;
let near = |a: f32, b: f32| (a - b).abs() < 0.01;
assert!(near(sizes[2].1, DESK), "the desk is the matrix's");
assert!(near(open_width(sizes[2].1), 350.0));
assert!(near(open_width(sizes[1].1), 350.0));
assert!(
near(open_width(sizes[0].1), 160.0),
"two fifths of the phone"
);
assert_eq!(cut("port the paint probe"), "por");
assert_eq!(cut("ab"), "ab");
let ctx = egui::Context::default();
let shut = tick(&ctx, &mut seated(), 0.0, None);
let row = bar(&shut)
.into_iter()
.find(|run| run.text.ends_with("por"))
.expect("the collapsed row");
assert_eq!(row.text, format!("{} por", theme::glyph::AT_REST));
assert_eq!(row.shown, row.laid, "uncut");
assert!(row.laid.max.x <= SHUT, "inside the glyph column");
}
#[test]
fn the_pointer_opens_it_and_it_lingers_after() {
let ctx = egui::Context::default();
let mut model = seated();
let open = |output: &egui::FullOutput| said(output).contains(&ROW.to_owned());
assert!(!open(&tick(&ctx, &mut model, 0.0, None)), "shut at rest");
let over = Some(egui::pos2(10.0, 300.0));
assert!(open(&tick(&ctx, &mut model, 1.0, over)), "open under it");
let inside = Some(egui::pos2(300.0, 300.0));
assert!(
open(&tick(&ctx, &mut model, 3.0, inside)),
"a pointer resting in the open bar keeps it open"
);
let away = Some(egui::pos2(900.0, 300.0));
let left = tick(&ctx, &mut model, 3.5, away);
assert!(open(&left), "lingering");
let owed = left.viewport_output[&egui::ViewportId::ROOT].repaint_delay;
assert!(
owed <= std::time::Duration::from_secs_f64(LINGER),
"{owed:?}"
);
assert!(open(&tick(&ctx, &mut model, 4.4, away)), "still lingering");
assert!(!open(&tick(&ctx, &mut model, 4.6, away)), "lapsed");
assert!(
!open(&tick(&ctx, &mut model, 6.0, None)),
"the pointer gone"
);
}
#[test]
fn the_pin_holds_it_open_and_says_what_it_would_do() {
let window = Window::new();
let mut model = seated();
let mut body = |ctx: &egui::Context| crate::ui::render(ctx, &mut model);
let shut = window.text(&mut body);
assert!(shut.lines().any(|line| line == theme::glyph::PIN), "{shut}");
crate::test_support::window::click(&window, theme::glyph::PIN, &mut body);
assert!(model.bar.pinned);
let ctx = egui::Context::default();
let pinned = said(&tick(&ctx, &mut model, 0.0, None));
let unpin = theme::worded(theme::glyph::PIN, super::UNPIN);
assert!(pinned.contains(&unpin), "{pinned:?}");
assert!(pinned.contains(&ROW.to_owned()), "{pinned:?}");
model.bar.pinned = false;
model.bar.left_at = Some(0.0);
let open = said(&tick(&ctx, &mut model, 0.5, None));
let pin = theme::worded(theme::glyph::PIN, super::PIN);
assert!(open.contains(&pin), "{open:?}");
}
#[test]
fn an_open_row_says_its_state_name_age_and_first_line_and_a_click_selects() {
let mut model = seated();
model.bar.pinned = true;
let mut child = conv("20260830T051300Z-c3d4", "the child");
child.depth = 1;
child.preview = "first line\nsecond line".to_owned();
child.attention = 1;
model.convs.push(child);
model.opened.insert("20260830T051200Z-a1b2".to_owned());
let ctx = egui::Context::default();
let runs = bar(&tick(&ctx, &mut model, 0.0, None));
let find = |text: &str| {
runs.iter()
.find(|run| run.text == text)
.unwrap_or_else(|| panic!("{text:?} in {runs:?}"))
};
let selected = find(ROW);
let quiet = theme::state_ink(&crate::reply::convs::AgentState::Quiescent);
assert_eq!(selected.ink, quiet, "the glyph in its state's ink");
let asking = find("? the child");
assert_eq!(asking.ink, theme::accent(theme::State::Attention));
assert!(asking.laid.min.x > selected.laid.min.x, "indented");
assert_eq!(find("42s").ink, theme::INK_WEAK);
assert_eq!(find("first line").ink, theme::INK_WEAK);
assert!(!runs.iter().any(|run| run.text.contains("second line")));
let window = Window::sized(DESK, 900.0);
let mut body = |ctx: &egui::Context| crate::ui::render(ctx, &mut model);
crate::test_support::window::click(&window, "? the child", &mut body);
assert_eq!(model.conversation.as_deref(), Some("20260830T051300Z-c3d4"));
}
#[test]
fn an_empty_list_says_why_open_and_nothing_shut() {
let ctx = egui::Context::default();
let mut model = seated();
model.convs.clear();
model.conversation = None;
model.answered.clone_from(&model.aim);
let shut = said(&tick(&ctx, &mut model, 0.0, None));
assert!(!shut.iter().any(|t| t == EMPTY), "{shut:?}");
model.bar.pinned = true;
let empty = said(&tick(&ctx, &mut model, 0.0, None));
assert!(empty.iter().any(|t| t == EMPTY), "{empty:?}");
model.answered = None;
let waiting = said(&tick(&ctx, &mut model, 0.0, None));
assert!(
waiting.iter().any(|t| t == crate::ui::convs::NOT_ANSWERED),
"{waiting:?}"
);
let mut nowhere = Model::default();
nowhere.bar.pinned = true;
let unaimed = said(&tick(&ctx, &mut nowhere, 0.0, None));
assert!(unaimed.iter().any(|t| t == NOTHING_AIMED), "{unaimed:?}");
assert!(!unaimed.iter().any(|t| t == crate::ui::roster::wall::NEW));
}
#[test]
fn new_begins_on_the_aimed_wall_and_stands_down_under_a_cover() {
let window = Window::new();
let mut model = seated();
model.switching = true;
let ctx = egui::Context::default();
let shut = said(&tick(&ctx, &mut model, 0.0, None));
assert!(shut.iter().any(|t| t == super::PLUS), "collapsed: {shut:?}");
model.bar.pinned = true;
click_bar(&window, &mut model, crate::ui::roster::wall::NEW);
assert_eq!(model.conversation, None, "the selection is put down");
assert!(!model.switching, "the switcher closed on the way");
window.frame(Vec::new(), |ctx| crate::ui::render(ctx, &mut model));
assert_eq!(
window.focused(),
Some(egui::Id::new(crate::ui::keys::BOX_ID)),
"the caret is in the box that begins one"
);
model.begin_trail();
model.bar.pinned = false;
let covered = said(&tick(&ctx, &mut model, 0.0, None));
assert!(!covered.iter().any(|t| t == super::PLUS), "{covered:?}");
let mut enrolling = seated();
enrolling.enroll = enrolling.aim.clone().map(crate::ui::Enrolling::at);
let none = said(&tick(&ctx, &mut enrolling, 0.0, None));
assert!(!none.iter().any(|t| t == theme::glyph::PIN), "{none:?}");
}
#[test]
fn the_rows_and_new_wear_their_tags_and_the_pin_none() {
use egui_kittest::kittest::Queryable;
let mut model = seated();
model.bar.pinned = true;
let harness = crate::snapshot::seat(model, DESK, 900.0);
let tag = |label: &str| {
harness
.query_all_by_label(label)
.find_map(|node| node.author_id().map(str::to_owned))
};
let prefix = crate::ui::act::PREFIX;
let row = tag(ROW).expect("the row is tagged");
assert!(row.starts_with(&format!("{prefix}{}", crate::verbs::TRANSCRIPT.word)));
let new = tag(crate::ui::roster::wall::NEW).expect("+ new is tagged");
assert!(new.contains(crate::verbs::PREPARE), "{new}");
let unpin = theme::worded(theme::glyph::PIN, super::UNPIN);
assert_eq!(tag(&unpin), None, "the pin posts nothing");
}