diff --git a/crates/engine/src/display.rs b/crates/engine/src/display.rs index aa8bdb22d..2189d6c64 100644 --- a/crates/engine/src/display.rs +++ b/crates/engine/src/display.rs @@ -90,14 +90,14 @@ pub fn edit_text(wb: &Workbook, sheet: &Sheet, c: CellRef) -> String { if reads_back(&iso) { iso } else { raw } } -/// Whether `c` has a formula that must not be shown: its cell is formatted Hidden on a -/// protected sheet. A spilled cell follows its anchor. +/// Whether `c` has a formula or Python code that must not be shown: its cell is formatted +/// Hidden on a protected sheet. A spilled cell follows its anchor. pub fn formula_hidden(wb: &Workbook, sheet: &Sheet, c: CellRef) -> bool { if !sheet.is_protected() { return false; } let at = match sheet.cell(c) { - Some(x) if x.formula.is_some() => c, + Some(x) if x.formula.is_some() || x.py.is_some() => c, _ => match sheet.spill_ranges.iter().find(|(_, r)| r.contains(c)) { Some((anchor, _)) => *anchor, None => return false, diff --git a/crates/engine/src/tests/python_cells.rs b/crates/engine/src/tests/python_cells.rs index ef1df605a..7971f7811 100644 --- a/crates/engine/src/tests/python_cells.rs +++ b/crates/engine/src/tests/python_cells.rs @@ -296,3 +296,23 @@ fn replace_leaves_python_code_that_would_no_longer_fit() { assert_eq!(r["skipped"], 1); assert_eq!(code(&s, 0, "A1").as_deref(), Some(long.as_str())); } + +#[test] +fn a_hidden_python_cell_on_a_protected_sheet_hides_its_code() { + let mut s = s(); + s.execute("python.set", json!({"cell": "A1", "code": "secret = 1\nsecret"})).unwrap(); + s.execute("python.set", json!({"cell": "B1", "code": "'shown'"})).unwrap(); + s.execute("home.formatCells", json!({"range": "A1", "style": {"protection": {"locked": true, "hidden": true}}})).unwrap(); + let hidden = |s: &Session, a: &str| { + let d = s.doc().unwrap(); + let sh = d.wb.active().unwrap(); + crate::display::formula_hidden(&d.wb, sh, CellRef::parse(a).unwrap()) + }; + assert!(!hidden(&s, "A1"), "an unprotected sheet hides nothing"); + s.execute("review.protectSheet", json!({})).unwrap(); + assert!(hidden(&s, "A1"), "Hidden on a protected sheet hides the code, as it hides a formula"); + assert!(!hidden(&s, "B1")); + // Editing it is refused where a formula's would be: the cell is locked. + assert!(s.execute("python.set", json!({"cell": "A1", "code": "2"})).is_err()); + assert_eq!(code(&s, 0, "A1").as_deref(), Some("secret = 1\nsecret")); +} diff --git a/crates/ui-egui/src/formula_bar.rs b/crates/ui-egui/src/formula_bar.rs index 3db0c3da4..557e3dac0 100644 --- a/crates/ui-egui/src/formula_bar.rs +++ b/crates/ui-egui/src/formula_bar.rs @@ -173,7 +173,8 @@ fn active_input(app: &SheetApp) -> String { .unwrap_or_default(); } if let Some(c) = sh.cell(a) { - if c.formula.is_some() && d.wb.styles.get(c.style).protection.hidden && sh.is_protected() { + // A Hidden cell on a protected sheet shows neither its formula nor its Python code. + if (c.formula.is_some() || c.py.is_some()) && d.wb.styles.get(c.style).protection.hidden && sh.is_protected() { return String::new(); } return if let Some(f) = &c.formula { @@ -753,6 +754,23 @@ mod spill_tests { app.run("sheet.activate", json!({"sheet": "Sheet1"})).unwrap(); check(&mut app, "A2", "=FILTER(D1:D3,D1:D3>0)", true); } + + #[test] + fn a_hidden_python_cell_on_a_protected_sheet_shows_no_code() { + let mut app = app(); + app.run("python.set", json!({"cell": "A1", "code": "secret = 1\nsecret"})).unwrap(); + app.run("python.set", json!({"cell": "B1", "code": "'shown'"})).unwrap(); + set(&mut app, "C1", "=1+1"); + app.run("home.formatCells", json!({"range": "A1:C1", "style": {"protection": {"locked": true, "hidden": true}}})).unwrap(); + app.run("home.formatCells", json!({"range": "B1", "style": {"protection": {"locked": true, "hidden": false}}})).unwrap(); + select(&mut app, "A1"); + assert_eq!(active_input(&app), "secret = 1\nsecret", "an unprotected sheet hides nothing"); + app.run("review.protectSheet", json!({})).unwrap(); + for (cell, text) in [("A1", ""), ("B1", "'shown'"), ("C1", "")] { + select(&mut app, cell); + assert_eq!(active_input(&app), text, "{cell}"); + } + } #[test] /// A disabled spill formula paints disabled and does not edit; clicking an active formula starts formula-bar editing in both light and dark themes, with accessibility checks. fn formula_bar_click_and_theme_presentation() {