mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-02 16:30:50 -06:00
ui.pythonMode {on: true} (or a switch with Python mode off) answered {"on": false}
when Python mode could not start, so a control or MCP client saw success. It now
returns an error naming why: a locked cell on a protected sheet, a cell or draft
inside another cell's spill, or code hidden on a protected sheet.
Test: a_refused_ui_python_mode_start_is_an_error_with_the_reason (fails without
the fix); python_mode_starts_no_edit_on_hidden_code and
python_mode_does_not_start_on_a_draft_typed_inside_a_spill now expect the error.
140 lines
5.4 KiB
Rust
140 lines
5.4 KiB
Rust
//! A Python cell formatted Hidden on a protected sheet keeps its code out of view, as a hidden
|
|
//! formula does: the formula bar shows nothing, and F2, a double-click, the formula bar,
|
|
//! Ctrl+Alt+Shift+P and the PY badge start no edit that would show the code, or
|
|
//! that would write blank code over it.
|
|
|
|
use egui::{Event, Key, Modifiers, vec2};
|
|
use egui_kittest::kittest::Queryable as _;
|
|
use gridcraft_engine::Session;
|
|
use gridcraft_engine::core::CellRef;
|
|
use serde_json::json;
|
|
|
|
use crate::common::Harness;
|
|
|
|
const CODE: &str = "secret_total = 41\nsecret_total";
|
|
|
|
/// A Python cell at B2, selected, with its protection `hidden` flag, on a sheet protected or not.
|
|
fn harness(hidden: bool, protected: bool) -> Harness {
|
|
let mut s = Session::new();
|
|
s.new_workbook();
|
|
s.execute("python.set", json!({"cell": "B2", "code": CODE})).unwrap();
|
|
s.execute("home.formatCells", json!({"range": "B2", "style": {"protection": {"locked": false, "hidden": hidden}}})).unwrap();
|
|
if protected {
|
|
s.execute("review.protectSheet", json!({})).unwrap();
|
|
}
|
|
s.execute("selection.set", json!({"cell": "B2"})).unwrap();
|
|
crate::common::harness_with(crate::common::builder().with_size(vec2(1200.0, 800.0)), crate::common::app(s), 4)
|
|
}
|
|
|
|
fn send(h: &mut Harness, events: impl IntoIterator<Item = Event>) {
|
|
h.input_mut().events.extend(events);
|
|
h.run_steps(3);
|
|
}
|
|
|
|
fn press(h: &mut Harness, key: Key, modifiers: Modifiers) {
|
|
send(
|
|
h,
|
|
[
|
|
Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers },
|
|
Event::Key { key, physical_key: None, pressed: false, repeat: false, modifiers },
|
|
],
|
|
);
|
|
}
|
|
|
|
const CTRL_ALT_SHIFT: Modifiers = Modifiers { alt: true, ctrl: true, shift: true, mac_cmd: false, command: true };
|
|
|
|
/// Two clicks on B2 in a row: a double-click on the grid.
|
|
fn double_click_b2(h: &mut Harness) {
|
|
let at = {
|
|
let app = h.state();
|
|
let sh = app.session.active().unwrap().wb.active().unwrap();
|
|
app.grid.geo.as_ref().unwrap().screen_cell_rect(sh, CellRef::parse("B2").unwrap()).center()
|
|
};
|
|
for _ in 0..2 {
|
|
for event in [
|
|
Event::PointerMoved(at),
|
|
Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed: true, modifiers: Modifiers::NONE },
|
|
Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed: false, modifiers: Modifiers::NONE },
|
|
] {
|
|
h.input_mut().events.push(event);
|
|
h.run_steps(1);
|
|
}
|
|
}
|
|
h.run_steps(2);
|
|
}
|
|
|
|
/// The open edit's text, and whether it is in Python mode.
|
|
fn editor(h: &Harness) -> Option<(String, bool)> {
|
|
h.state().editor.as_ref().map(|e| (e.text.clone(), e.python))
|
|
}
|
|
|
|
fn bar_shows_code(h: &Harness) -> bool {
|
|
h.query_by_label_contains("secret_total").is_some()
|
|
}
|
|
|
|
fn stored_code(h: &Harness) -> Option<String> {
|
|
let d = h.state().session.active()?;
|
|
d.wb.active()?.cell(CellRef::parse("B2")?)?.py.as_ref().map(|p| p.code.clone())
|
|
}
|
|
|
|
fn cancel(h: &mut Harness) {
|
|
h.state_mut().cancel_edit();
|
|
h.run_steps(2);
|
|
}
|
|
|
|
#[test]
|
|
fn a_hidden_python_cell_on_a_protected_sheet_shows_no_code_in_the_formula_bar() {
|
|
let h = harness(true, true);
|
|
assert!(!bar_shows_code(&h), "the formula bar shows nothing for hidden code");
|
|
assert!(h.query_by_label("Python Mode").is_some(), "the PY badge still marks a Python cell");
|
|
}
|
|
|
|
#[test]
|
|
fn f2_a_double_click_and_the_formula_bar_start_no_edit_on_hidden_code() {
|
|
let mut h = harness(true, true);
|
|
press(&mut h, Key::F2, Modifiers::NONE);
|
|
assert_eq!(editor(&h), None, "F2");
|
|
double_click_b2(&mut h);
|
|
assert_eq!(editor(&h), None, "a double-click");
|
|
h.state_mut().begin_edit(None, true);
|
|
assert_eq!(editor(&h), None, "a click in the formula bar");
|
|
assert!(!bar_shows_code(&h));
|
|
assert_eq!(stored_code(&h).as_deref(), Some(CODE), "the code is untouched");
|
|
}
|
|
|
|
#[test]
|
|
fn python_mode_starts_no_edit_on_hidden_code() {
|
|
let mut h = harness(true, true);
|
|
press(&mut h, Key::P, CTRL_ALT_SHIFT);
|
|
assert_eq!(editor(&h), None, "Ctrl+Alt+Shift+P");
|
|
h.get_by_label("Python Mode").click();
|
|
h.run_steps(3);
|
|
assert_eq!(editor(&h), None, "the PY badge");
|
|
let refused = h.state_mut().run("ui.pythonMode", json!({"on": true})).expect_err("ui.pythonMode");
|
|
assert!(refused.contains("hidden") && !refused.contains("secret"), "{refused}");
|
|
assert_eq!(editor(&h), None, "ui.pythonMode");
|
|
assert!(!bar_shows_code(&h));
|
|
// Ctrl+Enter with no edit open writes nothing over the code.
|
|
press(&mut h, Key::Enter, Modifiers::COMMAND);
|
|
assert_eq!(stored_code(&h).as_deref(), Some(CODE));
|
|
}
|
|
|
|
/// The same cell, unprotected or not hidden, shows and edits its code.
|
|
#[test]
|
|
fn the_code_shows_and_edits_when_the_sheet_is_unprotected_or_the_cell_not_hidden() {
|
|
for (hidden, protected) in [(true, false), (false, true)] {
|
|
let what = format!("hidden {hidden}, protected {protected}");
|
|
let mut h = harness(hidden, protected);
|
|
assert!(bar_shows_code(&h), "{what}: the formula bar shows the code");
|
|
press(&mut h, Key::F2, Modifiers::NONE);
|
|
assert_eq!(editor(&h), Some((CODE.to_string(), true)), "{what}: F2");
|
|
cancel(&mut h);
|
|
double_click_b2(&mut h);
|
|
assert_eq!(editor(&h), Some((CODE.to_string(), true)), "{what}: a double-click");
|
|
cancel(&mut h);
|
|
press(&mut h, Key::P, CTRL_ALT_SHIFT);
|
|
assert_eq!(editor(&h), Some((CODE.to_string(), true)), "{what}: Ctrl+Alt+Shift+P");
|
|
cancel(&mut h);
|
|
}
|
|
}
|