mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-24 10:46:50 -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
62 lines
2.9 KiB
Rust
62 lines
2.9 KiB
Rust
//! #1999: a conditional-format rule whose formula doesn't parse must be rejected at
|
|
//! creation (Excel's object model refuses it too), not saved and poison the file.
|
|
|
|
use gridcraft_engine::Session;
|
|
use serde_json::json;
|
|
|
|
fn session() -> Session {
|
|
let mut s = Session::new();
|
|
s.new_workbook();
|
|
s
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_cf_formulas_are_rejected() {
|
|
for rule in [
|
|
// exact repros from the issue's measured table
|
|
json!({"type": "expression", "formula": "=A1>", "preset": "lightRedFill"}),
|
|
json!({"type": "cellIs", "operator": "greaterThan", "value": "=1+", "preset": "lightRedFill"}),
|
|
json!({"type": "dataBar", "min": {"type": "formula", "value": "1+"}}),
|
|
// more garbage that the parser rejects
|
|
json!({"type": "expression", "formula": "=IF(A1", "preset": "lightRedFill"}),
|
|
json!({"type": "cellIs", "operator": "equal", "value": "=((A1", "preset": "lightRedFill"}),
|
|
// multiple leading '=' on a stored-as-typed threshold: the writer strips one,
|
|
// the rest must still parse (#1999 review)
|
|
json!({"type": "dataBar", "min": {"type": "formula", "value": "===A1"}}),
|
|
json!({"type": "dataBar", "max": {"type": "formula", "value": "==1+"}}),
|
|
] {
|
|
let err = session()
|
|
.execute("home.conditionalFormat", json!({"range": "A1:A5", "rule": rule.clone()}))
|
|
.expect_err("invalid formula {rule} must be rejected");
|
|
let msg = err.to_string();
|
|
assert!(msg.to_lowercase().contains("formula"), "error should mention the formula: {msg}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn valid_cf_formulas_still_work() {
|
|
let mut s = session();
|
|
// bare comparison without '=' (the issue: this one saves fine today)
|
|
s.execute("home.conditionalFormat", json!({"range": "A1:A5", "rule": {"type": "expression", "formula": "A1>5", "preset": "lightRedFill"}}))
|
|
.unwrap();
|
|
// leading '=' is accepted and stripped
|
|
s.execute("home.conditionalFormat", json!({"range": "B1:B5", "rule": {"type": "expression", "formula": "=B1>5", "preset": "lightRedFill"}}))
|
|
.unwrap();
|
|
// cellIs numeric and text values are not formulas, must pass untouched
|
|
s.execute(
|
|
"home.conditionalFormat",
|
|
json!({"range": "C1:C5", "rule": {"type": "cellIs", "operator": "greaterThan", "value": "5", "preset": "lightRedFill"}}),
|
|
)
|
|
.unwrap();
|
|
s.execute(
|
|
"home.conditionalFormat",
|
|
json!({"range": "D1:D5", "rule": {"type": "cellIs", "operator": "equal", "value": "hello there", "preset": "lightRedFill"}}),
|
|
)
|
|
.unwrap();
|
|
// dataBar formula thresholds that parse
|
|
s.execute("home.conditionalFormat", json!({"range": "E1:E5", "rule": {"type": "dataBar", "min": {"type": "formula", "value": "MIN(E1:E5)"}, "max": {"type": "formula", "value": "MAX(E1:E5)"}}}))
|
|
.unwrap();
|
|
// non-formula rule types are untouched
|
|
s.execute("home.conditionalFormat", json!({"range": "F1:F5", "rule": {"type": "duplicate", "preset": "lightRedFill"}})).unwrap();
|
|
}
|