diff --git a/crates/engine/src/cmd/pivot.rs b/crates/engine/src/cmd/pivot.rs index 8a441fa1b..c20bb5a84 100644 --- a/crates/engine/src/cmd/pivot.rs +++ b/crates/engine/src/cmd/pivot.rs @@ -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(); + } } diff --git a/crates/engine/src/cmd/sheet.rs b/crates/engine/src/cmd/sheet.rs index 7870dee4e..7cb82ccf4 100644 --- a/crates/engine/src/cmd/sheet.rs +++ b/crates/engine/src/cmd/sheet.rs @@ -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; + } + } + } } }