use super::{leads_to, straight};
use crate::paint_probe::frame::Window;
use crate::reply::convs::ConvRow;
use crate::test_support::window::{click, conv, right_click, seated};
use crate::ui::composer::acts::{DELETE, FLAG, RETARGET, STOP};
use crate::ui::{Column, Model, convs, keys, queue, records};
use egui_kittest::Harness;
use egui_kittest::kittest::{Queryable, by};
fn asking() -> ConvRow {
ConvRow {
attention: 1,
..conv("20260830T051200Z-a1b2", "one")
}
}
fn narrow() -> Model {
Model {
column: Column::Conversations,
convs: vec![asking()],
..seated()
}
}
fn row() -> (Window, String) {
(Window::sized(400.0, 800.0), convs::headline(&asking()))
}
#[test]
fn a_secondary_click_opens_the_row_s_acts_and_nothing_else_does() {
let (window, label) = row();
let mut model = narrow();
let quiet = window.text(|ctx| crate::ui::render(ctx, &mut model));
assert!(!quiet.contains(&leads_to(DELETE)), "{quiet}");
assert!(!quiet.contains(STOP), "{quiet}");
right_click(&window, &label, |ctx| crate::ui::render(ctx, &mut model));
let open = window.text(|ctx| crate::ui::render(ctx, &mut model));
for word in [STOP, RETARGET, queue::SEEN, records::OPEN] {
assert!(open.contains(word), "{word:?} is on the menu: {open}");
}
for word in [FLAG, DELETE] {
assert!(
open.contains(&leads_to(word)) && !open.contains(&format!("{word} ")),
"{word:?} leads somewhere and says so: {open}"
);
}
}
#[test]
fn seen_rides_only_on_a_row_that_is_asking() {
let words = |row: &ConvRow| -> Vec<&'static str> {
straight(row).into_iter().map(|(word, _, _)| word).collect()
};
assert_eq!(words(&asking()), vec![STOP, RETARGET, queue::SEEN]);
assert_eq!(words(&conv("id", "quiet")), vec![STOP, RETARGET]);
}
#[test]
fn a_fired_item_composes_the_row_s_own_gesture_and_moves_no_selection() {
for (word, op) in [
(STOP, crate::verbs::STOP.word),
(RETARGET, crate::verbs::RETARGET.word),
(queue::SEEN, crate::verbs::SEEN.word),
] {
let (window, label) = row();
let mut model = Model {
conversation: Some("something else".to_owned()),
..narrow()
};
right_click(&window, &label, |ctx| crate::ui::render(ctx, &mut model));
click(&window, word, |ctx| crate::ui::render(ctx, &mut model));
let posted = model.outbox.first().unwrap_or_else(|| {
panic!("{word:?} composed a gesture");
});
assert!(posted.act, "{word:?} changes the world");
assert_eq!(posted.envelope["op"], op);
assert_eq!(posted.envelope["workspace"], "home");
assert_eq!(posted.envelope["agent"], asking().root_id);
assert_eq!(
model.conversation.as_deref(),
Some("something else"),
"{word:?} took no selection"
);
}
}
#[test]
fn the_records_item_selects_the_row_and_opens_the_pane_on_it() {
let (window, label) = row();
let mut model = Model {
conversation: Some("something else".to_owned()),
..narrow()
};
right_click(&window, &label, |ctx| crate::ui::render(ctx, &mut model));
click(&window, records::OPEN, |ctx| {
crate::ui::render(ctx, &mut model);
});
assert_eq!(
model.conversation.as_deref(),
Some(asking().root_id.as_str())
);
assert!(model.records, "the pane is open");
assert!(model.outbox.is_empty(), "opening a pane composes nothing");
}
#[test]
fn flag_and_delete_land_the_cursor_in_their_box_and_ask_for_nothing() {
for (word, id) in [(FLAG, keys::REASON_ID), (DELETE, keys::ARM_ID)] {
let (window, label) = row();
let mut model = narrow();
right_click(&window, &label, |ctx| crate::ui::render(ctx, &mut model));
click(&window, &leads_to(word), |ctx| {
crate::ui::render(ctx, &mut model);
});
for _ in 0..2 {
window.text(|ctx| crate::ui::render(ctx, &mut model));
}
assert_eq!(model.column, Column::Conversation, "{word:?}");
assert_eq!(
model.conversation.as_deref(),
Some(asking().root_id.as_str()),
"{word:?}"
);
assert!(model.outbox.is_empty(), "{word:?} fired nothing");
assert_eq!(
window.focused(),
Some(egui::Id::new(id)),
"{word:?} put the cursor in its box"
);
}
}
#[test]
fn the_menu_s_acts_carry_their_tags_on_top_of_the_composer_s() {
let mut harness = crate::snapshot::seat(narrow(), 1400.0, 900.0);
let before = counts(&harness);
let aimed = at(&harness, &convs::headline(&asking()));
secondary(&mut harness, aimed);
let after = counts(&harness);
for (op, was, now) in [
(crate::verbs::STOP.word, before.0, after.0),
(crate::verbs::SEEN.word, before.1, after.1),
(crate::verbs::STEPS.word, before.2, after.2),
] {
assert_eq!(now, was + 1, "{op:?} gained the menu's control");
}
assert_eq!(before.1, 0, "`seen` is on no other control of this screen");
}
fn counts(harness: &Harness<'_, Model>) -> (usize, usize, usize) {
let tally = |op: &str| {
let token = format!("{}{op}", crate::ui::act::PREFIX);
harness
.query_all(by())
.filter(|node| !node.is_hidden())
.filter_map(|node| node.author_id().map(str::to_owned))
.filter(|author| author.split_whitespace().any(|held| held == token))
.count()
};
(
tally(crate::verbs::STOP.word),
tally(crate::verbs::SEEN.word),
tally(crate::verbs::STEPS.word),
)
}
fn at(harness: &Harness<'_, Model>, label: &str) -> egui::Pos2 {
let bounds = harness
.query_all(by())
.filter(|node| node.label().or_else(|| node.value()).as_deref() == Some(label))
.find_map(|node| node.bounding_box())
.expect("the row is in the accessibility tree");
egui::pos2(
whole(f64::midpoint(bounds.x0, bounds.x1)),
whole(f64::midpoint(bounds.y0, bounds.y1)),
)
}
fn whole(v: f64) -> f32 {
const BOUND: u16 = 4096;
f32::from(
(0..=BOUND)
.rev()
.find(|&n| f64::from(n) <= v)
.unwrap_or_default(),
)
}
fn secondary(harness: &mut Harness<'_, Model>, at: egui::Pos2) {
harness
.input_mut()
.events
.push(egui::Event::PointerMoved(at));
harness.step();
for pressed in [true, false] {
harness.input_mut().events.push(egui::Event::PointerButton {
pos: at,
button: egui::PointerButton::Secondary,
pressed,
modifiers: egui::Modifiers::NONE,
});
harness.step();
}
harness.run();
}