mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-11 02:30:53 +00:00
With the whole sheet selected (Ctrl+A on an empty sheet), Fill Down, Right, Up and Left tried to fill all 17 billion cells and ran out of memory, and Remove Duplicates scanned every row for minutes. Fill Up and Fill Left also took their source row or column before narrowing the selection to the data, so they filled up to row 1,048,576 even when the sheet had data. Whole rows and columns now narrow to the used range before anything else (nothing to do on an empty sheet), Remove Duplicates works on the active cell of an empty sheet, and a fill of more than 5,000,000 cells is refused, as Auto Fill already does.
209 lines
8.7 KiB
Rust
209 lines
8.7 KiB
Rust
use gridcraft_core::{CellRef, Value};
|
|
use serde_json::json;
|
|
|
|
use crate::Session;
|
|
|
|
fn s() -> Session {
|
|
let mut s = Session::new();
|
|
s.new_workbook();
|
|
s
|
|
}
|
|
|
|
fn v(s: &Session, a: &str) -> Value {
|
|
s.doc().unwrap().wb.active().unwrap().value(CellRef::parse(a).unwrap())
|
|
}
|
|
|
|
#[test]
|
|
fn enter_values_and_formulas() {
|
|
let mut s = s();
|
|
s.execute("cell.set", json!({"cell": "A1", "input": "10"})).unwrap();
|
|
s.execute("cell.set", json!({"cell": "A2", "input": "32"})).unwrap();
|
|
s.execute("cell.set", json!({"cell": "A3", "input": "=sum(a1:a2"})).unwrap();
|
|
assert_eq!(v(&s, "A3"), Value::Number(42.0));
|
|
assert!(s.execute("cell.set", json!({"cell": "A4", "input": "=1+*2"})).is_err());
|
|
s.execute("cell.set", json!({"cell": "B1", "input": "12%"})).unwrap();
|
|
let d = s.doc().unwrap();
|
|
let sh = d.wb.active().unwrap();
|
|
assert_eq!(crate::display::cell_text(&d.wb, sh, CellRef::parse("B1").unwrap()), "12%");
|
|
}
|
|
|
|
#[test]
|
|
fn undo_redo() {
|
|
let mut s = s();
|
|
s.execute("cell.set", json!({"cell": "A1", "input": "1"})).unwrap();
|
|
s.execute("cell.set", json!({"cell": "A1", "input": "2"})).unwrap();
|
|
s.execute("edit.undo", json!({})).unwrap();
|
|
assert_eq!(v(&s, "A1"), Value::Number(1.0));
|
|
s.execute("edit.redo", json!({})).unwrap();
|
|
assert_eq!(v(&s, "A1"), Value::Number(2.0));
|
|
assert!(s.doc().unwrap().is_dirty());
|
|
}
|
|
|
|
#[test]
|
|
fn copy_paste_shifts_formulas() {
|
|
let mut s = s();
|
|
s.execute("range.setValues", json!({"range": "A1", "values": [[1, 2], [3, 4]]})).unwrap();
|
|
s.execute("cell.set", json!({"cell": "C1", "input": "=A1+B1"})).unwrap();
|
|
s.execute("edit.copy", json!({"range": "C1"})).unwrap();
|
|
s.execute("selection.set", json!({"range": "C2"})).unwrap();
|
|
s.execute("edit.paste", json!({})).unwrap();
|
|
assert_eq!(v(&s, "C2"), Value::Number(7.0));
|
|
s.execute("edit.cut", json!({"range": "A1"})).unwrap();
|
|
s.execute("selection.set", json!({"range": "E5"})).unwrap();
|
|
s.execute("edit.paste", json!({})).unwrap();
|
|
assert_eq!(v(&s, "E5"), Value::Number(1.0));
|
|
let f = s.execute("cell.get", json!({"cell": "C1"})).unwrap();
|
|
assert_eq!(f["formula"], "=E5+B1");
|
|
}
|
|
|
|
#[test]
|
|
fn insert_delete_rows_adjust() {
|
|
let mut s = s();
|
|
s.execute("range.setValues", json!({"range": "A1", "values": [[1], [2], [3]]})).unwrap();
|
|
s.execute("cell.set", json!({"cell": "B1", "input": "=SUM(A1:A3)"})).unwrap();
|
|
s.execute("home.insertRows", json!({"rows": "2:2"})).unwrap();
|
|
assert_eq!(s.execute("cell.get", json!({"cell": "B1"})).unwrap()["formula"], "=SUM(A1:A4)");
|
|
s.execute("cell.set", json!({"cell": "A2", "input": "10"})).unwrap();
|
|
assert_eq!(v(&s, "B1"), Value::Number(16.0));
|
|
s.execute("home.deleteRows", json!({"rows": "1:1"})).unwrap();
|
|
assert_eq!(v(&s, "B1"), Value::Empty);
|
|
assert_eq!(v(&s, "A1"), Value::Number(10.0));
|
|
}
|
|
|
|
#[test]
|
|
fn fill_series() {
|
|
let mut s = s();
|
|
s.execute("range.setValues", json!({"range": "A1", "values": [[1], [3]]})).unwrap();
|
|
s.execute("edit.autoFill", json!({"source": "A1:A2", "target": "A1:A6"})).unwrap();
|
|
assert_eq!(v(&s, "A6"), Value::Number(11.0));
|
|
s.execute("cell.set", json!({"cell": "B1", "input": "Mon"})).unwrap();
|
|
s.execute("edit.autoFill", json!({"source": "B1", "target": "B1:B3"})).unwrap();
|
|
assert_eq!(v(&s, "B3"), Value::from("Wed"));
|
|
s.execute("cell.set", json!({"cell": "C1", "input": "Item 9"})).unwrap();
|
|
s.execute("edit.autoFill", json!({"source": "C1", "target": "C1:C2"})).unwrap();
|
|
assert_eq!(v(&s, "C2"), Value::from("Item 10"));
|
|
}
|
|
|
|
#[test]
|
|
fn sort_and_filter() {
|
|
let mut s = s();
|
|
s.execute("range.setValues", json!({"range": "A1", "values": [["Name", "Score"], ["b", 2], ["c", 3], ["a", 1]]})).unwrap();
|
|
s.execute("selection.set", json!({"cell": "B2"})).unwrap();
|
|
s.execute("data.sortDescending", json!({})).unwrap();
|
|
assert_eq!(v(&s, "A2"), Value::from("c"));
|
|
assert_eq!(v(&s, "A1"), Value::from("Name"));
|
|
s.execute("data.filter", json!({})).unwrap();
|
|
let r = s.execute("data.filterBy", json!({"column": "B", "custom": {"op": ">", "value": "1"}})).unwrap();
|
|
assert_eq!(r["hiddenRows"], 1);
|
|
}
|
|
|
|
#[test]
|
|
fn formatting_and_styles() {
|
|
let mut s = s();
|
|
s.execute("selection.set", json!({"range": "A1:B2"})).unwrap();
|
|
s.execute("home.bold", json!({})).unwrap();
|
|
s.execute("home.fillColor", json!({"color": "#FFFF00"})).unwrap();
|
|
s.execute("home.mergeCenter", json!({})).unwrap();
|
|
let c = s.execute("cell.get", json!({"cell": "B2"})).unwrap();
|
|
assert_eq!(c["style"]["font"]["bold"], true);
|
|
assert_eq!(c["merge"], "A1:B2");
|
|
s.execute("home.cellStyle", json!({"range": "D1", "name": "Good"})).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn tables_and_structured_refs() {
|
|
let mut s = s();
|
|
s.execute("range.setValues", json!({"range": "A1", "values": [["Item", "Qty"], ["x", 2], ["y", 5]]})).unwrap();
|
|
s.execute("insert.table", json!({"range": "A1:B3"})).unwrap();
|
|
s.execute("cell.set", json!({"cell": "D1", "input": "=SUM(Table1[Qty])"})).unwrap();
|
|
assert_eq!(v(&s, "D1"), Value::Number(7.0));
|
|
s.execute("table.totalRow", json!({"on": true, "table": "Table1"})).unwrap();
|
|
assert_eq!(v(&s, "B4"), Value::Number(7.0));
|
|
}
|
|
|
|
#[test]
|
|
fn sheets() {
|
|
let mut s = s();
|
|
s.execute("home.insertSheet", json!({})).unwrap();
|
|
s.execute("cell.set", json!({"cell": "A1", "input": "5"})).unwrap();
|
|
s.execute("sheet.rename", json!({"name": "Data"})).unwrap();
|
|
s.execute("sheet.activate", json!({"sheet": 0})).unwrap();
|
|
s.execute("cell.set", json!({"cell": "A1", "input": "=Data!A1*2"})).unwrap();
|
|
assert_eq!(v(&s, "A1"), Value::Number(10.0));
|
|
s.execute("sheet.rename", json!({"sheet": "Data", "name": "My Data"})).unwrap();
|
|
assert_eq!(s.execute("cell.get", json!({"cell": "A1"})).unwrap()["formula"], "='My Data'!A1*2");
|
|
}
|
|
|
|
#[test]
|
|
fn xlsx_roundtrip_through_engine() {
|
|
let mut s = s();
|
|
s.execute("range.setValues", json!({"range": "A1", "values": [["a", 1], ["b", 2]]})).unwrap();
|
|
s.execute("cell.set", json!({"cell": "B3", "input": "=SUM(B1:B2)"})).unwrap();
|
|
let r = s.execute("file.saveBytes", json!({"format": "xlsx"})).unwrap();
|
|
let b64 = r["base64"].as_str().unwrap().to_string();
|
|
s.execute("file.open", json!({"name": "x.xlsx", "base64": b64})).unwrap();
|
|
assert_eq!(v(&s, "B3"), Value::Number(3.0));
|
|
}
|
|
|
|
#[test]
|
|
fn samples_build() {
|
|
for (name, _) in crate::sample::SAMPLES {
|
|
let wb = crate::sample::build(name).unwrap_or_else(|| panic!("sample {name}"));
|
|
assert!(!wb.sheets[0].cells.is_empty());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn every_command_survives_empty_params() {
|
|
let mut s = Session::new();
|
|
s.execute("file.new", json!({"sample": "sales"})).unwrap();
|
|
for spec in crate::command_specs() {
|
|
if matches!(spec.id, "file.close" | "file.open" | "file.save" | "file.saveAs" | "file.exportCsv" | "file.exportHtml" | "insert.picture") {
|
|
continue;
|
|
}
|
|
let _ = s.execute(spec.id, json!({}));
|
|
let _ = s.execute(spec.id, json!([1, 2]));
|
|
let _ = s.execute(spec.id, json!({"range": "ZZZ999999999", "cell": "", "sheet": 99}));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn whole_sheet_selection_stays_within_the_data() {
|
|
// On an empty sheet with everything selected these filled or scanned every cell of the
|
|
// sheet, running out of memory or time.
|
|
let mut s = s();
|
|
s.execute("edit.selectAll", json!({})).unwrap();
|
|
for cmd in ["edit.fillDown", "edit.fillRight", "edit.fillUp", "edit.fillLeft", "data.removeDuplicates"] {
|
|
s.execute(cmd, json!({})).unwrap();
|
|
}
|
|
s.execute("range.setValues", json!({"range": "A1", "values": [[1, 2], [3, 4], [5, 6]]})).unwrap();
|
|
s.execute("selection.set", json!({"range": "A1:XFD1048576"})).unwrap();
|
|
s.execute("edit.fillUp", json!({})).unwrap();
|
|
assert_eq!(v(&s, "A1"), Value::Number(5.0));
|
|
assert!(s.execute("edit.fillDown", json!({"range": "A1:Z1000000"})).is_err(), "too large to fill");
|
|
}
|
|
|
|
#[test]
|
|
fn parity_counts() {
|
|
let (done, total) = crate::catalog::parity();
|
|
assert!(total > 250);
|
|
assert!(done > 150, "parity {done}/{total}");
|
|
}
|
|
|
|
#[test]
|
|
fn ink_strokes_and_ink_to_shape() {
|
|
let mut s = s();
|
|
// A rough closed box.
|
|
let pts: Vec<[f64; 2]> = vec![[100.0, 100.0], [200.0, 102.0], [201.0, 180.0], [99.0, 181.0], [101.0, 104.0]];
|
|
s.execute("draw.stroke", json!({"points": pts})).unwrap();
|
|
let sh = s.doc().unwrap().wb.active().unwrap().clone();
|
|
assert_eq!(sh.shapes.len(), 1);
|
|
assert_eq!(sh.shapes[0].kind, gridcraft_model::ShapeKind::Ink);
|
|
let r = s.execute("draw.inkToShape", json!({})).unwrap();
|
|
assert_eq!(r["kind"], "Rectangle");
|
|
// An open stroke becomes a line.
|
|
s.execute("draw.stroke", json!({"points": [[0.0, 0.0], [50.0, 10.0], [120.0, 30.0]]})).unwrap();
|
|
assert_eq!(s.execute("draw.inkToShape", json!({})).unwrap()["kind"], "Line");
|
|
assert!(s.execute("draw.stroke", json!({"points": [[1.0, 1.0]]})).is_err());
|
|
}
|