mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-26 11:46:49 -06:00
152 lines
7.3 KiB
Rust
152 lines
7.3 KiB
Rust
//! Excel keyboard shortcuts (crate::keymap) dispatched through real egui key events.
|
|
|
|
use egui::{Event, Key, Modifiers};
|
|
use gridcraft_engine::Session;
|
|
use gridcraft_engine::core::CellRef;
|
|
use gridcraft_engine::model::BorderStyle;
|
|
use gridcraft_ui_egui::SheetApp;
|
|
use gridcraft_ui_egui::keymap::{Shortcut, lookup};
|
|
use serde_json::json;
|
|
|
|
type Harness = egui_kittest::Harness<'static, SheetApp>;
|
|
|
|
fn harness(session: Session) -> Harness {
|
|
let mut h = Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_ui_state(
|
|
|ui, app: &mut SheetApp| {
|
|
let ctx = ui.ctx().clone();
|
|
app.logic(&ctx);
|
|
app.ui(ui);
|
|
},
|
|
SheetApp::new(session, Default::default()),
|
|
);
|
|
h.run_steps(4);
|
|
h
|
|
}
|
|
|
|
fn book(selected: &str) -> Session {
|
|
let mut s = Session::new();
|
|
s.new_workbook();
|
|
s.execute("range.setValues", json!({"range": "A1", "values": [[1.5, 2], [3, 4], [5, 6]]})).unwrap();
|
|
s.execute("cell.set", json!({"cell": "C1", "input": "=A1*2"})).unwrap();
|
|
s.execute("selection.set", json!({"range": selected})).unwrap();
|
|
s
|
|
}
|
|
|
|
/// A key press as a US keyboard reports it (the physical key is the unshifted one).
|
|
fn key(h: &mut Harness, k: Key, m: Modifiers) {
|
|
h.input_mut().events.push(Event::Key { key: k, physical_key: Some(k), pressed: true, repeat: false, modifiers: m });
|
|
h.input_mut().events.push(Event::Key { key: k, physical_key: Some(k), pressed: false, repeat: false, modifiers: m });
|
|
h.run_steps(2);
|
|
}
|
|
|
|
fn style(h: &Harness, a1: &str) -> gridcraft_engine::model::Style {
|
|
let d = h.state().session.active().unwrap();
|
|
let sh = d.wb.active().unwrap();
|
|
d.wb.styles.get(sh.style_id(CellRef::parse(a1).unwrap())).clone()
|
|
}
|
|
|
|
/// Ctrl on Windows and Linux sets both `ctrl` and `command`.
|
|
const CTRL: Modifiers = Modifiers { alt: false, ctrl: true, shift: false, mac_cmd: false, command: true };
|
|
const CTRL_SHIFT: Modifiers = Modifiers { shift: true, ..CTRL };
|
|
|
|
#[test]
|
|
fn ctrl_shift_symbols_apply_number_formats_and_borders() {
|
|
let mut h = harness(book("A1:B2"));
|
|
key(&mut h, Key::Num4, CTRL_SHIFT);
|
|
assert_eq!(style(&h, "B2").num_fmt.as_str(), "$#,##0.00_);($#,##0.00)");
|
|
key(&mut h, Key::Num5, CTRL_SHIFT);
|
|
assert_eq!(style(&h, "A1").num_fmt.as_str(), "0%");
|
|
key(&mut h, Key::Backtick, CTRL_SHIFT);
|
|
assert_eq!(style(&h, "A1").num_fmt.as_str(), "General");
|
|
key(&mut h, Key::Num7, CTRL_SHIFT);
|
|
assert!(style(&h, "A1").borders.top.style != BorderStyle::None && style(&h, "B2").borders.right.style != BorderStyle::None);
|
|
key(&mut h, Key::Minus, CTRL_SHIFT);
|
|
assert!(style(&h, "A1").borders.top.style == BorderStyle::None);
|
|
// Ctrl+Shift+End extends from the anchor to the last used cell.
|
|
key(&mut h, Key::End, CTRL_SHIFT);
|
|
assert_eq!(h.state().session.active().unwrap().selection.current().a1(), "A1:C3");
|
|
}
|
|
|
|
#[test]
|
|
fn copy_formula_from_above_and_function_keys() {
|
|
let mut h = harness(book("C2"));
|
|
key(&mut h, Key::Quote, CTRL);
|
|
assert_eq!(h.state().editor.as_ref().map(|e| e.text.clone()).as_deref(), Some("=A1*2"));
|
|
key(&mut h, Key::Escape, Modifiers::NONE);
|
|
assert!(h.state().editor.is_none());
|
|
let sheets = h.state().session.active().unwrap().wb.sheets.len();
|
|
key(&mut h, Key::F11, Modifiers::SHIFT);
|
|
assert_eq!(h.state().session.active().unwrap().wb.sheets.len(), sheets + 1);
|
|
// Back on the data: Alt+F1 charts the current region, Ctrl+Alt+= zooms in.
|
|
h.state_mut().session.run("sheet.previous", json!({})).unwrap();
|
|
h.state_mut().session.run("selection.set", json!({"range": "A1:B3"})).unwrap();
|
|
key(&mut h, Key::F1, Modifiers::ALT);
|
|
assert_eq!(h.state().session.active().unwrap().wb.sheets[0].charts.len(), 1);
|
|
key(&mut h, Key::Equals, Modifiers { alt: true, ..CTRL });
|
|
assert_eq!(h.state().session.active().unwrap().wb.sheets[0].zoom, 110);
|
|
}
|
|
|
|
#[test]
|
|
fn windows_and_mac_variants() {
|
|
let cmd = CTRL;
|
|
let mac_cmd = Modifiers { mac_cmd: true, command: true, ..Modifiers::NONE };
|
|
let ctrl = Modifiers::CTRL;
|
|
let run = |id: &'static str| Some(Shortcut::Run(id, json!({})));
|
|
assert_eq!(lookup(Key::L, Some(Key::L), cmd, false), run("insert.table"));
|
|
assert_eq!(lookup(Key::L, Some(Key::L), mac_cmd, true), run("home.alignLeft"));
|
|
assert_eq!(lookup(Key::E, Some(Key::E), mac_cmd, true), run("home.alignCenter"));
|
|
assert_eq!(lookup(Key::E, Some(Key::E), ctrl, true), run("edit.flashFill"));
|
|
assert_eq!(lookup(Key::Equals, Some(Key::Equals), Modifiers::ALT, false), run("formulas.autoSum"));
|
|
// The shifted character doesn't matter: Ctrl+Shift+1 is the Number format wherever `!` is.
|
|
assert_eq!(
|
|
lookup(Key::Exclamationmark, Some(Key::Num1), CTRL_SHIFT, false),
|
|
Some(Shortcut::Run("home.numberFormat", json!({"code": "#,##0.00"})))
|
|
);
|
|
// Navigation keys go by what they are, not where: the numpad's arrows and Page Down (Num
|
|
// Lock off) report Num6 / Num3 as their physical keys.
|
|
let alt_shift = Modifiers { alt: true, shift: true, ..Modifiers::NONE };
|
|
assert_eq!(lookup(Key::ArrowRight, Some(Key::Num6), alt_shift, false), run("data.group"));
|
|
assert_eq!(lookup(Key::ArrowLeft, Some(Key::Num4), alt_shift, false), run("data.ungroup"));
|
|
assert_eq!(lookup(Key::PageDown, Some(Key::Num3), Modifiers::ALT, false), Some(Shortcut::ScreenRight));
|
|
assert_eq!(lookup(Key::PageDown, Some(Key::Num3), CTRL_SHIFT, false), None);
|
|
// Plain keys stay with the grid (typing, navigation).
|
|
assert_eq!(lookup(Key::A, Some(Key::A), Modifiers::NONE, false), None);
|
|
assert_eq!(lookup(Key::F2, Some(Key::F2), Modifiers::NONE, false), None);
|
|
}
|
|
|
|
#[test]
|
|
fn space_selection_shortcuts_do_not_start_an_edit() {
|
|
let mut h = harness(book("B2"));
|
|
// Shift+Space: the key press, then the space the OS types with it.
|
|
let shift = Modifiers::SHIFT;
|
|
h.input_mut().events.push(Event::Key { key: Key::Space, physical_key: Some(Key::Space), pressed: true, repeat: false, modifiers: shift });
|
|
h.input_mut().events.push(Event::Text(" ".into()));
|
|
h.run_steps(2);
|
|
assert!(h.state().editor.is_none(), "Shift+Space started an edit");
|
|
assert_eq!(h.state().session.active().unwrap().selection.ranges[0].a1(), "2:2");
|
|
h.state_mut().session.run("selection.set", json!({"range": "B2"})).unwrap();
|
|
// Ctrl+Space selects the column, also without starting an edit.
|
|
h.input_mut().events.push(Event::Key { key: Key::Space, physical_key: Some(Key::Space), pressed: true, repeat: false, modifiers: CTRL });
|
|
h.input_mut().events.push(Event::Text(" ".into()));
|
|
h.run_steps(2);
|
|
assert!(h.state().editor.is_none());
|
|
assert_eq!(h.state().session.active().unwrap().selection.ranges[0].a1(), "B:B");
|
|
// Plain typing still edits.
|
|
h.input_mut().events.push(Event::Text("x".into()));
|
|
h.run_steps(2);
|
|
assert!(h.state().editor.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn space_shortcuts_per_platform() {
|
|
let run = |id: &'static str| Some(Shortcut::Run(id, json!({})));
|
|
let mac_cmd = Modifiers { mac_cmd: true, command: true, ..Modifiers::NONE };
|
|
let space = |m, mac| lookup(Key::Space, Some(Key::Space), m, mac);
|
|
assert_eq!(space(Modifiers::SHIFT, false), run("selection.row"));
|
|
assert_eq!(space(Modifiers::SHIFT, true), run("selection.row"));
|
|
assert_eq!(space(CTRL, false), run("selection.column"));
|
|
assert_eq!(space(Modifiers::CTRL, true), run("selection.column"));
|
|
// Cmd+Space is Spotlight on macOS.
|
|
assert_eq!(space(mac_cmd, true), None);
|
|
}
|