Move PivotTables with their cells on row/column insert and delete

Found while testing crates/engine/src/cmd/sheet.rs:115 (shift_sheet_features; it never touches sh.pivots)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-17 04:04:00 +02:00
co-authored by Claude Haiku 5.5
parent f1894c6da6
commit a6aa53cbff
2 changed files with 70 additions and 0 deletions
+49
View File
@@ -2308,4 +2308,53 @@ mod tests {
// 64 Sub items under the collapsed "g" show as one column group: (1 + grand total) x 256 columns fit.
s.execute("pivot.addField", json!({"field": "Sub", "area": "columns"})).unwrap();
}
#[test]
fn inserting_or_deleting_lines_moves_the_pivot_with_its_cells() {
let mut s = sales();
s.execute("insert.pivotTable", json!({"source": "Sheet1!A1:E13", "destination": "Sheet1!H10", "rows": ["Region"], "values": ["Sales"]}))
.unwrap();
assert_eq!(pt(&s).last_range.unwrap().a1(), "H10:I14");
// Rows above the report (and below the source) move the report only.
s.execute("home.insertRows", json!({"rows": "16:17"})).unwrap();
s.execute("home.insertRows", json!({"rows": "6:7"})).unwrap();
assert_eq!(text(&s, "H12"), "Row Labels");
let l = s.execute("pivot.list", json!({})).unwrap();
assert_eq!(l[0]["anchor"], "H12");
assert_eq!(l[0]["range"], "H12:I16");
assert_eq!(l[0]["source"], "Sheet1!A1:E15");
// A row above the source moves both.
s.execute("home.insertRows", json!({"rows": "1:1"})).unwrap();
let p = pt(&s);
assert_eq!(p.source, "Sheet1!A2:E16");
assert_eq!(p.anchor.a1(), "H13");
s.execute("pivot.refresh", json!({})).unwrap();
assert_eq!(pt(&s).last_range.unwrap().a1(), "H13:I17");
assert_eq!(text(&s, "H13"), "Row Labels");
assert_eq!(val(&s, "I17"), Value::Number(TOTAL));
// Deleting a column left of the report moves it left.
s.execute("home.deleteColumns", json!({"cols": "F:F"})).unwrap();
let p = pt(&s);
assert_eq!(p.anchor.a1(), "G13");
assert_eq!(p.last_range.unwrap().a1(), "G13:H17");
// Deleting rows above moves it up.
s.execute("home.deleteRows", json!({"rows": "1:1"})).unwrap();
assert_eq!(pt(&s).source, "Sheet1!A1:E15");
assert_eq!(pt(&s).anchor.a1(), "G12");
s.execute("pivot.refresh", json!({})).unwrap();
assert_eq!(text(&s, "G12"), "Row Labels");
assert_eq!(val(&s, "H16"), Value::Number(TOTAL));
}
#[test]
fn inserting_rows_on_the_source_sheet_moves_another_sheets_pivot_source() {
let mut s = sales();
s.execute("insert.pivotTable", json!({"source": "Sheet1!A1:E13", "rows": ["Region"], "values": ["Sales"]})).unwrap();
s.execute("sheet.activate", json!({"sheet": "Sheet1"})).unwrap();
s.execute("home.insertRows", json!({"rows": "1:2"})).unwrap();
let l = s.execute("pivot.list", json!({})).unwrap();
assert_eq!(l[0]["source"], "Sheet1!A3:E15");
assert_eq!(l[0]["anchor"], "A3");
s.execute("pivot.refresh", json!({"pivot": "PivotTable1"})).unwrap();
}
}
+21
View File
@@ -211,6 +211,15 @@ fn shift_sheet_features(sh: &mut Sheet, axis: Axis, at: u32, count: u32, insert:
if let Some(pa) = sh.print.print_area {
sh.print.print_area = map_range(pa);
}
// PivotTables follow their report cells. A deleted anchor line collapses onto the line that
// takes its place; a report deleted entirely has no last area until the next refresh.
for pt in sh.pivots.iter_mut() {
pt.anchor = map_cell(pt.anchor).unwrap_or_else(|| match axis {
Axis::Rows => CellRef::new(at, pt.anchor.col),
Axis::Cols => CellRef::new(pt.anchor.row, at),
});
pt.last_range = pt.last_range.and_then(map_range);
}
}
/// Rewrites every formula reference (and chart series) for a structural edit on `target`.
@@ -235,6 +244,18 @@ fn rewrite(wb: &mut gridcraft_model::Workbook, target: &str, e: &Edit) {
for sp in sh.sparklines.iter_mut() {
sp.source = fix(&sp.source);
}
// PivotTable sources (a range, table or defined name) are only rewritten when the edit
// moves them, so table and name sources keep their text.
for pt in sh.pivots.iter_mut() {
let body = pt.source.trim().trim_start_matches('=');
if let Ok(expr) = gridcraft_formula::parse(body) {
let before = gridcraft_formula::print(&expr);
let after = gridcraft_formula::print(&gridcraft_formula::adjust::adjust(expr, &host, target, e));
if after != before {
pt.source = after;
}
}
}
}
}