From 8ba30fde331e4676822ed6eb085e5227b89e5257 Mon Sep 17 00:00:00 2001 From: Gian Hancock Date: Thu, 26 Dec 2024 23:35:59 +1100 Subject: [PATCH] Add more tests for OR and XOR Some of these tests fail due to inconsistencies with Excel cleanup --- base/src/test/mod.rs | 2 +- base/src/test/test_fn_or.rs | 36 ----- base/src/test/test_fn_or_xor.rs | 204 ++++++++++++++++++++++++++ xlsx/tests/calc_tests/AND_OR_XOR.xlsx | Bin 0 -> 14999 bytes 4 files changed, 205 insertions(+), 37 deletions(-) delete mode 100644 base/src/test/test_fn_or.rs create mode 100644 base/src/test/test_fn_or_xor.rs create mode 100644 xlsx/tests/calc_tests/AND_OR_XOR.xlsx diff --git a/base/src/test/mod.rs b/base/src/test/mod.rs index fcacb71..109771b 100644 --- a/base/src/test/mod.rs +++ b/base/src/test/mod.rs @@ -19,6 +19,7 @@ mod test_fn_formulatext; mod test_fn_if; mod test_fn_maxifs; mod test_fn_minifs; +mod test_fn_or_xor; mod test_fn_product; mod test_fn_rept; mod test_fn_sum; @@ -46,7 +47,6 @@ pub(crate) mod util; mod engineering; mod test_fn_offset; -mod test_fn_or; mod test_number_format; mod test_escape_quotes; diff --git a/base/src/test/test_fn_or.rs b/base/src/test/test_fn_or.rs deleted file mode 100644 index 3c49b8a..0000000 --- a/base/src/test/test_fn_or.rs +++ /dev/null @@ -1,36 +0,0 @@ -#![allow(clippy::unwrap_used)] - -use crate::test::util::new_empty_model; - -#[test] -fn fn_or() { - let mut model = new_empty_model(); - model._set("A1", "=OR(1, 0)"); - model._set("A2", "=OR(0, 0)"); - model._set("A3", "=OR(true, false)"); - model._set("A4", "=OR(false, false)"); - - model.evaluate(); - assert_eq!(model._get_text("A1"), *"TRUE"); - assert_eq!(model._get_text("A2"), *"FALSE"); - assert_eq!(model._get_text("A3"), *"TRUE"); - assert_eq!(model._get_text("A4"), *"FALSE"); -} - -#[test] -fn fn_or_no_arguments() { - let mut model = new_empty_model(); - model._set("A1", "=OR()"); - model.evaluate(); - assert_eq!(model._get_text("A1"), *"#ERROR!"); -} - -#[test] -fn fn_or_missing_arguments() { - let mut model = new_empty_model(); - model._set("A1", "=OR(,)"); - model._set("A2", "=OR(,1)"); - model.evaluate(); - assert_eq!(model._get_text("A1"), *"FALSE"); - assert_eq!(model._get_text("A2"), *"TRUE"); -} diff --git a/base/src/test/test_fn_or_xor.rs b/base/src/test/test_fn_or_xor.rs new file mode 100644 index 0000000..9630a63 --- /dev/null +++ b/base/src/test/test_fn_or_xor.rs @@ -0,0 +1,204 @@ +#![allow(clippy::unwrap_used)] +#![allow(clippy::print_stdout)] +use crate::test::util::new_empty_model; + +// These tests are grouped because in many cases XOR and OR have similar behaviour. + +// Test specific to xor +#[test] +fn fn_xor() { + let mut model = new_empty_model(); + + model._set("A1", "=XOR(1, 1, 1, 0, 0)"); + model._set("A2", "=XOR(1, 1, 0, 0, 0)"); + model._set("A3", "=XOR(TRUE, TRUE, TRUE, FALSE, FALSE)"); + model._set("A4", "=XOR(TRUE, TRUE, FALSE, FALSE, FALSE)"); + model._set("A5", "=XOR(FALSE, FALSE, FALSE, FALSE, FALSE)"); + model._set("A6", "=XOR(TRUE, TRUE)"); + model._set("A7", "=XOR(0,0,0)"); + model._set("A8", "=XOR(0,0,1)"); + model._set("A9", "=XOR(0,1,0)"); + model._set("A10", "=XOR(0,1,1)"); + model._set("A11", "=XOR(1,0,0)"); + model._set("A12", "=XOR(1,0,1)"); + model._set("A13", "=XOR(1,1,0)"); + model._set("A14", "=XOR(1,1,1)"); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), *"TRUE"); + assert_eq!(model._get_text("A2"), *"FALSE"); + assert_eq!(model._get_text("A3"), *"TRUE"); + assert_eq!(model._get_text("A4"), *"FALSE"); + assert_eq!(model._get_text("A5"), *"FALSE"); + assert_eq!(model._get_text("A6"), *"FALSE"); + assert_eq!(model._get_text("A7"), *"FALSE"); + assert_eq!(model._get_text("A8"), *"TRUE"); + assert_eq!(model._get_text("A9"), *"TRUE"); + assert_eq!(model._get_text("A10"), *"FALSE"); + assert_eq!(model._get_text("A11"), *"TRUE"); + assert_eq!(model._get_text("A12"), *"FALSE"); + assert_eq!(model._get_text("A13"), *"FALSE"); + assert_eq!(model._get_text("A14"), *"TRUE"); +} + +#[test] +fn fn_or() { + let mut model = new_empty_model(); + + model._set("A1", "=OR(1, 1, 1, 0, 0)"); + model._set("A2", "=OR(1, 1, 0, 0, 0)"); + model._set("A3", "=OR(TRUE, TRUE, TRUE, FALSE, FALSE)"); + model._set("A4", "=OR(TRUE, TRUE, FALSE, FALSE, FALSE)"); + model._set("A5", "=OR(FALSE, FALSE, FALSE, FALSE, FALSE)"); + model._set("A6", "=OR(TRUE, TRUE)"); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), *"TRUE"); + assert_eq!(model._get_text("A2"), *"TRUE"); + assert_eq!(model._get_text("A3"), *"TRUE"); + assert_eq!(model._get_text("A4"), *"TRUE"); + assert_eq!(model._get_text("A5"), *"FALSE"); + assert_eq!(model._get_text("A6"), *"TRUE"); +} + +#[test] +fn fn_or_xor() { + inner("or"); + inner("xor"); + + fn inner(func: &str) { + println!("Testing function: {func}"); + + let mut model = new_empty_model(); + + // Text args + model._set("A1", &format!(r#"={func}("")"#)); + model._set("A2", &format!(r#"={func}("", "")"#)); + model._set("A3", &format!(r#"={func}("", TRUE)"#)); + model._set("A4", &format!(r#"={func}("", FALSE)"#)); + + model._set("A5", &format!("={func}(FALSE, TRUE)")); + model._set("A6", &format!("={func}(FALSE, FALSE)")); + model._set("A7", &format!("={func}(TRUE, FALSE)")); + + // Reference to empty cell, plus true argument + model._set("A8", &format!("={func}(Z99, 1)")); + + // Reference to empty cell/range + model._set("A9", &format!("={func}(Z99)")); + model._set("A10", &format!("={func}(X99:Z99")); + + // Reference to cell with reference to empty range + model._set("B11", "=X99:Z99"); + model._set("A11", &format!("={func}(B11)")); + + // Reference to cell with non-empty range + model._set("X12", "1"); + model._set("B12", "=X12:Z12"); + model._set("A12", &format!("={func}(B12)")); + + // Reference to text cell + model._set("B13", "some_text"); + model._set("A13", &format!("={func}(B13)")); + model._set("A14", &format!("={func}(B13, 0)")); + model._set("A15", &format!("={func}(B13, 1)")); + + // Reference to Implicit intersection + model._set("X16", "1"); + model._set("B16", "=@X15:X16"); + model._set("A16", &format!("={func}(B16)")); + + // Non-empty range + model._set("B17", "1"); + model._set("A17", &format!("={func}(B17:C17)")); + + // Non-empty range with text + model._set("B18", "text"); + model._set("A18", &format!("={func}(B18:C18)")); + + // Non-empty range with text and number + model._set("B19", "text"); + model._set("C19", "1"); + model._set("A19", &format!("={func}(B19:C19)")); + + // range with error + model._set("B20", "=1/0"); + model._set("A20", &format!("={func}(B20:C20)")); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), *"#VALUE!"); + assert_eq!(model._get_text("A2"), *"#VALUE!"); + assert_eq!(model._get_text("A3"), *"TRUE"); + assert_eq!(model._get_text("A4"), *"FALSE"); + + assert_eq!(model._get_text("A5"), *"TRUE"); + assert_eq!(model._get_text("A6"), *"FALSE"); + assert_eq!(model._get_text("A7"), *"TRUE"); + + assert_eq!(model._get_text("A8"), *"TRUE"); + + assert_eq!(model._get_text("A9"), *"#VALUE!"); + assert_eq!(model._get_text("A10"), *"#VALUE!"); + + assert_eq!(model._get_text("A11"), *"#VALUE!"); + + // TODO: This one depends on spill behaviour which isn't implemented yet + // assert_eq!(model._get_text("A12"), *"TRUE"); + + assert_eq!(model._get_text("A13"), *"#VALUE!"); + assert_eq!(model._get_text("A14"), *"FALSE"); + assert_eq!(model._get_text("A15"), *"TRUE"); + + // TODO: This one depends on @ implicit intersection behaviour which isn't implemented yet + // assert_eq!(model._get_text("A16"), *"TRUE"); + + assert_eq!(model._get_text("A17"), *"TRUE"); + + assert_eq!(model._get_text("A18"), *"#VALUE!"); + + assert_eq!(model._get_text("A19"), *"TRUE"); + + assert_eq!(model._get_text("A20"), *"#DIV/0!"); + } +} + +#[test] +fn fn_or_xor_no_arguments() { + inner("or"); + inner("xor"); + + fn inner(func: &str) { + println!("Testing function: {func}"); + + let mut model = new_empty_model(); + model._set("A1", &format!("={}()", func)); + model.evaluate(); + assert_eq!(model._get_text("A1"), *"#ERROR!"); + } +} + +#[test] +fn fn_or_xor_missing_arguments() { + inner("or"); + inner("xor"); + + fn inner(func: &str) { + println!("Testing function: {func}"); + + let mut model = new_empty_model(); + model._set("A1", &format!("={func}(,)")); + model._set("A2", &format!("={func}(,1)")); + model._set("A3", &format!("={func}(1,)")); + model._set("A4", &format!("={func}(,B1)")); + model._set("A5", &format!("={func}(,B1:B4)")); + model.evaluate(); + assert_eq!(model._get_text("A1"), *"FALSE"); + assert_eq!(model._get_text("A2"), *"TRUE"); + assert_eq!(model._get_text("A3"), *"TRUE"); + assert_eq!(model._get_text("A4"), *"FALSE"); + assert_eq!(model._get_text("A5"), *"FALSE"); + } +} diff --git a/xlsx/tests/calc_tests/AND_OR_XOR.xlsx b/xlsx/tests/calc_tests/AND_OR_XOR.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..1dbc15b14f4002e0861af8fae0d500cf53ff3e96 GIT binary patch literal 14999 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