use super::seated;
use crate::paint_probe::frame::{Window, chord};
use crate::settings::SCALE;
use crate::test_support::window::{conv, said};
use crate::ui::chat::scroll;
use crate::ui::keys::{Act, BINDINGS, Binding, Chord, Keyed, chord_for, cited, cited_word};
use crate::ui::{Lookup, Model};
const ACTS: [Act; 17] = [
Act::Switch,
Act::Model,
Act::Dock,
Act::Settings,
Act::Help,
Act::Pin,
Act::New,
Act::CloseTab,
Act::NextTab,
Act::PrevTab,
Act::PageDown,
Act::PageUp,
Act::End,
Act::Head,
Act::ScaleUp,
Act::ScaleDown,
Act::ScaleReset,
];
fn chorded(window: &Window, model: &mut Model, key: egui::Key, modifiers: egui::Modifiers) {
window.frame(vec![chord(key, modifiers)], |ctx| {
crate::ui::render(ctx, model);
});
window.frame(Vec::new(), |ctx| crate::ui::render(ctx, model));
}
fn fire(window: &Window, model: &mut Model, act: Act) {
let row = BINDINGS
.iter()
.find_map(|row| match row.keyed {
Keyed::Fired(chord, fires) if fires == act => Some(chord),
_ => None,
})
.expect("every act has a row");
let modifiers = egui::Modifiers {
command: row.ctrl,
ctrl: row.ctrl,
shift: row.shift,
..egui::Modifiers::NONE
};
chorded(window, model, row.key, modifiers);
}
#[test]
fn every_act_has_a_row_and_the_chords_spell_as_the_operator_types_them() {
for act in ACTS {
assert!(!chord_for(act).is_empty(), "{act:?} has no row");
}
assert_eq!(chord_for(Act::Settings), "Ctrl+,");
assert_eq!(chord_for(Act::ScaleUp), "Ctrl+=");
assert_eq!(chord_for(Act::ScaleDown), "Ctrl+-");
assert_eq!(chord_for(Act::ScaleReset), "Ctrl+0");
assert_eq!(chord_for(Act::PageDown), "PageDown");
assert_eq!(chord_for(Act::Help), "F1");
let shifted = Chord {
key: egui::Key::Enter,
ctrl: false,
shift: true,
};
assert_eq!(shifted.spell(), "Shift+Enter");
assert_eq!(Chord::plain(egui::Key::Escape).spell(), "Esc");
let row = Binding::fired(Chord::ctrl(egui::Key::B), Act::Pin, "pins");
assert_eq!(row.says(), "Ctrl+B");
assert_eq!(Binding::told("Enter", "sends").says(), "Enter");
let told: Vec<String> = BINDINGS
.iter()
.filter(|row| matches!(row.keyed, Keyed::Told(_)))
.map(Binding::says)
.collect();
assert!(told.contains(&"Shift+Enter".to_owned()), "{told:?}");
}
#[test]
fn a_sentence_cites_its_chords_and_a_word_cites_the_chord_that_opens_it() {
assert_eq!(cited("does", &[]), "does");
assert_eq!(
cited("does", &[Act::Pin, Act::Dock]),
"does · Ctrl+B / Ctrl+T"
);
assert_eq!(
cited_word("opens", crate::ui::settings::WORD),
"opens · Ctrl+,"
);
assert_eq!(cited_word("opens", crate::ui::doctor::WORD), "opens");
}
#[test]
fn the_chords_open_the_switcher_the_model_the_dock_the_settings_and_help() {
let window = Window::new();
let mut model = seated();
fire(&window, &mut model, Act::Switch);
assert!(model.switching);
model.chose();
fire(&window, &mut model, Act::Model);
assert!(model.dropup.is_some(), "Ctrl+O opens the model's dropup");
model.escape();
fire(&window, &mut model, Act::Settings);
assert_eq!(model.lookup, Some(Lookup::Settings));
fire(&window, &mut model, Act::Help);
assert_eq!(model.lookup, Some(Lookup::Commands));
model.escape();
let before = crate::ui::dock::unfolded(window.ctx(), false);
fire(&window, &mut model, Act::Dock);
assert_ne!(crate::ui::dock::unfolded(window.ctx(), false), before);
}
#[test]
fn ctrl_b_pins_the_bar_and_ctrl_n_begins_where_plus_new_would() {
let window = Window::new();
let mut model = seated();
fire(&window, &mut model, Act::Pin);
assert!(model.bar.pinned);
fire(&window, &mut model, Act::Pin);
assert!(!model.bar.pinned);
assert!(model.conversation.is_some());
fire(&window, &mut model, Act::New);
assert_eq!(model.conversation, None, "a start is no conversation");
let mut covered = seated();
covered.lookup = Some(Lookup::Commands);
fire(&window, &mut covered, Act::New);
assert!(covered.conversation.is_some());
}
fn tabbed() -> Model {
let mut model = seated();
model
.convs
.push(conv("20260830T052000Z-b2c3", "tidy the rule"));
for root in ["20260830T051200Z-a1b2", "20260830T052000Z-b2c3"] {
model.select(root);
}
model
}
#[test]
fn the_tab_chords_step_the_strip_and_close_the_selected_tab() {
let window = Window::new();
let mut model = tabbed();
assert_eq!(model.conversation.as_deref(), Some("20260830T052000Z-b2c3"));
fire(&window, &mut model, Act::PrevTab);
assert_eq!(model.conversation.as_deref(), Some("20260830T051200Z-a1b2"));
fire(&window, &mut model, Act::PrevTab);
assert_eq!(
model.conversation.as_deref(),
Some("20260830T051200Z-a1b2"),
"the strip saturates"
);
fire(&window, &mut model, Act::NextTab);
assert_eq!(model.conversation.as_deref(), Some("20260830T052000Z-b2c3"));
fire(&window, &mut model, Act::CloseTab);
assert_eq!(model.strip().len(), 1);
assert_eq!(model.conversation.as_deref(), Some("20260830T051200Z-a1b2"));
model.conversation = None;
fire(&window, &mut model, Act::CloseTab);
assert_eq!(model.strip().len(), 1);
fire(&window, &mut model, Act::NextTab);
assert_eq!(model.conversation.as_deref(), Some("20260830T051200Z-a1b2"));
}
#[test]
fn the_scale_chords_move_the_knob_within_its_bounds_and_put_it_back() {
let window = Window::new();
let mut model = seated();
fire(&window, &mut model, Act::ScaleUp);
assert!(
(model.settings.scale - 1.1).abs() < 1e-5,
"{}",
model.settings.scale
);
fire(&window, &mut model, Act::ScaleDown);
assert!((model.settings.scale - SCALE).abs() < 1e-5);
model.settings.scale = 2.0;
fire(&window, &mut model, Act::ScaleUp);
assert!(
(model.settings.scale - 2.0).abs() < 1e-5,
"clamped at the ceiling"
);
fire(&window, &mut model, Act::ScaleReset);
assert!((model.settings.scale - SCALE).abs() < 1e-5);
chorded(
&window,
&mut model,
egui::Key::Plus,
egui::Modifiers::COMMAND,
);
assert!(
(model.settings.scale - 1.1).abs() < 1e-5,
"{}",
model.settings.scale
);
model.settings.scale = SCALE;
chorded(
&window,
&mut model,
egui::Key::Equals,
egui::Modifiers::NONE,
);
assert!((model.settings.scale - SCALE).abs() < 1e-5);
}
fn tall() -> Model {
let mut model = seated();
model.transcript.entries = (0..120)
.map(|i| said("op", &format!("line {i} of a long conversation")))
.collect();
model
}
#[test]
fn page_keys_scroll_the_transcript_and_the_ends_reach_its_ends() {
let window = Window::sized(800.0, 400.0);
let mut model = tall();
for _ in 0..3 {
window.frame(Vec::new(), |ctx| crate::ui::render(ctx, &mut model));
}
let bottom = scroll::offset(window.ctx()).expect("the transcript noted where it stands");
assert!(bottom > 400.0, "stuck to the bottom: {bottom}");
fire(&window, &mut model, Act::PageUp);
let up = scroll::offset(window.ctx()).expect("noted");
assert!(up < bottom, "a page back: {up} < {bottom}");
fire(&window, &mut model, Act::Head);
assert_eq!(scroll::offset(window.ctx()), Some(0.0));
fire(&window, &mut model, Act::PageDown);
let down = scroll::offset(window.ctx()).expect("noted");
assert!(down > 0.0 && down < bottom, "a page on: {down}");
fire(&window, &mut model, Act::End);
let end = scroll::offset(window.ctx()).expect("noted");
assert!((end - bottom).abs() < 1.0, "the end: {end} vs {bottom}");
model.lookup = Some(Lookup::Commands);
for _ in 0..2 {
window.frame(Vec::new(), |ctx| crate::ui::render(ctx, &mut model));
}
let under = scroll::offset(window.ctx()).expect("noted");
fire(&window, &mut model, Act::Head);
let held = scroll::offset(window.ctx()).expect("noted");
assert!(
(held - under).abs() < 1.0,
"held under the pane: {held} vs {under}"
);
}