mod center;
mod planes;
mod spell;
use super::*;
fn bare_press(key: Key) -> Option<KeyAction> {
keymap(key, Mods::Bare, Held::Nothing)
}
fn combo(key: Key) -> Option<KeyAction> {
keymap(key, Mods::Command, Held::TextBox)
}
#[test]
fn vertical_arrows_step_the_visible_list() {
assert_eq!(bare_press(Key::Up), Some(KeyAction::ListPrev));
assert_eq!(bare_press(Key::Down), Some(KeyAction::ListNext));
}
#[test]
fn horizontal_arrows_unfold_the_selected_row_on_both_planes() {
assert_eq!(bare_press(Key::Right), Some(KeyAction::ExpandRow));
assert_eq!(bare_press(Key::Left), Some(KeyAction::CollapseRow));
assert_eq!(combo(Key::Right), Some(KeyAction::ExpandRow));
assert_eq!(combo(Key::Left), Some(KeyAction::CollapseRow));
assert_eq!(
keymap(Key::Right, Mods::Bare, Held::TextBox),
None,
"the bare arrow stays out of the caret's way — that is why the combo exists"
);
}
#[test]
fn ctrl_arrows_continue_the_walk_from_inside_the_box() {
assert_eq!(combo(Key::Up), Some(KeyAction::ListPrev));
assert_eq!(combo(Key::Down), Some(KeyAction::ListNext));
assert_eq!(
keymap(Key::Up, Mods::Bare, Held::TextBox),
None,
"the bare arrow still yields to typing — that is why the combo exists"
);
for key in [Key::Up, Key::Down] {
assert_eq!(
keymap(key, Mods::CommandShift, Held::Nothing),
None,
"{key:?} rides the plain Command plane only"
);
}
}
#[test]
fn enter_fires_the_pending_goal_and_escape_cancels_it() {
assert_eq!(bare_press(Key::Enter), Some(KeyAction::Fire));
assert_eq!(bare_press(Key::Escape), Some(KeyAction::Cancel));
}
#[test]
fn letters_fire_the_altitude_gestures() {
let want = [
('i', KeyAction::FocusComposer),
('n', KeyAction::NewConversation),
('w', KeyAction::NewWorkspace),
('s', KeyAction::StartHead),
('x', KeyAction::Stop),
('f', KeyAction::Scan),
('c', KeyAction::CloseBall),
('r', KeyAction::ReleaseBall),
('b', KeyAction::ToggleBalls),
('m', KeyAction::ToggleModelPicker),
('g', KeyAction::ToggleGrouping),
('a', KeyAction::ToggleActivity),
];
for (c, action) in want {
assert_eq!(
bare_press(Key::Char(c)),
Some(action),
"`{c}` binds {action:?}"
);
}
let actions: Vec<KeyAction> = want.iter().map(|(_, a)| *a).collect();
for (i, a) in actions.iter().enumerate() {
assert_eq!(
actions.iter().filter(|b| *b == a).count(),
1,
"intent {a:?} is bound once (entry {i})"
);
}
}
#[test]
fn unbound_letters_map_to_nothing() {
for c in ['q', 'z', 'e', 'v', 'k', 'j', 'I', 'N', 'M'] {
assert_eq!(bare_press(Key::Char(c)), None, "`{c}` binds nothing");
}
}
#[test]
fn the_command_plane_pairs_the_safe_gestures() {
let want = [
('i', KeyAction::FocusComposer),
('n', KeyAction::NewConversation),
('b', KeyAction::ToggleBalls),
('g', KeyAction::ToggleGrouping),
('j', KeyAction::ToggleActivity),
];
for (c, action) in want {
assert_eq!(
combo(Key::Char(c)),
Some(action),
"Ctrl+{c} binds {action:?}"
);
}
for n in 1..=6 {
let tab = InspectorTab::from_digit(n);
assert_eq!(combo(Key::Digit(n)), tab.map(KeyAction::Tab));
}
assert_eq!(combo(Key::Digit(7)), None, "Ctrl+7 selects no tab");
}
#[test]
fn command_shift_carries_the_workspace_raise_and_the_center_tabs() {
assert_eq!(
keymap(Key::Char('n'), Mods::CommandShift, Held::TextBox),
Some(KeyAction::NewWorkspace)
);
for key in [Key::Char('i'), Key::Char('w'), Key::Enter] {
assert_eq!(
keymap(key, Mods::CommandShift, Held::Nothing),
None,
"{key:?}"
);
}
}
#[test]
fn no_combo_fires_a_selection_verb_or_steals_a_text_box_key() {
let selection_verbs = [
KeyAction::StartHead,
KeyAction::Stop,
KeyAction::Scan,
KeyAction::CloseBall,
KeyAction::ReleaseBall,
KeyAction::Fire,
KeyAction::Cancel,
];
for c in 'a'..='z' {
for mods in [Mods::Command, Mods::CommandShift] {
let action = keymap(Key::Char(c), mods, Held::Nothing);
for verb in selection_verbs {
assert_ne!(action, Some(verb), "{mods:?}+{c} must not fire {verb:?}");
}
}
}
for c in ['a', 'z', 'y', 'c', 'x', 'v'] {
assert_eq!(
combo(Key::Char(c)),
None,
"Ctrl+{c} belongs to the text box"
);
}
for c in ['r', 's', 'w'] {
assert_eq!(
combo(Key::Char(c)),
None,
"Ctrl+{c} binds nothing by design"
);
}
assert_eq!(combo(Key::Char('f')), Some(KeyAction::Search));
assert_eq!(
bare_press(Key::Char('f')),
Some(KeyAction::Scan),
"the bare plane keeps the mutation the letter always had"
);
}
#[test]
fn digits_one_through_six_select_the_inspector_tabs() {
let want = [
(1, InspectorTab::Transcript),
(2, InspectorTab::Steps),
(3, InspectorTab::Inbox),
(4, InspectorTab::Files),
(5, InspectorTab::Config),
(6, InspectorTab::Work),
];
for (n, tab) in want {
assert_eq!(bare_press(Key::Digit(n)), Some(KeyAction::Tab(tab)));
assert_eq!(InspectorTab::from_digit(n), Some(tab));
}
}
#[test]
fn digits_outside_one_through_six_are_unbound() {
assert_eq!(InspectorTab::from_digit(0), None);
for n in 7..=9 {
assert_eq!(bare_press(Key::Digit(n)), None, "digit {n} binds no tab");
}
assert_eq!(bare_press(Key::Digit(0)), None, "bare 0 is typing");
}
#[test]
fn zoom_is_the_browser_combo_and_nothing_bare() {
assert_eq!(combo(Key::Plus), Some(KeyAction::Zoom(ZoomStep::In)));
assert_eq!(combo(Key::Minus), Some(KeyAction::Zoom(ZoomStep::Out)));
assert_eq!(combo(Key::Digit(0)), Some(KeyAction::Zoom(ZoomStep::Reset)));
assert_eq!(
keymap(Key::Plus, Mods::CommandShift, Held::Nothing),
Some(KeyAction::Zoom(ZoomStep::In))
);
assert_eq!(
keymap(Key::Minus, Mods::CommandShift, Held::Nothing),
None,
"Ctrl+Shift+- binds nothing"
);
for key in [Key::Plus, Key::Minus] {
assert_eq!(bare_press(key), None, "{key:?} bare is typing, not zoom");
}
assert_eq!(
combo(Key::Digit(1)),
Some(KeyAction::Tab(InspectorTab::Transcript))
);
}
#[test]
fn all_lists_the_six_tabs_in_order_and_labels_each() {
let all = InspectorTab::all();
assert_eq!(all.len(), 6);
assert_eq!(all[0], InspectorTab::default(), "Transcript is the default");
let labels: Vec<&str> = all.iter().map(|t| t.label()).collect();
assert_eq!(
labels,
vec!["Transcript", "Steps", "Inbox", "Files", "Config", "Work"]
);
let pinnable: Vec<&str> = all
.iter()
.filter(|t| t.pinnable())
.map(|t| t.label())
.collect();
assert_eq!(
pinnable,
vec!["Transcript", "Steps", "Inbox", "Files", "Config"]
);
for (i, tab) in all.into_iter().enumerate() {
assert_eq!(InspectorTab::from_digit(i as u8 + 1), Some(tab));
}
}