Merge pull request #604 from MarekSpodBiedry:issue-559

This commit is contained in:
Brandon Thomas 2026-10-12 23:35:53 -04:00
commit 87f4932f0e
2 changed files with 40 additions and 4 deletions

View File

@ -559,6 +559,14 @@ fn diff_sets(d: &mut Diffs, sheet: &str, a: &BTreeSet<String>, b: &BTreeSet<Stri
}
}
/// A sheet's conditional-format rules in precedence order, as comparable text. The priority number
/// itself is left out, so renumbering without changing the order isn't a difference.
fn cond_format_list(s: &Sheet) -> Vec<String> {
let mut cfs: Vec<_> = s.cond_formats.iter().collect();
cfs.sort_by_key(|c| c.priority);
cfs.iter().map(|c| format!("{:?} {:?} stopIfTrue={}", c.ranges, c.rule, c.stop_if_true)).collect()
}
fn diff_count(d: &mut Diffs, sheet: &str, a: usize, b: usize) {
if a != b {
d.push(|| format!("{sheet}: {a} → {b}"));
@ -634,7 +642,12 @@ pub fn diff_workbooks(a: &Workbook, b: &Workbook, opts: &Options) -> Vec<Categor
.collect()
};
diff_sets(&mut tables, &name, &set(&table_list, sa), &set(&table_list, sb));
diff_count(&mut cond_formats, &name, sa.cond_formats.len(), sb.cond_formats.len());
let (cfa, cfb) = (cond_format_list(sa), cond_format_list(sb));
if cfa.len() != cfb.len() {
cond_formats.push(|| format!("{name}: {} → {}", cfa.len(), cfb.len()));
} else if let Some((x, y)) = cfa.iter().zip(&cfb).find(|(x, y)| x != y) {
cond_formats.push(|| format!("{name}: {x} → {y}"));
}
diff_count(&mut validations, &name, sa.validations.len(), sb.validations.len());
let chart_list = |s: &Sheet| s.charts.iter().map(|c| format!("{:?} ({} series)", c.kind, c.series.len())).collect::<Vec<_>>();
let (ca, cb) = (chart_list(sa), chart_list(sb));
@ -1303,6 +1316,28 @@ mod tests {
assert_eq!(get("tables").diffs, 0);
}
#[test]
fn diff_detects_changed_cond_format_rule() {
use gridcraft_engine::model::{CfRule, CfValueKind, Color, CondFormat};
let bar = |gradient: bool, priority: u32| CondFormat {
ranges: vec![gridcraft_engine::core::RangeRef::parse("A1").unwrap()],
rule: CfRule::DataBar { min: CfValueKind::Min, max: CfValueKind::Max, color: Color::Rgb(0x638EC6), gradient, show_value: true },
priority,
stop_if_true: false,
};
let with = |cf: CondFormat| {
let mut wb = book(&[("A1", "2", n(2.0))]);
wb.sheet_mut(0).unwrap().cond_formats.push(cf);
wb
};
let diffs = |a: &Workbook, b: &Workbook| {
diff_workbooks(a, b, &Options::default()).into_iter().find(|c| c.name == "condFormats").unwrap().diffs
};
assert_eq!(diffs(&with(bar(false, 1)), &with(bar(true, 1))), 1);
assert_eq!(diffs(&with(bar(true, 1)), &with(bar(true, 1))), 0);
assert_eq!(diffs(&with(bar(true, 1)), &with(bar(true, 5))), 0);
}
fn zip_of(parts: &[(&str, &str)]) -> Vec<u8> {
let mut z = zip::ZipWriter::new(Cursor::new(Vec::new()));
for (name, body) in parts {

View File

@ -86,9 +86,10 @@ up by *signature*, `expected type → actual type` (e.g. `number → #SPILL!`),
**Round trip.** The recalculated workbook is written with GridCraft's XLSX writer, the package is validated (every
part has a content type, every content-type override and internal relationship target exists, relationship ids are
unique: what Excel would "repair") and read back. Categories compared: `package`, `sheets` (names and order),
`values`, `formulas`, `numFmts` (per cell), `merges`, `names`, `tables` (name, range, columns), `condFormats` (rules
per sheet), `validations`, `charts` (kinds and series counts), `comments`, `hyperlinks`, `pivots`. `#SPILL!`/`#CALC!`
compare equal to `#VALUE!` and `#CIRC!` to 0, as that's what the file format stores.
`values`, `formulas`, `numFmts` (per cell), `merges`, `names`, `tables` (name, range, columns), `condFormats` (each
sheet's rules in priority order, priority numbers aside), `validations`, `charts` (kinds and series counts), `comments`,
`hyperlinks`, `pivots`. `#SPILL!`/`#CALC!` compare equal to `#VALUE!` and `#CIRC!` to 0, as
that's what the file format stores.
Files that fail to open are reported with GridCraft's error and don't stop the run. Exit code: 0 when every file
opened with no mismatch and no round-trip difference, 1 otherwise, 2 on a usage error. `--max-report` caps the