mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-24 02:36:48 -06:00
* CI: dev builds keep line tables only * CI: one integration-test binary per crate * CI: xtask runs tests with nextest, doctests apart, --keep-going on clippy and wasm cargo xtask test runs the workspace tests (--lib --bins --tests) with cargo-nextest when it is installed, else cargo test, then the doctests. cargo xtask ci uses the same two steps. * CI: docs name cargo xtask test * CI: agent guides name the moved fuzz test * CI: tests added on main join their crate's test binary
88 lines
4.2 KiB
Rust
88 lines
4.2 KiB
Rust
//! Clearing a large sparse selection must invalidate every changed cell (issue #1032).
|
|
|
|
use gridcraft_core::{CellError, CellRef, Value};
|
|
use gridcraft_engine::Session;
|
|
use serde_json::json;
|
|
|
|
fn value(s: &Session, address: &str) -> Value {
|
|
s.doc().unwrap().wb.active().unwrap().value(CellRef::parse(address).unwrap())
|
|
}
|
|
|
|
#[test]
|
|
fn large_clear_updates_distant_direct_and_transitive_dependents() {
|
|
for command in ["edit.clearContents", "edit.clearAll"] {
|
|
let mut s = Session::new();
|
|
s.new_workbook();
|
|
for (cell, input) in [("A1000001", "7"), ("B1", "=A1000001"), ("C1", "=B1*2")] {
|
|
s.execute("cell.set", json!({"cell": cell, "input": input})).unwrap();
|
|
}
|
|
assert_eq!((value(&s, "B1"), value(&s, "C1")), (Value::Number(7.0), Value::Number(14.0)));
|
|
|
|
// B1 stretches the used range to row 1; the changed source lies just past the old cap.
|
|
s.execute(command, json!({"range": "A1:A1000001"})).unwrap();
|
|
assert_eq!(value(&s, "A1000001"), Value::Empty, "{command}");
|
|
assert_eq!((value(&s, "B1"), value(&s, "C1")), (Value::Number(0.0), Value::Number(0.0)), "{command}");
|
|
|
|
s.execute("edit.undo", json!({})).unwrap();
|
|
assert_eq!(value(&s, "A1000001"), Value::Number(7.0), "{command} undo");
|
|
assert_eq!((value(&s, "B1"), value(&s, "C1")), (Value::Number(7.0), Value::Number(14.0)), "{command} undo");
|
|
s.execute("edit.redo", json!({})).unwrap();
|
|
assert_eq!(value(&s, "A1000001"), Value::Empty, "{command} redo");
|
|
assert_eq!((value(&s, "B1"), value(&s, "C1")), (Value::Number(0.0), Value::Number(0.0)), "{command} redo");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn large_clear_removes_a_distant_formulas_spill_and_updates_its_dependent() {
|
|
for command in ["edit.clearContents", "edit.clearAll"] {
|
|
let mut s = Session::new();
|
|
s.new_workbook();
|
|
s.execute("cell.set", json!({"cell": "A1000001", "input": "=SEQUENCE(2)"})).unwrap();
|
|
s.execute("cell.set", json!({"cell": "B1", "input": "=A1000002"})).unwrap();
|
|
assert_eq!((value(&s, "A1000002"), value(&s, "B1")), (Value::Number(2.0), Value::Number(2.0)));
|
|
|
|
// The spill lies outside the selection, but must disappear when its anchor is cleared.
|
|
s.execute(command, json!({"range": "A1:A1000001"})).unwrap();
|
|
assert_eq!(value(&s, "A1000001"), Value::Empty, "{command}");
|
|
assert_eq!(value(&s, "A1000002"), Value::Empty, "{command}");
|
|
assert_eq!(value(&s, "B1"), Value::Number(0.0), "{command}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn clearing_an_empty_merge_unblocks_a_spill() {
|
|
for command in ["edit.clearFormats", "edit.clearAll"] {
|
|
let mut s = Session::new();
|
|
s.new_workbook();
|
|
s.execute("cell.set", json!({"cell": "A1", "input": "=SEQUENCE(3)"})).unwrap();
|
|
// Keep the empty merged area inside the used range after the spill becomes blocked.
|
|
s.execute("cell.set", json!({"cell": "C4", "input": "1"})).unwrap();
|
|
s.execute("home.mergeCells", json!({"range": "A2:B3"})).unwrap();
|
|
assert_eq!(value(&s, "A1"), Value::Error(CellError::Spill));
|
|
|
|
s.execute(command, json!({"range": "A2:B3"})).unwrap();
|
|
assert_eq!((value(&s, "A1"), value(&s, "A3")), (Value::Number(1.0), Value::Number(3.0)), "{command}");
|
|
assert!(s.doc().unwrap().wb.active().unwrap().merges.is_empty(), "{command}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn clearing_a_distant_styled_blocker_unblocks_a_spill() {
|
|
let mut s = Session::new();
|
|
s.new_workbook();
|
|
s.execute("cell.set", json!({"cell": "A1", "input": "=SEQUENCE(100)"})).unwrap();
|
|
s.execute("cell.set", json!({"cell": "A90", "input": "x"})).unwrap();
|
|
s.execute("home.bold", json!({"range": "A90", "on": true})).unwrap();
|
|
assert_eq!(value(&s, "A1"), Value::Error(CellError::Spill));
|
|
|
|
// The cleared blocker stays stored for its style and lies beyond the local 64-row scan.
|
|
s.execute("edit.clearContents", json!({"range": "A2:A100"})).unwrap();
|
|
assert_eq!(value(&s, "A1"), Value::Number(1.0));
|
|
assert_eq!(value(&s, "A90"), Value::Number(90.0));
|
|
assert_eq!(value(&s, "A100"), Value::Number(100.0));
|
|
let wb = &s.doc().unwrap().wb;
|
|
let cell = wb.active().unwrap().cell(CellRef::parse("A90").unwrap()).unwrap();
|
|
assert_eq!(cell.value, Value::Empty);
|
|
assert!(wb.styles.get(cell.style).font.bold);
|
|
}
|