UPDATE: Dump of initial files

This commit is contained in:
Nicolás Hatcher
2023-11-18 21:26:18 +01:00
commit c5b8efd83d
279 changed files with 42654 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
mod test_bessel;
mod test_bit_operations;
mod test_complex;
mod test_convert;
mod test_misc;
mod test_number_basis;

View File

@@ -0,0 +1,53 @@
use crate::test::util::new_empty_model;
#[test]
fn fn_besseli() {
let mut model = new_empty_model();
model._set("B1", "=BESSELI()");
model._set("B2", "=BESSELI(1,2, 1)");
model.evaluate();
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_besselj() {
let mut model = new_empty_model();
model._set("B1", "=BESSELJ()");
model._set("B2", "=BESSELJ(1,2, 1)");
model.evaluate();
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_besselk() {
let mut model = new_empty_model();
model._set("B1", "=BESSELK()");
model._set("B2", "=BESSELK(1,2, 1)");
model.evaluate();
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_bessely() {
let mut model = new_empty_model();
model._set("B1", "=BESSELY()");
model._set("B2", "=BESSELY(1,2, 1)");
model.evaluate();
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}

View File

@@ -0,0 +1,99 @@
use crate::test::util::new_empty_model;
#[test]
fn fn_bitand() {
let mut model = new_empty_model();
model._set("A1", "=BITAND(1,5)");
model._set("A2", "=BITAND(13, 25");
model._set("B1", "=BITAND(1)");
model._set("B2", "=BITAND(1, 2, 3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "1");
assert_eq!(model._get_text("A2"), "9");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_bitor() {
let mut model = new_empty_model();
model._set("A1", "=BITOR(1, 5)");
model._set("A2", "=BITOR(13, 10");
model._set("B1", "=BITOR(1)");
model._set("B2", "=BITOR(1, 2, 3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "5");
assert_eq!(model._get_text("A2"), "15");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_bitxor() {
let mut model = new_empty_model();
model._set("A1", "=BITXOR(1, 5)");
model._set("A2", "=BITXOR(13, 25");
model._set("B1", "=BITXOR(1)");
model._set("B2", "=BITXOR(1, 2, 3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "4");
assert_eq!(model._get_text("A2"), "20");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_bitlshift() {
let mut model = new_empty_model();
model._set("A1", "=BITLSHIFT(4, 2)");
model._set("A2", "=BITLSHIFT(13, 7");
model._set("B1", "=BITLSHIFT(1)");
model._set("B2", "=BITLSHIFT(1, 2, 3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "16");
assert_eq!(model._get_text("A2"), "1664");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_bitrshift() {
let mut model = new_empty_model();
model._set("A1", "=BITRSHIFT(4, 2)");
model._set("A2", "=BITRSHIFT(13, 7");
model._set("A3", "=BITRSHIFT(145, -3");
model._set("B1", "=BITRSHIFT(1)");
model._set("B2", "=BITRSHIFT(1, 2, 3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "1");
assert_eq!(model._get_text("A2"), "0");
assert_eq!(model._get_text("A3"), "1160");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
// Excel does not pass this test (g sheets does)
#[test]
fn fn_bitshift_overflow() {
let mut model = new_empty_model();
model._set("A1", "=BITRSHIFT(12, -53)");
model._set("A2", "=BITLSHIFT(12, 53)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#NUM!");
assert_eq!(model._get_text("A2"), *"#NUM!");
}

View File

@@ -0,0 +1,162 @@
use crate::test::util::new_empty_model;
#[test]
fn fn_complex() {
let mut model = new_empty_model();
model._set("A1", r#"=COMPLEX(3, 4.5, "i")"#);
model._set("A2", r#"=COMPLEX(3, -5)"#);
model._set("A3", r#"=COMPLEX(0, 42, "j")"#);
model._set("B1", "=COMPLEX()");
model._set("B2", r#"=COMPLEX(1,2, "i", 1)"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "3+4.5i");
assert_eq!(model._get_text("A2"), "3-5i");
assert_eq!(model._get_text("A3"), "42j");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_imabs() {
let mut model = new_empty_model();
model._set("A1", r#"=IMABS("3+4i")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "5");
}
#[test]
fn fn_imaginary() {
let mut model = new_empty_model();
model._set("A1", r#"=IMAGINARY("3+4i")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "4");
}
#[test]
fn fn_imreal() {
let mut model = new_empty_model();
model._set("A1", r#"=IMREAL("3+4i")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "3");
}
#[test]
fn fn_imargument() {
let mut model = new_empty_model();
model._set("A1", r#"=IMARGUMENT("4+3i")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "0.643501109");
}
#[test]
fn fn_imconjugate() {
let mut model = new_empty_model();
model._set("A1", r#"=IMCONJUGATE("3+4i")"#);
model._set("A2", r#"=IMCONJUGATE("12.7-32j")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "3-4i");
assert_eq!(model._get_text("A2"), "12.7+32j");
}
#[test]
fn fn_imcos() {
let mut model = new_empty_model();
model._set("A1", r#"=IMCOS("4+3i")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "-6.58066304055116+7.58155274274654i");
}
#[test]
fn fn_imsin() {
let mut model = new_empty_model();
model._set("A1", r#"=IMSIN("4+3i")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "-7.61923172032141-6.548120040911i");
}
#[test]
fn fn_imaginary_misc() {
let mut model = new_empty_model();
model._set("A1", r#"=IMAGINARY("3.4i")"#);
model._set("A2", r#"=IMAGINARY("-3.4")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "3.4");
assert_eq!(model._get_text("A2"), "0");
}
#[test]
fn fn_imcosh() {
let mut model = new_empty_model();
model._set("A1", r#"=IMCOSH("4+3i")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "-27.0349456030742+3.85115333481178i");
}
#[test]
fn fn_imcot() {
let mut model = new_empty_model();
model._set("A1", r#"=IMCOT("4+3i")"#);
model.evaluate();
assert_eq!(
model._get_text("A1"),
"0.0049011823943045-0.999266927805902i"
);
}
#[test]
fn fn_imtan() {
let mut model = new_empty_model();
model._set("A1", r#"=IMTAN("4+3i")"#);
model.evaluate();
assert_eq!(
model._get_text("A1"),
"0.00490825806749608+1.00070953606723i"
);
}
#[test]
fn fn_power() {
let mut model = new_empty_model();
model._set("A2", r#"=IMPOWER("4+3i", 3)"#);
model._set("A3", r#"=IMABS(IMSUB(IMPOWER("-i", -3), "-1"))<G1"#);
model._set("A3", r#"=IMABS(IMSUB(IMPOWER("-1", 0.5), "i"))<G1"#);
model._set("A1", r#"=IMABS(IMSUB(B1, "-1"))<G1"#);
model._set("B1", r#"=IMPOWER("i", 2)"#);
// small number
model._set("G1", "0.0000001");
model.evaluate();
assert_eq!(model._get_text("A1"), "TRUE");
assert_eq!(model._get_text("A2"), "-44+117i");
assert_eq!(model._get_text("A3"), "TRUE");
assert_eq!(model._get_text("A3"), "TRUE");
}

View File

@@ -0,0 +1,35 @@
use crate::test::util::new_empty_model;
#[test]
fn fn_convert() {
let mut model = new_empty_model();
model._set("A1", r#"=CONVERT(1, "lbm", "kg")"#);
model._set("A2", r#"=CONVERT(68, "F", "C")"#);
model._set("A3", r#"=CONVERT(2.5, "ft", "sec")"#);
model._set("A4", r#"=CONVERT(CONVERT(100,"ft","m"),"ft","m""#);
model._set("B1", "6");
model._set("A5", r#"=CONVERT(B1,"C","F")"#);
model._set("A6", r#"=CONVERT(B1,"tsp","tbs")"#);
model._set("A7", r#"=CONVERT(B1,"gal","l")"#);
model._set("A8", r#"=CONVERT(B1,"mi","km")"#);
model._set("A9", r#"=CONVERT(B1,"km","mi")"#);
model._set("A10", r#"=CONVERT(B1,"in","ft")"#);
model._set("A11", r#"=CONVERT(B1,"cm","in")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "0.45359237");
assert_eq!(model._get_text("A2"), "19.65");
assert_eq!(model._get_text("A3"), "#N/A");
assert_eq!(model._get_text("A4"), "9.290304");
assert_eq!(model._get_text("A5"), "42.8");
assert_eq!(model._get_text("A6"), "2");
assert_eq!(model._get_text("A7"), "22.712470704"); //22.71741274");
assert_eq!(model._get_text("A8"), "9.656064");
assert_eq!(model._get_text("A9"), "3.728227153");
assert_eq!(model._get_text("A10"), "0.5");
assert_eq!(model._get_text("A11"), "2.362204724");
}

View File

@@ -0,0 +1,66 @@
use crate::test::util::new_empty_model;
#[test]
fn fn_getstep() {
let mut model = new_empty_model();
model._set("A1", "=GESTEP(7, 4.6)");
model._set("A2", "=GESTEP(45, 45)");
model._set("A3", "=GESTEP(-7, -6)");
model._set("A4", "=GESTEP(0.1)");
model._set("A5", "=GESTEP(-0.1)");
model._set("B1", "=GESTEP()");
model._set("B2", "=GESTEP(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"1");
assert_eq!(model._get_text("A2"), *"1");
assert_eq!(model._get_text("A3"), *"0");
assert_eq!(model._get_text("A4"), *"1");
assert_eq!(model._get_text("A5"), *"0");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_delta() {
let mut model = new_empty_model();
model._set("A1", "=DELTA(7, 7)");
model._set("A2", "=DELTA(-7, -7)");
model._set("A3", "=DELTA(-7, 7)");
model._set("A4", "=DELTA(5, 0.5)");
model._set("A5", "=DELTA(-0, 0)");
model._set("B1", "=DELTA()");
model._set("B2", "=DELTA(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"1");
assert_eq!(model._get_text("A2"), *"1");
assert_eq!(model._get_text("A3"), *"0");
assert_eq!(model._get_text("A4"), *"0");
assert_eq!(model._get_text("A5"), *"1");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_delta_misc() {
let mut model = new_empty_model();
model._set("A1", "=3+1e-16");
model._set("A2", "=3");
model._set("A3", "=3+1e-15");
model._set("B1", "=DELTA(A1, A2)");
model._set("B2", "=DELTA(A1, A3)");
model._set("B1", "1");
model._set("B2", "0");
model.evaluate();
assert_eq!(model._get_text("B1"), *"1");
}

View File

@@ -0,0 +1,345 @@
use crate::test::util::new_empty_model;
#[test]
fn fn_bin2dec() {
let mut model = new_empty_model();
model._set("A1", "=BIN2DEC(1100100)");
model._set("A2", "=BIN2DEC(1111111111)");
model._set("B1", "=BIN2DEC()");
model._set("B2", "=BIN2DEC(1,2)");
model.evaluate();
assert_eq!(model._get_text("A1"), "100");
assert_eq!(model._get_text("A2"), "-1");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_bin2hex() {
let mut model = new_empty_model();
model._set("A1", "=BIN2HEX(11111011, 4)");
model._set("A2", "=BIN2HEX(1110)");
model._set("A3", "=BIN2HEX(1111111111)");
model._set("A4", "=BIN2HEX(1100011011)");
model._set("B1", "=BIN2HEX()");
model._set("B2", "=BIN2HEX(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "00FB");
assert_eq!(model._get_text("A2"), "E");
assert_eq!(model._get_text("A3"), "FFFFFFFFFF");
assert_eq!(model._get_text("A4"), "FFFFFFFF1B");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_bin2oct() {
let mut model = new_empty_model();
model._set("A1", "=BIN2OCT(11111011, 4)");
model._set("A2", "=BIN2OCT(1110)");
model._set("A3", "=BIN2OCT(1111111111)");
model._set("A4", "=BIN2OCT(1100011011)");
model._set("B1", "=BIN2OCT()");
model._set("B2", "=BIN2OCT(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "0373");
assert_eq!(model._get_text("A2"), "16");
assert_eq!(model._get_text("A3"), "7777777777");
assert_eq!(model._get_text("A4"), "7777777433");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_dec2bin() {
let mut model = new_empty_model();
model._set("A1", "=DEC2BIN(9, 4)");
model._set("A2", "=DEC2BIN(-100)");
model._set("A3", "=DEC2BIN(-1)");
model._set("A4", "=DEC2BIN(0, 3)");
model._set("B1", "=DEC2BIN()");
model._set("B2", "=DEC2BIN(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "1001");
assert_eq!(model._get_text("A2"), "1110011100");
assert_eq!(model._get_text("A3"), "1111111111");
assert_eq!(model._get_text("A4"), "000");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_dec2hex() {
let mut model = new_empty_model();
model._set("A1", "=DEC2HEX(100, 4)");
model._set("A2", "=DEC2HEX(-54)");
model._set("A3", "=DEC2HEX(28)");
model._set("A4", "=DEC2HEX(64, 1)");
model._set("B1", "=DEC2HEX()");
model._set("B2", "=DEC2HEX(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "0064");
assert_eq!(model._get_text("A2"), "FFFFFFFFCA");
assert_eq!(model._get_text("A3"), "1C");
assert_eq!(model._get_text("A4"), "#NUM!");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_dec2oct() {
let mut model = new_empty_model();
model._set("A1", "=DEC2OCT(58, 3)");
model._set("A2", "=DEC2OCT(-100)");
model._set("B1", "=DEC2OCT()");
model._set("B2", "=DEC2OCT(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "072");
assert_eq!(model._get_text("A2"), "7777777634");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_hex2bin() {
let mut model = new_empty_model();
model._set("A1", r#"=HEX2BIN("F", 8)"#);
model._set("A2", r#"=HEX2BIN("B7")"#);
model._set("A3", r#"=HEX2BIN("FFFFFFFFFF")"#);
model._set("B1", "=HEX2BIN()");
model._set("B2", "=HEX2BIN(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "00001111");
assert_eq!(model._get_text("A2"), "10110111");
assert_eq!(model._get_text("A3"), "1111111111");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_hex2dec() {
let mut model = new_empty_model();
model._set("A1", r#"=HEX2DEC("A5")"#);
model._set("A2", r#"=HEX2DEC("FFFFFFFF5B")"#);
model._set("A3", r#"=HEX2DEC("3DA408B9")"#);
model._set("A4", r#"=HEX2DEC("FE")"#);
model._set("B1", "=HEX2DEC()");
model._set("B2", "=HHEX2DEC(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "165");
assert_eq!(model._get_text("A2"), "-165");
assert_eq!(model._get_text("A3"), "1034160313");
assert_eq!(model._get_text("A4"), "254");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_hex2oct() {
let mut model = new_empty_model();
model._set("A1", r#"=HEX2OCT("F", 3)"#);
model._set("A2", r#"=HEX2OCT("3B4E")"#);
model._set("A3", r#"=HEX2OCT("FFFFFFFF00")"#);
model._set("B1", "=HEX2OCT()");
model._set("B2", "=HEX2OCT(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "017");
assert_eq!(model._get_text("A2"), "35516");
assert_eq!(model._get_text("A3"), "7777777400");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_oct2bin() {
let mut model = new_empty_model();
model._set("A1", r#"=OCT2BIN(3, 3)"#);
model._set("A2", r#"=OCT2BIN(7777777000)"#);
// bounds
model._set("G1", r#"=OCT2BIN(777)"#);
model._set("G2", r#"=OCT2BIN(778)"#);
model._set("B1", "=OCT2BIN()");
model._set("B2", "=OCT2BIN(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "011");
assert_eq!(model._get_text("A2"), "1000000000");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
assert_eq!(model._get_text("G1"), "111111111");
assert_eq!(model._get_text("G2"), "#NUM!");
}
#[test]
fn fn_oct2dec() {
let mut model = new_empty_model();
model._set("A1", r#"=OCT2DEC(54)"#);
model._set("A2", r#"=OCT2DEC(7777777533)"#);
model._set("B1", "=OCT2DEC()");
model._set("B2", "=OCT2DEC(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "44");
assert_eq!(model._get_text("A2"), "-165");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_oct2hex() {
let mut model = new_empty_model();
model._set("A1", r#"=OCT2HEX(100, 4)"#);
model._set("A2", r#"=OCT2HEX(7777777533)"#);
model._set("B1", "=OCT2HEX()");
model._set("B2", "=OCT2HEX(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "0040");
assert_eq!(model._get_text("A2"), "FFFFFFFF5B");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_bin2hex_misc() {
let mut model = new_empty_model();
model._set("A1", "=BIN2HEX(1100011011, -2)");
model._set("A2", "=BIN2HEX(1100011011, 11)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#NUM!");
assert_eq!(model._get_text("A2"), *"#NUM!");
}
#[test]
fn fn_bin2oct_misc() {
let mut model = new_empty_model();
model._set("A1", "=BIN2OCT(1100011011, -2)");
model._set("A2", "=BIN2OCT(1100011011, 11)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#NUM!");
assert_eq!(model._get_text("A2"), *"#NUM!");
}
#[test]
fn fn_dec2oct_misc() {
let mut model = new_empty_model();
model._set("A1", "=DEC2OCT(-1213, 1)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"7777775503");
}
#[test]
fn fn_dec2bin_misc() {
let mut model = new_empty_model();
model._set("A1", "=DEC2BIN(-511, 4)");
model._set("A2", "=DEC2BIN(TRUE, -1)");
model._set("A3", "=DEC2OCT(TRUE, -1)");
model._set("A4", "=DEC2HEX(TRUE, -1)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"1000000001");
// Note Excel here return #NUM! instead
assert_eq!(model._get_text("A2"), *"#VALUE!");
assert_eq!(model._get_text("A3"), *"#VALUE!");
assert_eq!(model._get_text("A4"), *"#VALUE!");
}
#[test]
fn fn_hex2whatever_misc() {
let mut model = new_empty_model();
model._set("A1", r#"=HEX2BIN(TRUE, 4)"#);
model._set("A2", r#"=HEX2DEC(TRUE, 4)"#);
model._set("A3", r#"=HEX2OCT(TRUE, 4)"#);
model.evaluate();
// Note Excel here return #VALUE! instead
assert_eq!(model._get_text("A1"), *"#NUM!");
assert_eq!(model._get_text("A2"), *"#NUM!");
assert_eq!(model._get_text("A3"), *"#NUM!");
}
#[test]
fn fn_oct2whatever_misc() {
let mut model = new_empty_model();
model._set("A1", r#"=OCT2BIN(TRUE, 4)"#);
model._set("A2", r#"=OCT2DEC(TRUE, 4)"#);
model._set("A3", r#"=OCT2HEX(TRUE, 4)"#);
model.evaluate();
// Note Excel here return #VALUE! instead
assert_eq!(model._get_text("A1"), *"#NUM!");
assert_eq!(model._get_text("A2"), *"#NUM!");
assert_eq!(model._get_text("A3"), *"#NUM!");
}
#[test]
fn fn_oct2dec_misc() {
let mut model = new_empty_model();
model._set("A1", r#"=OCT2DEC(777)"#);
model._set("A2", r#"=OCT2DEC("777")"#);
model._set("A3", r#"=OCT2DEC("-1")"#);
model._set("A4", r#"=OCT2BIN("-1")"#);
model._set("A5", r#"=OCT2HEX("-1")"#);
model._set("A6", r#"=OCT2DEC(4000000000)"#);
model.evaluate();
assert_eq!(model._get_text("A1"), *"511");
assert_eq!(model._get_text("A1"), *"511");
assert_eq!(model._get_text("A3"), *"#NUM!");
assert_eq!(model._get_text("A4"), *"#NUM!");
assert_eq!(model._get_text("A5"), *"#NUM!");
assert_eq!(model._get_text("A6"), *"-536870912");
}

51
base/src/test/mod.rs Normal file
View File

@@ -0,0 +1,51 @@
mod test_actions;
mod test_binary_search;
mod test_cell;
mod test_circular_references;
mod test_column_width;
mod test_criteria;
mod test_currency;
mod test_date_and_time;
mod test_error_propagation;
mod test_fn_average;
mod test_fn_averageifs;
mod test_fn_choose;
mod test_fn_concatenate;
mod test_fn_count;
mod test_fn_exact;
mod test_fn_financial;
mod test_fn_if;
mod test_fn_maxifs;
mod test_fn_minifs;
mod test_fn_product;
mod test_fn_rept;
mod test_fn_sum;
mod test_fn_sumifs;
mod test_fn_textbefore;
mod test_fn_textjoin;
mod test_forward_references;
mod test_frozen_rows_columns;
mod test_general;
mod test_math;
mod test_metadata;
mod test_model_delete_cell;
mod test_model_is_empty_cell;
mod test_model_set_cell_empty;
mod test_move_formula;
mod test_quote_prefix;
mod test_set_user_input;
mod test_sheet_markup;
mod test_sheets;
mod test_styles;
mod test_trigonometric;
mod test_worksheet;
pub(crate) mod util;
mod engineering;
mod test_fn_offset;
mod test_number_format;
mod test_escape_quotes;
mod test_fn_type;
mod test_percentage;
mod test_today;

View File

@@ -0,0 +1,287 @@
#![allow(clippy::unwrap_used)]
use crate::constants::LAST_COLUMN;
use crate::model::Model;
use crate::test::util::new_empty_model;
#[test]
fn test_insert_columns() {
let mut model = new_empty_model();
// We populate cells A1 to C1
model._set("A1", "1");
model._set("B1", "2");
model._set("C1", "=B1*2");
model._set("F1", "=B1");
model._set("L11", "300");
model._set("M11", "=L11*5");
model.evaluate();
assert_eq!(model._get_text("C1"), *"4");
// Let's insert 5 columns in column F (6)
let r = model.insert_columns(0, 6, 5);
assert!(r.is_ok());
model.evaluate();
// Check F1 is now empty
assert!(model.is_empty_cell(0, 1, 6).unwrap());
// The old F1 is K1
assert_eq!(model._get_formula("K1"), *"=B1");
// L11 and M11 are Q11 and R11
assert_eq!(model._get_text("Q11"), *"300");
assert_eq!(model._get_formula("R11"), *"=Q11*5");
assert_eq!(model._get_formula("C1"), "=B1*2");
assert_eq!(model._get_text("A1"), "1");
// inserting a negative number of columns fails:
let r = model.insert_columns(0, 6, -5);
assert!(r.is_err());
let r = model.insert_columns(0, 6, -5);
assert!(r.is_err());
// If you have data at the very ebd it fails
model._set("XFC12", "300");
let r = model.insert_columns(0, 6, 5);
assert!(r.is_err());
}
#[test]
fn test_insert_rows() {
let mut model = new_empty_model();
model._set("C4", "3");
model._set("C5", "7");
model._set("C6", "=C5");
model._set("H11", "=C4");
model._set("R10", "=C6");
model.evaluate();
// Let's insert 5 rows in row 6
let r = model.insert_rows(0, 6, 5);
assert!(r.is_ok());
model.evaluate();
// Check C6 is now empty
assert!(model.is_empty_cell(0, 6, 3).unwrap());
// Old C6 is now C11
assert_eq!(model._get_formula("C11"), *"=C5");
assert_eq!(model._get_formula("H16"), *"=C4");
assert_eq!(model._get_formula("R15"), *"=C11");
assert_eq!(model._get_text("C4"), *"3");
assert_eq!(model._get_text("C5"), *"7");
}
#[test]
fn test_insert_rows_styles() {
let mut model = new_empty_model();
assert!(
(21.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
);
// sets height 42 in row 10
model
.workbook
.worksheet_mut(0)
.unwrap()
.set_row_height(10, 42.0)
.unwrap();
assert!(
(42.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
);
// Let's insert 5 rows in row 3
let r = model.insert_rows(0, 3, 5);
assert!(r.is_ok());
// Row 10 has the default height
assert!(
(21.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
);
// Row 10 is now row 15
assert!(
(42.0 - model.workbook.worksheet(0).unwrap().row_height(15).unwrap()).abs() < f64::EPSILON
);
}
#[test]
fn test_delete_rows_styles() {
let mut model = new_empty_model();
assert!(
(21.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
);
// sets height 42 in row 10
model
.workbook
.worksheet_mut(0)
.unwrap()
.set_row_height(10, 42.0)
.unwrap();
assert!(
(42.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
);
// Let's delete 5 rows in row 3 (3-8)
let r = model.delete_rows(0, 3, 5);
assert!(r.is_ok());
// Row 10 has the default height
assert!(
(21.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
);
// Row 10 is now row 5
assert!(
(42.0 - model.workbook.worksheet(0).unwrap().row_height(5).unwrap()).abs() < f64::EPSILON
);
}
#[test]
fn test_delete_columns() {
let mut model = new_empty_model();
model._set("C4", "3");
model._set("D4", "7");
model._set("E4", "=D4");
model._set("F4", "=C4");
model._set("H11", "=D4");
model._set("R10", "=C6");
model._set("M5", "300");
model._set("N5", "=M5*6");
model._set("A1", "=SUM(M5:N5)");
model._set("A2", "=SUM(C4:M4)");
model._set("A3", "=SUM(E4:M4)");
model.evaluate();
// We delete columns D and E
let r = model.delete_columns(0, 4, 2);
assert!(r.is_ok());
model.evaluate();
// Old H11 will be F11 and contain =#REF!
assert_eq!(model._get_formula("F11"), *"=#REF!");
// Old F4 will be D4 now
assert_eq!(model._get_formula("D4"), *"=C4");
// Old N5 will be L5
assert_eq!(model._get_formula("L5"), *"=K5*6");
// Range in A1 is displaced correctly
assert_eq!(model._get_formula("A1"), *"=SUM(K5:L5)");
// Note that range in A2 would contain some of the deleted cells
// A long as the borders of the range are not included that's ok.
assert_eq!(model._get_formula("A2"), *"=SUM(C4:K4)");
// FIXME: In Excel this would be (lower limit won't change)
// assert_eq!(model._get_formula("A3"), *"=SUM(E4:K4)");
assert_eq!(model._get_formula("A3"), *"=SUM(#REF!:K4)");
}
#[test]
fn test_delete_rows() {
let mut model = new_empty_model();
model._set("C4", "4");
model._set("C5", "5");
model._set("C6", "6");
model._set("C7", "=C6*2");
model._set("C72", "=C1*3");
model.evaluate();
// We delete rows 5, 6
let r = model.delete_rows(0, 5, 2);
assert!(r.is_ok());
model.evaluate();
assert_eq!(model._get_formula("C5"), *"=#REF!*2");
assert_eq!(model._get_formula("C70"), *"=C1*3");
}
// E F G H I J K
// 3 1 1 2
// 4 2 5 8
// -2 3 6 7
fn populate_table(model: &mut Model) {
model._set("G1", "3");
model._set("H1", "1");
model._set("I1", "1");
model._set("J1", "2");
model._set("G2", "4");
model._set("H2", "2");
model._set("I2", "5");
model._set("J2", "8");
model._set("G3", "-2");
model._set("H3", "3");
model._set("I3", "6");
model._set("J3", "7");
}
#[test]
fn test_move_column_right() {
let mut model = new_empty_model();
populate_table(&mut model);
model._set("E3", "=G3");
model._set("E4", "=H3");
model._set("E5", "=SUM(G3:J7)");
model._set("E6", "=SUM(G3:G7)");
model._set("E7", "=SUM(H3:H7)");
model.evaluate();
// Wee swap column G with column H
let result = model.move_column_action(0, 7, 1);
assert!(result.is_ok());
model.evaluate();
assert_eq!(model._get_formula("E3"), "=H3");
assert_eq!(model._get_formula("E4"), "=G3");
assert_eq!(model._get_formula("E5"), "=SUM(H3:J7)");
assert_eq!(model._get_formula("E6"), "=SUM(H3:H7)");
assert_eq!(model._get_formula("E7"), "=SUM(G3:G7)");
}
#[test]
fn tets_move_column_error() {
let mut model = new_empty_model();
model.evaluate();
let result = model.move_column_action(0, 7, -10);
assert!(result.is_err());
let result = model.move_column_action(0, -7, 20);
assert!(result.is_err());
let result = model.move_column_action(0, LAST_COLUMN, 1);
assert!(result.is_err());
let result = model.move_column_action(0, LAST_COLUMN + 1, -10);
assert!(result.is_err());
// This works
let result = model.move_column_action(0, LAST_COLUMN, -1);
assert!(result.is_ok());
}
// A B C D E F G H I J K L M N O P Q R
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

View File

@@ -0,0 +1,28 @@
use crate::functions::binary_search::*;
#[test]
fn test_binary_search() {
let t = vec![1, 2, 3, 40, 55, 155];
assert_eq!(binary_search_or_smaller(&40, &t), Some(3));
assert_eq!(binary_search_or_greater(&40, &t), Some(3));
assert_eq!(binary_search_or_smaller(&45, &t), Some(3));
assert_eq!(binary_search_or_greater(&45, &t), Some(4));
}
#[test]
fn test_binary_search_descending() {
let t = vec![100, 33, 23, 14, 5, -155];
assert_eq!(binary_search_descending_or_smaller(&23, &t), Some(2));
assert_eq!(binary_search_descending_or_greater(&23, &t), Some(2));
assert_eq!(binary_search_descending_or_smaller(&25, &t), Some(2));
assert_eq!(binary_search_descending_or_greater(&25, &t), Some(1));
}
#[test]
fn test_binary_search_multiple() {
let t = vec![1, 2, 3, 40, 40, 40, 40, 55, 155];
assert_eq!(binary_search_or_smaller(&40, &t), Some(3));
assert_eq!(binary_search_or_smaller(&39, &t), Some(2));
assert_eq!(binary_search_or_greater(&40, &t), Some(3));
assert_eq!(binary_search_or_greater(&41, &t), Some(7));
}

View File

@@ -0,0 +1,35 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
use crate::types::CellType;
#[test]
fn test_cell_get_type() {
let mut model = new_empty_model();
model._set("A1", "");
model._set("A2", "42");
model._set("A3", "12.34");
model._set("A4", "foobar");
model._set("A5", "1+2");
model._set("A6", "TRUE");
model._set("A7", "#VALUE!");
model._set("A8", "=Z100"); // an empty cell, considered to be a CellType::Number
model._set("A9", "=2*3*7");
model._set("A10", "=\"foo\"");
model._set("A11", "=1/0");
model._set("A12", "=1>0");
model.evaluate();
assert_eq!(model._get_cell("A1").get_type(), CellType::Text);
assert_eq!(model._get_cell("A2").get_type(), CellType::Number);
assert_eq!(model._get_cell("A3").get_type(), CellType::Number);
assert_eq!(model._get_cell("A4").get_type(), CellType::Text);
assert_eq!(model._get_cell("A5").get_type(), CellType::Text);
assert_eq!(model._get_cell("A6").get_type(), CellType::LogicalValue);
assert_eq!(model._get_cell("A7").get_type(), CellType::ErrorValue);
assert_eq!(model._get_cell("A8").get_type(), CellType::Number);
assert_eq!(model._get_cell("A9").get_type(), CellType::Number);
assert_eq!(model._get_cell("A10").get_type(), CellType::Text);
assert_eq!(model._get_cell("A11").get_type(), CellType::ErrorValue);
assert_eq!(model._get_cell("A12").get_type(), CellType::LogicalValue);
}

View File

@@ -0,0 +1,27 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_simple_circ() {
let mut model = new_empty_model();
model._set("A1", "=A1+1");
model.evaluate();
assert_eq!(model._get_text("A1"), "#CIRC!");
}
#[test]
fn test_simple_circ_propagate() {
let mut model = new_empty_model();
model._set("A1", "=B6");
model._set("A2", "=A1+1");
model._set("A3", "=A2+1");
model._set("A4", "=A3+5");
model._set("B6", "=A4*7");
model.evaluate();
assert_eq!(model._get_text("A1"), "#CIRC!");
assert_eq!(model._get_text("A2"), "#CIRC!");
assert_eq!(model._get_text("A3"), "#CIRC!");
assert_eq!(model._get_text("A4"), "#CIRC!");
assert_eq!(model._get_text("B6"), "#CIRC!");
}

View File

@@ -0,0 +1,82 @@
#![allow(clippy::unwrap_used)]
use crate::constants::{COLUMN_WIDTH_FACTOR, DEFAULT_COLUMN_WIDTH};
use crate::test::util::new_empty_model;
use crate::types::Col;
#[test]
fn test_column_width() {
let mut model = new_empty_model();
let cols = vec![Col {
custom_width: false,
max: 16384,
min: 1,
style: Some(6),
width: 8.7,
}];
model.workbook.worksheets[0].cols = cols;
model
.workbook
.worksheet_mut(0)
.unwrap()
.set_column_width(2, 30.0)
.unwrap();
assert_eq!(model.workbook.worksheets[0].cols.len(), 3);
let worksheet = model.workbook.worksheet(0).unwrap();
assert!((worksheet.column_width(1).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
assert!((worksheet.column_width(2).unwrap() - 30.0).abs() < f64::EPSILON);
assert!((worksheet.column_width(3).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
assert_eq!(model.get_cell_style_index(0, 23, 2), 6);
}
#[test]
fn test_column_width_lower_edge() {
let mut model = new_empty_model();
let cols = vec![Col {
custom_width: true,
max: 16,
min: 5,
style: Some(1),
width: 10.0,
}];
model.workbook.worksheets[0].cols = cols;
model
.workbook
.worksheet_mut(0)
.unwrap()
.set_column_width(5, 30.0)
.unwrap();
assert_eq!(model.workbook.worksheets[0].cols.len(), 2);
let worksheet = model.workbook.worksheet(0).unwrap();
assert!((worksheet.column_width(4).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
assert!((worksheet.column_width(5).unwrap() - 30.0).abs() < f64::EPSILON);
assert!((worksheet.column_width(6).unwrap() - 10.0 * COLUMN_WIDTH_FACTOR).abs() < f64::EPSILON);
assert_eq!(model.get_cell_style_index(0, 23, 5), 1);
}
#[test]
fn test_column_width_higher_edge() {
let mut model = new_empty_model();
let cols = vec![Col {
custom_width: true,
max: 16,
min: 5,
style: Some(1),
width: 10.0,
}];
model.workbook.worksheets[0].cols = cols;
model
.workbook
.worksheet_mut(0)
.unwrap()
.set_column_width(16, 30.0)
.unwrap();
assert_eq!(model.workbook.worksheets[0].cols.len(), 2);
let worksheet = model.workbook.worksheet(0).unwrap();
assert!(
(worksheet.column_width(15).unwrap() - 10.0 * COLUMN_WIDTH_FACTOR).abs() < f64::EPSILON
);
assert!((worksheet.column_width(16).unwrap() - 30.0).abs() < f64::EPSILON);
assert!((worksheet.column_width(17).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
assert_eq!(model.get_cell_style_index(0, 23, 16), 1);
}

View File

@@ -0,0 +1,86 @@
use crate::calc_result::CalcResult;
use crate::functions::util::build_criteria;
// Tests for build_criteria
// ------------------------
//
// Note that any test here is mostly for documentation purposes.
// A real test must be done in Excel.
//
// `build_criteria` takes a string ('criteria') and returns a function ('fn_criteria') that takes a CalcResult and returns a boolean.
//
// For instance if criteria is "123" we want all cells that contain the number "123". Then
//
// let fn_criteria = build_criteria(&CalcResult::Number(123));
//
// Then fn_criteria(calc_result) will return true every time calc_result is the number "123"
//
// There are different types of criteria
//
// * We want the cells that are equal to a value (say a number, string, bool or an error).
// We can build those with a calc_result of the type (i.e CalcResult::Number(123))
// or we can use a string preceded by "=" like CalcResult::String("=123")
// * We can use inequality signs "<", ">", "<=", ">=" or "<>"
// * If you use "=" or "<>" you can use wildcards (like "=*brown")
//
// All of them are case insensitive.
#[test]
fn test_build_criteria_is_number() {
let c = CalcResult::Number(42.0);
let fn_criteria = build_criteria(&c);
assert!(fn_criteria(&CalcResult::Number(42.0)));
assert!(fn_criteria(&CalcResult::String("42".to_string())));
assert!(fn_criteria(&CalcResult::String("42.00".to_string())));
assert!(!fn_criteria(&CalcResult::Number(2.0)));
let c = CalcResult::String("=42".to_string());
let fn_criteria = build_criteria(&c);
assert!(fn_criteria(&CalcResult::Number(42.0)));
assert!(fn_criteria(&CalcResult::String("42".to_string())));
assert!(fn_criteria(&CalcResult::String("42.00".to_string())));
assert!(!fn_criteria(&CalcResult::Number(2.0)));
}
#[test]
fn test_build_criteria_is_bool() {
let c = CalcResult::Boolean(true);
let fn_criteria = build_criteria(&c);
assert!(fn_criteria(&CalcResult::Boolean(true)));
assert!(!fn_criteria(&CalcResult::String("true".to_string())));
assert!(!fn_criteria(&CalcResult::Number(1.0)));
let c = CalcResult::String("=True".to_string());
let fn_criteria = build_criteria(&c);
assert!(fn_criteria(&CalcResult::Boolean(true)));
assert!(!fn_criteria(&CalcResult::String("true".to_string())));
assert!(!fn_criteria(&CalcResult::Number(1.0)));
}
#[test]
fn test_build_criteria_is_less_than() {
let c = CalcResult::String("<100".to_string());
let fn_criteria = build_criteria(&c);
assert!(!fn_criteria(&CalcResult::Boolean(true)));
assert!(!fn_criteria(&CalcResult::String("23".to_string())));
assert!(fn_criteria(&CalcResult::Number(1.0)));
assert!(!fn_criteria(&CalcResult::Number(101.0)));
}
#[test]
fn test_build_criteria_is_less_wildcard() {
let c = CalcResult::String("=D* G*".to_string());
let fn_criteria = build_criteria(&c);
assert!(fn_criteria(&CalcResult::String(
"Diarmuid Glynn".to_string()
)));
assert!(fn_criteria(&CalcResult::String(
"Daniel Gonzalez".to_string()
)));
assert!(!fn_criteria(&CalcResult::String(
"DanielGonzalez".to_string()
)));
assert!(!fn_criteria(&CalcResult::String(
" Daniel Gonzalez".to_string()
)));
}

View File

@@ -0,0 +1,24 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_cell_currency_dollar() {
let mut model = new_empty_model();
model._set("A1", "=PMT(8/1200,10,10000)");
model.evaluate();
assert_eq!(model._get_text("A1"), "-$1,037.03");
assert!(model.set_currency("EUR").is_ok());
}
#[test]
fn test_cell_currency_euro() {
let mut model = new_empty_model();
assert!(model.set_currency("EUR").is_ok());
model._set("A1", "=PMT(8/1200,10,10000)");
model.evaluate();
assert_eq!(model._get_text("A1"), "-€1,037.03");
}

View File

@@ -0,0 +1,215 @@
#![allow(clippy::unwrap_used)]
/// Here we add tests that cannot be done in Excel
/// Either because Excel does not have that feature (i.e. wrong number of arguments)
/// or because we differ from Excel throwing #NUM! on invalid dates
/// We can also enter examples that illustrate/document a part of the function
use crate::{cell::CellValue, test::util::new_empty_model};
#[test]
fn test_fn_date_arguments() {
let mut model = new_empty_model();
// Wrong number of arguments produce #ERROR!
// NB: Excel does not have this error, but does not let you enter wrong number of arguments in the UI
model._set("A1", "=DATE()");
model._set("A2", "=DATE(1975)");
model._set("A3", "=DATE(1975, 2)");
model._set("A4", "=DATE(1975, 2, 10, 3)");
// Arguments are out of rage. This is against Excel
// Excel will actually compute a date by continuing to the next month, year...
// We throw #NUM!
model._set("A5", "=DATE(1975, -2, 10)");
model._set("A6", "=DATE(1975, 2, -10)");
model._set("A7", "=DATE(1975, 14, 10)");
// February doesn't have 30 days
model._set("A8", "=DATE(1975, 2, 30)");
// 1975, a great year, wasn't a leap year
model._set("A9", "=DATE(1975, 2, 29)");
// 1976 was
model._set("A10", "=DATE(1976, 2, 29)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"#ERROR!");
assert_eq!(model._get_text("A3"), *"#ERROR!");
assert_eq!(model._get_text("A4"), *"#ERROR!");
assert_eq!(model._get_text("A5"), *"#NUM!");
assert_eq!(model._get_text("A6"), *"#NUM!");
assert_eq!(model._get_text("A7"), *"#NUM!");
assert_eq!(model._get_text("A8"), *"#NUM!");
assert_eq!(model._get_text("A9"), *"#NUM!");
assert_eq!(model._get_text("A10"), *"29/02/1976");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A10"),
Ok(CellValue::Number(27819.0))
);
}
#[test]
fn test_date_out_of_range() {
let mut model = new_empty_model();
// month
model._set("A1", "=DATE(2022, 0, 10)");
model._set("A2", "=DATE(2022, 13, 10)");
// day
model._set("B1", "=DATE(2042, 5, 0)");
model._set("B2", "=DATE(2025, 5, 32)");
// year (actually years < 1900 don't really make sense)
model._set("C1", "=DATE(-1, 5, 5)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#NUM!");
assert_eq!(model._get_text("A2"), *"#NUM!");
assert_eq!(model._get_text("B1"), *"#NUM!");
assert_eq!(model._get_text("B2"), *"#NUM!");
assert_eq!(model._get_text("C1"), *"#NUM!");
}
#[test]
fn test_year_arguments() {
let mut model = new_empty_model();
model._set("A1", "=YEAR()");
model._set("A2", "=YEAR(27819)");
model._set("A3", "=YEAR(27819, 3)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"1976");
assert_eq!(model._get_text("A3"), *"#ERROR!");
}
#[test]
fn test_month_arguments() {
let mut model = new_empty_model();
model._set("A1", "=MONTH()");
model._set("A2", "=MONTH(27819)");
model._set("A3", "=MONTH(27819, 3)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"2");
assert_eq!(model._get_text("A3"), *"#ERROR!");
}
#[test]
fn test_day_arguments() {
let mut model = new_empty_model();
model._set("A1", "=DAY()");
model._set("A2", "=DAY(27819)");
model._set("A3", "=DAY(27819, 3)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"29");
assert_eq!(model._get_text("A3"), *"#ERROR!");
}
#[test]
fn test_day_small_serial() {
let mut model = new_empty_model();
model._set("A1", "=DAY(-1)");
model._set("A2", "=DAY(0)");
model._set("A3", "=DAY(60)");
model._set("A4", "=DAY(61)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#NUM!");
// This agrees with Google Docs and disagrees with Excel
assert_eq!(model._get_text("A2"), *"30");
// Excel thinks is Feb 29, 1900
assert_eq!(model._get_text("A3"), *"28");
// From now on everyone agrees
assert_eq!(model._get_text("A4"), *"1");
}
#[test]
fn test_month_small_serial() {
let mut model = new_empty_model();
model._set("A1", "=MONTH(-1)");
model._set("A2", "=MONTH(0)");
model._set("A3", "=MONTH(60)");
model._set("A4", "=MONTH(61)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#NUM!");
// This agrees with Google Docs and disagrees with Excel
assert_eq!(model._get_text("A2"), *"12");
// We agree with Excel here (We are both in Feb)
assert_eq!(model._get_text("A3"), *"2");
// Same as Excel
assert_eq!(model._get_text("A4"), *"3");
}
#[test]
fn test_year_small_serial() {
let mut model = new_empty_model();
model._set("A1", "=YEAR(-1)");
model._set("A2", "=YEAR(0)");
model._set("A3", "=YEAR(60)");
model._set("A4", "=YEAR(61)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#NUM!");
// This agrees with Google Docs and disagrees with Excel
assert_eq!(model._get_text("A2"), *"1899");
assert_eq!(model._get_text("A3"), *"1900");
// Same as Excel
assert_eq!(model._get_text("A4"), *"1900");
}
#[test]
fn test_date_early_dates() {
let mut model = new_empty_model();
model._set("A1", "=DATE(1900, 1, 1)");
model._set("A2", "=DATE(1900, 2, 28)");
model._set("B2", "=DATE(1900, 2, 29)");
model._set("A3", "=DATE(1900, 3, 1)");
model.evaluate();
// This is 1 in Excel, we agree with Google Docs
assert_eq!(model._get_text("A1"), *"01/01/1900");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A1"),
Ok(CellValue::Number(2.0))
);
// 1900 was not a leap year, this is a bug in EXCEL
// This would be 60 in Excel
assert_eq!(model._get_text("A2"), *"28/02/1900");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A2"),
Ok(CellValue::Number(60.0))
);
assert_eq!(model._get_text("B2"), *"#NUM!");
// This agrees with Excel from he onward
assert_eq!(model._get_text("A3"), *"01/03/1900");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A3"),
Ok(CellValue::Number(61.0))
);
}

View File

@@ -0,0 +1,39 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
use crate::types::Cell;
#[test]
fn test_simple_error_propagation() {
let mut model = new_empty_model();
model._set("A1", "=1/0");
model._set("A2", "=2+A1");
model._set("A3", "=C2+A2");
model.evaluate();
match model._get_cell("Sheet1!A3") {
Cell::CellFormulaError { o, .. } => {
assert_eq!(o, "Sheet1!A1");
}
_ => panic!("Unreachable"),
}
}
#[test]
fn test_simple_errors() {
let mut model = new_empty_model();
model._set("A1", "#CALC!");
model._set("A2", "#SPILL!");
model._set("A3", "#OTHER!");
model._set("B1", "=ISERROR(A1)");
model._set("B2", "=ISERROR(A2)");
model._set("B3", "=ISERROR(A3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "#CALC!");
assert_eq!(model._get_text("A2"), "#SPILL!");
assert_eq!(model._get_text("A3"), "#OTHER!");
assert_eq!(model._get_text("B1"), "TRUE");
assert_eq!(model._get_text("B2"), "TRUE");
assert_eq!(model._get_text("B3"), "FALSE");
}

View File

@@ -0,0 +1,12 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn escape_quotes() {
let mut model = new_empty_model();
model._set("A1", r#"="TEST""ABC""#);
model.evaluate();
assert_eq!(model._get_text("A1"), *r#"TEST"ABC"#);
}

View File

@@ -0,0 +1,31 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_fn_average_arguments() {
let mut model = new_empty_model();
model._set("A1", "=AVERAGE()");
model._set("A2", "=AVERAGEA()");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"#ERROR!");
}
#[test]
fn test_fn_average_minimal() {
let mut model = new_empty_model();
model._set("B1", "1");
model._set("B2", "2");
model._set("B3", "3");
model._set("B4", "'2");
// B5 is empty
model._set("B6", "true");
model._set("A1", "=AVERAGE(B1:B6)");
model._set("A2", "=AVERAGEA(B1:B6)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"2");
assert_eq!(model._get_text("A2"), *"1.4");
}

View File

@@ -0,0 +1,40 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_fn_averageifs_arguments() {
let mut model = new_empty_model();
// Incorrect number of arguments
model._set("A1", "=AVERAGEIFS()");
model._set("A2", "=AVERAGEIFS(B2:B9)");
model._set("A3", "=AVERAGEIFS(B2:B9,C2:C9)");
model._set("A4", "=AVERAGEIFS(B2:B9,C2:C9,\"=A*\",D2:D9)");
// Correct (Sum everything in column 'B' if column 'C' starts with "A")
model._set("A5", "=AVERAGEIFS(B2:B9,C2:C9,\"=A*\")");
// Data
model._set("B2", "5");
model._set("B3", "4");
model._set("B4", "15");
model._set("B5", "22");
model._set("B6", "=NA()");
model._set("C2", "Apples");
model._set("C3", "Bananas");
model._set("C4", "Almonds");
model._set("C5", "Yoni");
model._set("C6", "Mandarin");
model.evaluate();
// Error (Incorrect number of arguments)
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"#ERROR!");
assert_eq!(model._get_text("A3"), *"#ERROR!");
assert_eq!(model._get_text("A4"), *"#ERROR!");
// Correct
assert_eq!(model._get_text("A5"), *"10");
}

View File

@@ -0,0 +1,50 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_fn_choose_args_number() {
let mut model = new_empty_model();
model._set("A1", "=CHOOSE()");
model._set("A2", "=CHOOSE(1)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"#ERROR!");
}
#[test]
fn test_fn_choose_incorrect_index() {
let mut model = new_empty_model();
model._set("A1", "=CHOOSE(-1, 42)");
model._set("A2", "=CHOOSE(0, 42)");
model._set("A3", "=CHOOSE(1, 42)");
model._set("A4", "=CHOOSE(2, 42)");
model._set("B1", "TEST");
model._set("A5", "=CHOOSE(B1, 42)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#VALUE!");
assert_eq!(model._get_text("A2"), *"#VALUE!");
assert_eq!(model._get_text("A3"), *"42");
assert_eq!(model._get_text("A4"), *"#VALUE!");
assert_eq!(model._get_text("A5"), *"#VALUE!");
}
#[test]
fn test_fn_choose_basic_tests() {
let mut model = new_empty_model();
model._set("B1", "1");
model._set("B2", "2");
model._set("B3", "3");
model._set("A1", "=CHOOSE(3.1, B1, B2, B3)");
model._set("A2", "=SUM(B1:CHOOSE(1, B1, B2, B3))");
model._set("A3", "=SUM(CHOOSE(3, B1:B1, B1:B2, B1:B3))");
model._set("A4", "=CHOOSE(3,\"Wide\",115,\"world\",8)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"3");
assert_eq!(model._get_text("A2"), *"1");
assert_eq!(model._get_text("A3"), *"6");
assert_eq!(model._get_text("A4"), *"world");
}

View File

@@ -0,0 +1,34 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn fn_concatenate_args_number() {
let mut model = new_empty_model();
model._set("A1", "=CONCATENATE()");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
}
#[test]
fn fn_concatenate() {
let mut model = new_empty_model();
model._set("A1", "Hello");
model._set("A2", " my ");
model._set("A3", "World");
model._set("B1", r#"=CONCATENATE(A1, A2, A3, "!")"#);
// This will break once we implement the implicit intersection operator
// It should be:
// model._set("B2", r#"=CONCATENATE(@A1:A3, "!")"#);
model._set("B2", r#"=CONCATENATE(A1:A3, "!")"#);
model._set("B3", r#"=CONCAT(A1:A3, "!")"#);
model.evaluate();
assert_eq!(model._get_text("B1"), *"Hello my World!");
assert_eq!(model._get_text("B2"), *" my !");
assert_eq!(model._get_text("B3"), *"Hello my World!");
}

View File

@@ -0,0 +1,37 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_fn_count_arguments() {
let mut model = new_empty_model();
model._set("A1", "=COUNT()");
model._set("A2", "=COUNTA()");
model._set("A3", "=COUNTBLANK()");
model._set("A4", "=COUNTBLANK(C1:D1, H3:H4)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"#ERROR!");
assert_eq!(model._get_text("A3"), *"#ERROR!");
assert_eq!(model._get_text("A4"), *"#ERROR!");
}
#[test]
fn test_fn_count_minimal() {
let mut model = new_empty_model();
model._set("B1", "3.1415926");
model._set("B2", "Tomorrow's the day my bride's gonna come");
model._set("B3", "");
model._set("A1", "=COUNT(B1:B5)");
model._set("A2", "=COUNTA(B1:B5)");
model._set("A3", "=COUNTBLANK(B1:B5)");
model.evaluate();
// There is only one number
assert_eq!(model._get_text("A1"), *"1");
// Thre are three non-empty cells
assert_eq!(model._get_text("A2"), *"3");
// There are 3 blank cells B4, B5 and B3 that contains the empty string
assert_eq!(model._get_text("A3"), *"3");
}

View File

@@ -0,0 +1,31 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn fn_exact_args_number() {
let mut model = new_empty_model();
model._set("A1", "=EXACT(1)");
model._set("A2", "=EXACT(1, 1, 1)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"#ERROR!");
}
#[test]
fn fn_exact() {
let mut model = new_empty_model();
model._set("A1", "=EXACT(2.3, 2.3)");
model._set("A2", r#"=EXACT(2.3, "2.3")"#);
model._set("A3", r#"=EXACT("Hello", "hello")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), *"TRUE");
assert_eq!(model._get_text("A2"), *"TRUE");
assert_eq!(model._get_text("A3"), *"FALSE");
}

View File

@@ -0,0 +1,470 @@
#![allow(clippy::unwrap_used)]
use crate::{cell::CellValue, test::util::new_empty_model};
#[test]
fn fn_arguments() {
let mut model = new_empty_model();
model._set("A1", "=PMT()");
model._set("A2", "=PMT(1,1)");
model._set("A3", "=PMT(1,1,1,1,1,1)");
model._set("B1", "=FV()");
model._set("B2", "=FV(1,1)");
model._set("B3", "=FV(1,1,1,1,1,1)");
model._set("C1", "=PV()");
model._set("C2", "=PV(1,1)");
model._set("C3", "=PV(1,1,1,1,1,1)");
model._set("D1", "=NPER()");
model._set("D2", "=NPER(1,1)");
model._set("D3", "=NPER(1,1,1,1,1,1)");
model._set("E1", "=RATE()");
model._set("E2", "=RATE(1,1)");
model._set("E3", "=RATE(1,1,1,1,1,1)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"#ERROR!");
assert_eq!(model._get_text("A3"), *"#ERROR!");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
assert_eq!(model._get_text("B3"), *"#ERROR!");
assert_eq!(model._get_text("C1"), *"#ERROR!");
assert_eq!(model._get_text("C2"), *"#ERROR!");
assert_eq!(model._get_text("C3"), *"#ERROR!");
assert_eq!(model._get_text("D1"), *"#ERROR!");
assert_eq!(model._get_text("D2"), *"#ERROR!");
assert_eq!(model._get_text("D3"), *"#ERROR!");
assert_eq!(model._get_text("E1"), *"#ERROR!");
assert_eq!(model._get_text("E2"), *"#ERROR!");
assert_eq!(model._get_text("E3"), *"#ERROR!");
}
#[test]
fn fn_impmt_ppmt_arguments() {
let mut model = new_empty_model();
model._set("A1", "=IPMT()");
model._set("A2", "=IPMT(1,1,1)");
model._set("A3", "=IPMT(1,1,1,1,1,1,1)");
model._set("B1", "=PPMT()");
model._set("B2", "=PPMT(1,1,1)");
model._set("B3", "=PPMT(1,1,1,1,1,1,1)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"#ERROR!");
assert_eq!(model._get_text("A3"), *"#ERROR!");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
assert_eq!(model._get_text("B3"), *"#ERROR!");
}
#[test]
fn fn_irr_npv_arguments() {
let mut model = new_empty_model();
model._set("A1", "=NPV()");
model._set("A2", "=NPV(1,1)");
model._set("C1", "-2"); // v0
model._set("C2", "5"); // v1
model._set("B1", "=IRR()");
model._set("B3", "=IRR(1, 2, 3, 4)");
// r such that v0 + v1/(1+r) = 0
// r = -v1/v0 - 1
model._set("B4", "=IRR(C1:C2)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"$0.50");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B3"), *"#ERROR!");
// r = 5/2-1 = 1.5
assert_eq!(model._get_text("B4"), *"150%");
}
#[test]
fn fn_mirr() {
let mut model = new_empty_model();
model._set("A2", "-120000");
model._set("A3", "39000");
model._set("A4", "30000");
model._set("A5", "21000");
model._set("A6", "37000");
model._set("A7", "46000");
model._set("A8", "0.1");
model._set("A9", "0.12");
model._set("B1", "=MIRR(A2:A7, A8, A9)");
model._set("B2", "=MIRR(A2:A5, A8, A9)");
model.evaluate();
assert_eq!(
model.get_cell_value_by_ref("Sheet1!B1"),
Ok(CellValue::Number(0.1260941303659051))
);
assert_eq!(model._get_text("B1"), "13%");
assert_eq!(model._get_text("B2"), "-5%");
}
#[test]
fn fn_mirr_div_0() {
// This test produces #DIV/0! in Excel (but it is incorrect)
let mut model = new_empty_model();
model._set("A2", "-30");
model._set("A3", "-20");
model._set("A4", "-10");
model._set("A5", "5");
model._set("A6", "5");
model._set("A7", "5");
model._set("A8", "-1");
model._set("A9", "2");
model._set("B1", "=MIRR(A2:A7, A8, A9)");
model.evaluate();
assert_eq!(
model.get_cell_value_by_ref("Sheet1!B1"),
Ok(CellValue::Number(-1.0))
);
assert_eq!(model._get_text("B1"), "-100%");
}
#[test]
fn fn_ispmt() {
let mut model = new_empty_model();
model._set("A1", "1"); // rate
model._set("A2", "2"); // per
model._set("A3", "5"); // nper
model._set("A4", "4"); // pv
model._set("B1", "=ISPMT(A1, A2, A3, A4)");
model._set("B2", "=ISPMT(A1, A2, A3, A4, 1)");
model._set("B3", "=ISPMT(A1, A2, A3)");
model.evaluate();
assert_eq!(model._get_text("B1"), "-2.4");
assert_eq!(model._get_text("B2"), *"#ERROR!");
assert_eq!(model._get_text("B3"), *"#ERROR!");
}
#[test]
fn fn_rri() {
let mut model = new_empty_model();
model._set("A1", "1"); // nper
model._set("A2", "2"); // pv
model._set("A3", "3"); // fv
model._set("B1", "=RRI(A1, A2, A3)");
model._set("B2", "=RRI(A1, A2)");
model._set("B3", "=RRI(A1, A2, A3, 1)");
model.evaluate();
assert_eq!(model._get_text("B1"), "0.5");
assert_eq!(model._get_text("B2"), *"#ERROR!");
assert_eq!(model._get_text("B3"), *"#ERROR!");
}
#[test]
fn fn_sln() {
let mut model = new_empty_model();
model._set("A1", "1"); // cost
model._set("A2", "2"); // salvage
model._set("A3", "3"); // life
model._set("B1", "=SLN(A1, A2, A3)");
model._set("B2", "=SLN(A1, A2)");
model._set("B3", "=SLN(A1, A2, A3, 1)");
model.evaluate();
assert_eq!(
model.get_cell_value_by_ref("Sheet1!B1"),
Ok(CellValue::Number(-1.0 / 3.0))
);
assert_eq!(model._get_text("B1"), "-$0.33");
assert_eq!(model._get_text("B2"), *"#ERROR!");
assert_eq!(model._get_text("B3"), *"#ERROR!");
}
#[test]
fn fn_syd() {
let mut model = new_empty_model();
model._set("A1", "100"); // cost
model._set("A2", "5"); // salvage
model._set("A3", "20"); // life
model._set("A4", "10"); // periods
model._set("B1", "=SYD(A1, A2, A3, A4)");
model._set("B2", "=SYD(A1, A2, A3)");
model._set("B3", "=SYD(A1, A2, A3, A4, 1)");
model.evaluate();
assert_eq!(
model.get_cell_value_by_ref("Sheet1!B1"),
Ok(CellValue::Number(4.976190476190476))
);
assert_eq!(model._get_text("B1"), "$4.98");
assert_eq!(model._get_text("B2"), *"#ERROR!");
assert_eq!(model._get_text("B3"), *"#ERROR!");
}
#[test]
fn fn_effect() {
let mut model = new_empty_model();
model._set("A1", "2"); // rate
model._set("A2", "1"); // periods
model._set("B1", "=EFFECT(A1, A2)");
model._set("B2", "=EFFECT(A1)");
model._set("B3", "=EFFECT(A1, A2, A3)");
model.evaluate();
assert_eq!(model._get_text("B1"), "2");
assert_eq!(model._get_text("B2"), *"#ERROR!");
assert_eq!(model._get_text("B3"), *"#ERROR!");
}
#[test]
fn fn_nominal() {
let mut model = new_empty_model();
model._set("A1", "2"); // rate
model._set("A2", "1"); // periods
model._set("B1", "=NOMINAL(A1, A2)");
model._set("B2", "=NOMINAL(A1)");
model._set("B3", "=NOMINAL(A1, A2, A3)");
model.evaluate();
assert_eq!(model._get_text("B1"), "2");
assert_eq!(model._get_text("B2"), *"#ERROR!");
assert_eq!(model._get_text("B3"), *"#ERROR!");
}
#[test]
fn fn_db() {
let mut model = new_empty_model();
model._set("A2", "$1,000,000"); // cost
model._set("A3", "$100,000"); // salvage
model._set("A4", "6"); // life
model._set("B1", "=DB(A2,A3,A4,1,7)");
model._set("B2", "=DB(A2,A3,A4,2,7)");
model._set("B3", "=DB(A2,A3,A4,3,7)");
model._set("B4", "=DB(A2,A3,A4,4,7)");
model._set("B5", "=DB(A2,A3,A4,5,7)");
model._set("B6", "=DB(A2,A3,A4,6,7)");
model._set("B7", "=DB(A2,A3,A4,7,7)");
model._set("C1", "=DB(A2,A3,A4,7,7,1)");
model._set("C2", "=DB(A2,A3,A4)");
model.evaluate();
assert_eq!(model._get_text("B1"), "$186,083.33");
assert_eq!(model._get_text("B2"), "$259,639.42");
assert_eq!(model._get_text("B3"), "$176,814.44");
assert_eq!(model._get_text("B4"), "$120,410.64");
assert_eq!(model._get_text("B5"), "$81,999.64");
assert_eq!(model._get_text("B6"), "$55,841.76");
assert_eq!(model._get_text("B7"), "$15,845.10");
assert_eq!(model._get_text("C1"), *"#ERROR!");
assert_eq!(model._get_text("C2"), *"#ERROR!");
}
#[test]
fn fn_ddb() {
let mut model = new_empty_model();
model._set("A2", "$2,400"); // cost
model._set("A3", "$300"); // salvage
model._set("A4", "10"); // life
model._set("B1", "=DDB(A2,A3,A4*365,1)");
model._set("B2", "=DDB(A2,A3,A4*12,1,2)");
model._set("B3", "=DDB(A2,A3,A4,1,2)");
model._set("B4", "=DDB(A2,A3,A4,2,1.5)");
model._set("B5", "=DDB(A2,A3,A4,10)");
model._set("C1", "=DB(A2,A3,A4,7,7,1)");
model._set("C2", "=DB(A2,A3,A4)");
model.evaluate();
assert_eq!(model._get_text("B1"), "$1.32");
assert_eq!(model._get_text("B2"), "$40.00");
assert_eq!(model._get_text("B3"), "$480.00");
assert_eq!(model._get_text("B4"), "$306.00");
assert_eq!(model._get_text("B5"), "$22.12");
assert_eq!(model._get_text("C1"), *"#ERROR!");
assert_eq!(model._get_text("C2"), *"#ERROR!");
}
#[test]
fn fn_tbilleq() {
let mut model = new_empty_model();
model._set("A2", "=DATE(2008, 3, 31)"); // settlement date
model._set("A3", "=DATE(2008, 6, 1)"); // maturity date
model._set("A4", "9.14%");
model._set("B1", "=TBILLEQ(A2,A3,A4)");
model._set("C1", "=TBILLEQ(A2,A3)");
model._set("C2", "=TBILLEQ(A2,A3,A4,1)");
model.evaluate();
assert_eq!(model._get_text("B1"), "9.42%");
assert_eq!(model._get_text("C1"), *"#ERROR!");
assert_eq!(model._get_text("C2"), *"#ERROR!");
}
#[test]
fn fn_tbillprice() {
let mut model = new_empty_model();
model._set("A2", "=DATE(2008, 3, 31)"); // settlement date
model._set("A3", "=DATE(2008, 6, 1)"); // maturity date
model._set("A4", "9.0%");
model._set("B1", "=TBILLPRICE(A2,A3,A4)");
model._set("C1", "=TBILLPRICE(A2,A3)");
model._set("C2", "=TBILLPRICE(A2,A3,A4,1)");
model.evaluate();
assert_eq!(model._get_text("B1"), "$98.45");
assert_eq!(model._get_text("C1"), *"#ERROR!");
assert_eq!(model._get_text("C2"), *"#ERROR!");
}
#[test]
fn fn_tbillyield() {
let mut model = new_empty_model();
model._set("A2", "=DATE(2008, 3, 31)"); // settlement date
model._set("A3", "=DATE(2008, 6, 1)"); // maturity date
model._set("A4", "$98.45");
model._set("B1", "=TBILLYIELD(A2,A3,A4)");
model._set("C1", "=TBILLYIELD(A2,A3)");
model._set("C2", "=TBILLYIELD(A2,A3,A4,1)");
model.evaluate();
assert_eq!(model._get_text("B1"), "9.14%");
assert_eq!(model._get_text("C1"), *"#ERROR!");
assert_eq!(model._get_text("C2"), *"#ERROR!");
}
#[test]
fn fn_dollarde() {
let mut model = new_empty_model();
model._set("A1", "=DOLLARDE(1.02, 16)");
model._set("A2", "=DOLLARDE(1.1, 32)");
model._set("C1", "=DOLLARDE(1.1)");
model._set("C2", "=DOLLARDE(1.1, 32, 1)");
model.evaluate();
assert_eq!(model._get_text("A1"), "1.125");
assert_eq!(model._get_text("A2"), "1.3125");
assert_eq!(model._get_text("C1"), *"#ERROR!");
assert_eq!(model._get_text("C2"), *"#ERROR!");
}
#[test]
fn fn_dollarfr() {
let mut model = new_empty_model();
model._set("A1", "=DOLLARFR(1.125,16)");
model._set("A2", "=DOLLARFR(1.125,32)");
model._set("C1", "=DOLLARFR(1.1)");
model._set("C2", "=DOLLARFR(1.1, 32, 1)");
model.evaluate();
assert_eq!(model._get_text("A1"), "1.02");
assert_eq!(model._get_text("A2"), "1.04");
assert_eq!(model._get_text("C1"), *"#ERROR!");
assert_eq!(model._get_text("C2"), *"#ERROR!");
}
#[test]
fn fn_cumipmt() {
let mut model = new_empty_model();
model._set("A2", "9%"); // annual interest rate
model._set("A3", "30"); // years of the load
model._set("A4", "$125,000"); // present value
model._set("B1", "=CUMIPMT(A2/12,A3*12,A4,13,24,0)");
model._set("B2", "=CUMIPMT(A2/12,A3*12,A4,1,1,0)");
model._set("C1", "=CUMIPMT(A2/12,A3*12,A4,1,1,0,1)");
model._set("C2", "=CUMIPMT(A2/12,A3*12,A4,1,1)");
model.evaluate();
assert_eq!(model._get_text("B1"), "-$11,135.23");
assert_eq!(model._get_text("B2"), "-$937.50");
assert_eq!(model._get_text("C1"), *"#ERROR!");
assert_eq!(model._get_text("C2"), *"#ERROR!");
}
#[test]
fn fn_cumprinc() {
let mut model = new_empty_model();
model._set("A2", "9%"); // annual interest rate
model._set("A3", "30"); // years of the load
model._set("A4", "$125,000"); // present value
model._set("B1", "=CUMPRINC(A2/12,A3*12,A4,13,24,0)");
model._set("B2", "=CUMPRINC(A2/12,A3*12,A4,1,1,0)");
model._set("C1", "=CUMPRINC(A2/12,A3*12,A4,1,1,0,1)");
model._set("C2", "=CUMPRINC(A2/12,A3*12,A4,1,1)");
model.evaluate();
assert_eq!(model._get_text("B1"), "-$934.11");
assert_eq!(model._get_text("B2"), "-$68.28");
assert_eq!(model._get_text("C1"), *"#ERROR!");
assert_eq!(model._get_text("C2"), *"#ERROR!");
}
#[test]
fn fn_db_misc() {
let mut model = new_empty_model();
model._set("B1", "=DB(0,10,1,2,2)");
model.evaluate();
assert_eq!(model._get_text("B1"), "$0.00");
}

View File

@@ -0,0 +1,36 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn fn_if_arguments() {
let mut model = new_empty_model();
model._set("A1", "=IF()");
model._set("A2", "=IF(1, 2, 3, 4)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"#ERROR!");
}
#[test]
fn fn_if_2_args() {
let mut model = new_empty_model();
model._set("A1", "=IF(2 > 3, TRUE)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"FALSE");
}
#[test]
fn fn_if_missing_args() {
let mut model = new_empty_model();
model._set("A1", "=IF(2 > 3, TRUE, )");
model._set("A2", "=IF(2 > 3, , 5)");
model._set("A3", "=IF(2 < 3, , 5)");
model.evaluate();
// assert_eq!(model._get_text("A1"), *"0");
assert_eq!(model._get_text("A2"), *"5");
assert_eq!(model._get_text("A3"), *"0");
}

View File

@@ -0,0 +1,40 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_fn_maxifs_arguments() {
let mut model = new_empty_model();
// Incorrect number of arguments
model._set("A1", "=MAXIFS()");
model._set("A2", "=MAXIFS(B2:B9)");
model._set("A3", "=MAXIFS(B2:B9,C2:C9)");
model._set("A4", "=MAXIFS(B2:B9,C2:C9,\"=A*\",D2:D9)");
// Correct (Sum everything in column 'B' if column 'C' starts with "A")
model._set("A5", "=MAXIFS(B2:B9,C2:C9,\"=A*\")");
// Data
model._set("B2", "5");
model._set("B3", "4");
model._set("B4", "15");
model._set("B5", "22");
model._set("B6", "=NA()");
model._set("C2", "Apples");
model._set("C3", "Bananas");
model._set("C4", "Almonds");
model._set("C5", "Yoni");
model._set("C6", "Mandarin");
model.evaluate();
// Error (Incorrect number of arguments)
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"#ERROR!");
assert_eq!(model._get_text("A3"), *"#ERROR!");
assert_eq!(model._get_text("A4"), *"#ERROR!");
// Correct
assert_eq!(model._get_text("A5"), *"15");
}

View File

@@ -0,0 +1,40 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_fn_minifs_arguments() {
let mut model = new_empty_model();
// Incorrect number of arguments
model._set("A1", "=MINIFS()");
model._set("A2", "=MINIFS(B2:B9)");
model._set("A3", "=MINIFS(B2:B9,C2:C9)");
model._set("A4", "=MINIFS(B2:B9,C2:C9,\"=A*\",D2:D9)");
// Correct (Sum everything in column 'B' if column 'C' starts with "A")
model._set("A5", "=MINIFS(B2:B9,C2:C9,\"=A*\")");
// Data
model._set("B2", "5");
model._set("B3", "4");
model._set("B4", "15");
model._set("B5", "22");
model._set("B6", "=NA()");
model._set("C2", "Apples");
model._set("C3", "Bananas");
model._set("C4", "Almonds");
model._set("C5", "Yoni");
model._set("C6", "Mandarin");
model.evaluate();
// Error (Incorrect number of arguments)
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"#ERROR!");
assert_eq!(model._get_text("A3"), *"#ERROR!");
assert_eq!(model._get_text("A4"), *"#ERROR!");
// Correct
assert_eq!(model._get_text("A5"), *"5");
}

View File

@@ -0,0 +1,19 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn fn_offset_reference() {
let mut model = new_empty_model();
model._set("B1", "12");
model._set("B2", "13");
model._set("B3", "15");
model._set("A1", "=SUM(B1:OFFSET($B$1,3,0))");
model._set("A2", "=SUM(OFFSET(A1, 1, 1):B3)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"40");
assert_eq!(model._get_text("A2"), *"28");
}

View File

@@ -0,0 +1,15 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_fn_product_arguments() {
let mut model = new_empty_model();
// Incorrect number of arguments
model._set("A1", "=PRODUCT()");
model.evaluate();
// Error (Incorrect number of arguments)
assert_eq!(model._get_text("A1"), *"#ERROR!");
}

View File

@@ -0,0 +1,29 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn simple_cases() {
let mut model = new_empty_model();
model._set("A1", "Well");
model._set("B1", "=REPT(A1, 3)");
model.evaluate();
assert_eq!(model._get_text("B1"), *"WellWellWell");
}
#[test]
fn wrong_number_of_arguments() {
let mut model = new_empty_model();
model._set("A1", "Well");
model._set("B1", "=REPT(A1)");
model._set("B2", "=REPT(A1,3,1)");
model.evaluate();
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}

View File

@@ -0,0 +1,19 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_fn_sum_arguments() {
let mut model = new_empty_model();
model._set("A1", "=SUM()");
model._set("A2", "=SUM(1, 2, 3)");
model._set("A3", "=SUM(1, )");
model._set("A4", "=SUM(1, , 3)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"6");
assert_eq!(model._get_text("A3"), *"1");
assert_eq!(model._get_text("A4"), *"4");
}

View File

@@ -0,0 +1,40 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_fn_sumifs_arguments() {
let mut model = new_empty_model();
// Incorrect number of arguments
model._set("A1", "=SUMIFS()");
model._set("A2", "=SUMIFS(B2:B9)");
model._set("A3", "=SUMIFS(B2:B9,C2:C9)");
model._set("A4", "=SUMIFS(B2:B9,C2:C9,\"=A*\",D2:D9)");
// Correct (Sum everything in column 'B' if column 'C' starts with "A")
model._set("A5", "=SUMIFS(B2:B9,C2:C9,\"=A*\")");
// Data
model._set("B2", "5");
model._set("B3", "4");
model._set("B4", "15");
model._set("B5", "22");
model._set("B6", "=NA()");
model._set("C2", "Apples");
model._set("C3", "Bananas");
model._set("C4", "Almonds");
model._set("C5", "Yoni");
model._set("C6", "Mandarin");
model.evaluate();
// Error (Incorrect number of arguments)
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"#ERROR!");
assert_eq!(model._get_text("A3"), *"#ERROR!");
assert_eq!(model._get_text("A4"), *"#ERROR!");
// Correct
assert_eq!(model._get_text("A5"), *"20");
}

View File

@@ -0,0 +1,29 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn simple_cases() {
let mut model = new_empty_model();
model._set("A1", "Brian Wiles");
model._set("A2", "Jeden,dwa,trzy,cztery");
model._set("B1", "=TEXTAFTER(A1, \" \")");
model._set("B2", "=TEXTAFTER(A2, \",\")");
model._set("C2", "=TEXTAFTER(A2, \",\", 2)");
model._set("H1", "=TEXTBEFORE(A1, \" \")");
model._set("H2", "=TEXTBEFORE(A2, \",\")");
model._set("I2", "=_xlfn.TEXTBEFORE(A2, \",\", 2)");
model.evaluate();
assert_eq!(model._get_text("B1"), *"Wiles");
assert_eq!(model._get_text("B2"), *"dwa,trzy,cztery");
assert_eq!(model._get_text("C2"), *"trzy,cztery");
assert_eq!(model._get_text("H1"), *"Brian");
assert_eq!(model._get_text("H2"), *"Jeden");
assert_eq!(model._get_text("I2"), *"Jeden,dwa");
assert_eq!(model._get_formula("I2"), *"=TEXTBEFORE(A2,\",\",2)");
}

View File

@@ -0,0 +1,40 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn simple_cases() {
let mut model = new_empty_model();
model._set("A1", "Monday");
model._set("A2", "Tuesday");
model._set("A3", "Wednesday");
model._set("B1", "=TEXTJOIN(\", \", TRUE, A1:A3)");
model._set("B2", "=TEXTJOIN(\" and \", TRUE, A1:A3)");
// This formula might have the _xlfn. prefix
model._set("B3", "=_xlfn.TEXTJOIN(\" or \", , A1:A3)");
model.evaluate();
assert_eq!(model._get_text("B1"), *"Monday, Tuesday, Wednesday");
assert_eq!(model._get_text("B2"), *"Monday and Tuesday and Wednesday");
assert_eq!(model._get_text("B3"), *"Monday or Tuesday or Wednesday");
// Our text version removes the prefix, of course (and some white spaces)
assert_eq!(model._get_formula("B3"), *"=TEXTJOIN(\" or \",,A1:A3)");
}
#[test]
fn wrong_number_of_arguments() {
let mut model = new_empty_model();
model._set("A1", "Monday");
model._set("A2", "Tuesday");
model._set("A3", "Wednesday");
model._set("B1", "=TEXTJOIN(\", \", TRUE)");
model._set("B2", "=TEXTJOIN(\" and \", A1:A3)");
model.evaluate();
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}

View File

@@ -0,0 +1,15 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn fn_type_array() {
let mut model = new_empty_model();
model._set("A1", "=TYPE()");
model._set("A2", "=TYPE(A1:C30)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"64");
}

View File

@@ -0,0 +1,121 @@
#![allow(clippy::unwrap_used)]
use crate::expressions::types::{Area, CellReferenceIndex};
use crate::test::util::new_empty_model;
#[test]
fn test_forward_references() {
let mut model = new_empty_model();
// test single ref changed nd not changed
model._set("H8", "=F6*G9");
// tests areas
model._set("H9", "=SUM(D4:F6)");
// absolute coordinates
model._set("H10", "=$F$6");
// area larger than the source area
model._set("H11", "=SUM(D3:F6)");
// Test arguments and concat
model._set("H12", "=SUM(F6, D4:F6) & D4");
// Test range operator. This is syntax error for now.
// model._set("H13", "=SUM(D4:INDEX(D4:F5,4,2))");
// Test operations
model._set("H14", "=-D4+D5*F6/F5");
model.evaluate();
// Source Area is D4:F6
let source_area = &Area {
sheet: 0,
row: 4,
column: 4,
width: 3,
height: 3,
};
// We paste in B10
let target_row = 10;
let target_column = 2;
let result = model.forward_references(
source_area,
&CellReferenceIndex {
sheet: 0,
row: target_row,
column: target_column,
},
);
assert!(result.is_ok());
model.evaluate();
assert_eq!(model._get_formula("H8"), "=D12*G9");
assert_eq!(model._get_formula("H9"), "=SUM(B10:D12)");
assert_eq!(model._get_formula("H10"), "=$D$12");
assert_eq!(model._get_formula("H11"), "=SUM(D3:F6)");
assert_eq!(model._get_formula("H12"), "=SUM(D12,B10:D12)&B10");
// assert_eq!(model._get_formula("H13"), "=SUM(B10:INDEX(B10:D11,4,2))");
assert_eq!(model._get_formula("H14"), "=-B10+B11*D12/D11");
}
#[test]
fn test_different_sheet() {
let mut model = new_empty_model();
// test single ref changed not changed
model._set("H8", "=F6*G9");
// tests areas
model._set("H9", "=SUM(D4:F6)");
// absolute coordinates
model._set("H10", "=$F$6");
// area larger than the source area
model._set("H11", "=SUM(D3:F6)");
// Test arguments and concat
model._set("H12", "=SUM(F6, D4:F6) & D4");
// Test range operator. This is syntax error for now.
// model._set("H13", "=SUM(D4:INDEX(D4:F5,4,2))");
// Test operations
model._set("H14", "=-D4+D5*F6/F5");
// Adds a new sheet
assert!(model.add_sheet("Sheet2").is_ok());
model.evaluate();
// Source Area is D4:F6
let source_area = &Area {
sheet: 0,
row: 4,
column: 4,
width: 3,
height: 3,
};
// We paste in Sheet2!B10
let target_row = 10;
let target_column = 2;
let result = model.forward_references(
source_area,
&CellReferenceIndex {
sheet: 1,
row: target_row,
column: target_column,
},
);
assert!(result.is_ok());
model.evaluate();
assert_eq!(model._get_formula("H8"), "=Sheet2!D12*G9");
assert_eq!(model._get_formula("H9"), "=SUM(Sheet2!B10:D12)");
assert_eq!(model._get_formula("H10"), "=Sheet2!$D$12");
assert_eq!(model._get_formula("H11"), "=SUM(D3:F6)");
assert_eq!(
model._get_formula("H12"),
"=SUM(Sheet2!D12,Sheet2!B10:D12)&Sheet2!B10"
);
// assert_eq!(model._get_formula("H13"), "=SUM(B10:INDEX(B10:D11,4,2))");
assert_eq!(
model._get_formula("H14"),
"=-Sheet2!B10+Sheet2!B11*Sheet2!D12/Sheet2!D11"
);
}

View File

@@ -0,0 +1,54 @@
#![allow(clippy::unwrap_used)]
use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::test::util::new_empty_model;
#[test]
fn test_empty_model() {
let mut model = new_empty_model();
let worksheet = model.workbook.worksheet_mut(0).unwrap();
assert_eq!(worksheet.frozen_rows, 0);
assert_eq!(worksheet.frozen_columns, 0);
let e = worksheet.set_frozen_rows(3);
assert!(e.is_ok());
assert_eq!(worksheet.frozen_rows, 3);
assert_eq!(worksheet.frozen_columns, 0);
let e = worksheet.set_frozen_columns(53);
assert!(e.is_ok());
assert_eq!(worksheet.frozen_rows, 3);
assert_eq!(worksheet.frozen_columns, 53);
// Set them back to zero
let e = worksheet.set_frozen_rows(0);
assert!(e.is_ok());
let e = worksheet.set_frozen_columns(0);
assert!(e.is_ok());
assert_eq!(worksheet.frozen_rows, 0);
assert_eq!(worksheet.frozen_columns, 0);
}
#[test]
fn test_invalid_rows_columns() {
let mut model = new_empty_model();
let worksheet = model.workbook.worksheet_mut(0).unwrap();
assert_eq!(
worksheet.set_frozen_rows(-3),
Err("Frozen rows cannot be negative".to_string())
);
assert_eq!(
worksheet.set_frozen_columns(-5),
Err("Frozen columns cannot be negative".to_string())
);
assert_eq!(
worksheet.set_frozen_rows(LAST_ROW),
Err("Too many rows".to_string())
);
assert_eq!(
worksheet.set_frozen_columns(LAST_COLUMN),
Err("Too many columns".to_string())
);
}

View File

@@ -0,0 +1,484 @@
#![allow(clippy::unwrap_used)]
use crate::cell::CellValue;
use crate::number_format::to_excel_precision_str;
use crate::test::util::new_empty_model;
#[test]
fn test_empty_model() {
let model = new_empty_model();
let names = model.workbook.get_worksheet_names();
assert_eq!(names.len(), 1);
assert_eq!(names[0], "Sheet1");
}
#[test]
fn test_model_simple_evaluation() {
let mut model = new_empty_model();
model.set_user_input(0, 1, 1, "= 1 + 3".to_string());
model.evaluate();
let result = model._get_text_at(0, 1, 1);
assert_eq!(result, *"4");
let result = model._get_formula("A1");
assert_eq!(result, *"=1+3");
}
#[test]
fn test_model_simple_evaluation_order() {
let mut model = new_empty_model();
model._set("A1", "=1/2/3");
model._set("A2", "=(1/2)/3");
model._set("A3", "=1/(2/3)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"0.166666667");
assert_eq!(model._get_text("A2"), *"0.166666667");
assert_eq!(model._get_text("A3"), *"1.5");
// Unnecessary parenthesis are lost
assert_eq!(model._get_formula("A2"), *"=1/2/3");
assert_eq!(model._get_formula("A3"), *"=1/(2/3)");
}
#[test]
fn test_model_invalid_formula() {
let mut model = new_empty_model();
model.set_user_input(0, 1, 1, "= 1 +".to_string());
model.evaluate();
let result = model._get_text_at(0, 1, 1);
assert_eq!(result, *"#ERROR!");
let result = model._get_formula("A1");
assert_eq!(result, *"= 1 +");
}
#[test]
fn test_model_dependencies() {
let mut model = new_empty_model();
model.set_user_input(0, 1, 1, "23".to_string()); // A1
model.set_user_input(0, 1, 2, "= A1* 2-4".to_string()); // B1
model.evaluate();
let result = model._get_text_at(0, 1, 1);
assert_eq!(result, *"23");
assert!(!model._has_formula("A1"));
let result = model._get_text_at(0, 1, 2);
assert_eq!(result, *"42");
let result = model._get_formula("B1");
assert_eq!(result, *"=A1*2-4");
model.set_user_input(0, 2, 1, "=SUM(A1, B1)".to_string()); // A2
model.evaluate();
let result = model._get_text_at(0, 2, 1);
assert_eq!(result, *"65");
}
#[test]
fn test_model_strings() {
let mut model = new_empty_model();
model.set_user_input(0, 1, 1, "Hello World".to_string());
model.set_user_input(0, 1, 2, "=A1".to_string());
model.evaluate();
let result = model._get_text_at(0, 1, 1);
assert_eq!(result, *"Hello World");
let result = model._get_text_at(0, 1, 2);
assert_eq!(result, *"Hello World");
}
#[test]
fn test_get_sheet_index_by_sheet_id() {
let mut model = new_empty_model();
model.new_sheet();
assert_eq!(model.get_sheet_index_by_sheet_id(1), Some(0));
assert_eq!(model.get_sheet_index_by_sheet_id(2), Some(1));
assert_eq!(model.get_sheet_index_by_sheet_id(1337), None);
}
#[test]
fn test_set_row_height() {
let mut model = new_empty_model();
let worksheet = model.workbook.worksheet_mut(0).unwrap();
worksheet.set_row_height(5, 25.0).unwrap();
let worksheet = model.workbook.worksheet(0).unwrap();
assert!((25.0 - worksheet.row_height(5).unwrap()).abs() < f64::EPSILON);
let worksheet = model.workbook.worksheet_mut(0).unwrap();
worksheet.set_row_height(5, 5.0).unwrap();
let worksheet = model.workbook.worksheet(0).unwrap();
assert!((5.0 - worksheet.row_height(5).unwrap()).abs() < f64::EPSILON);
}
#[test]
fn test_to_excel_precision_str() {
struct TestCase<'a> {
value: f64,
str: &'a str,
}
let test_cases = vec![
TestCase {
value: 2e-23,
str: "2e-23",
},
TestCase {
value: 42.0,
str: "42",
},
TestCase {
value: 200.0e-23,
str: "2e-21",
},
TestCase {
value: -200e-23,
str: "-2e-21",
},
TestCase {
value: 10.002,
str: "10.002",
},
TestCase {
value: f64::INFINITY,
str: "inf",
},
TestCase {
value: f64::NAN,
str: "NaN",
},
];
for test_case in test_cases {
let str = to_excel_precision_str(test_case.value);
assert_eq!(str, test_case.str);
}
}
#[test]
fn test_booleans() {
let mut model = new_empty_model();
model.set_user_input(0, 1, 1, "true".to_string());
model.set_user_input(0, 2, 1, "TRUE".to_string());
model.set_user_input(0, 3, 1, "True".to_string());
model.set_user_input(0, 4, 1, "false".to_string());
model.set_user_input(0, 5, 1, "FALSE".to_string());
model.set_user_input(0, 6, 1, "False".to_string());
model.set_user_input(0, 1, 2, "=ISLOGICAL(A1)".to_string());
model.set_user_input(0, 2, 2, "=ISLOGICAL(A2)".to_string());
model.set_user_input(0, 3, 2, "=ISLOGICAL(A3)".to_string());
model.set_user_input(0, 4, 2, "=ISLOGICAL(A4)".to_string());
model.set_user_input(0, 5, 2, "=ISLOGICAL(A5)".to_string());
model.set_user_input(0, 6, 2, "=ISLOGICAL(A6)".to_string());
model.set_user_input(0, 1, 5, "=IF(false, True, FALSe)".to_string());
model.evaluate();
assert_eq!(model._get_text_at(0, 1, 1), *"TRUE");
assert_eq!(model._get_text_at(0, 2, 1), *"TRUE");
assert_eq!(model._get_text_at(0, 3, 1), *"TRUE");
assert_eq!(model._get_text_at(0, 4, 1), *"FALSE");
assert_eq!(model._get_text_at(0, 5, 1), *"FALSE");
assert_eq!(model._get_text_at(0, 6, 1), *"FALSE");
assert_eq!(model._get_text_at(0, 1, 2), *"TRUE");
assert_eq!(model._get_text_at(0, 2, 2), *"TRUE");
assert_eq!(model._get_text_at(0, 3, 2), *"TRUE");
assert_eq!(model._get_text_at(0, 4, 2), *"TRUE");
assert_eq!(model._get_text_at(0, 5, 2), *"TRUE");
assert_eq!(model._get_text_at(0, 6, 2), *"TRUE");
assert_eq!(model._get_formula("E1"), *"=IF(FALSE,TRUE,FALSE)");
}
#[test]
fn test_set_cell_style() {
let mut model = new_empty_model();
let mut style = model.get_style_for_cell(0, 1, 1);
assert!(!style.font.b);
style.font.b = true;
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
let mut style = model.get_style_for_cell(0, 1, 1);
assert!(style.font.b);
style.font.b = false;
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
let style = model.get_style_for_cell(0, 1, 1);
assert!(!style.font.b);
}
#[test]
fn test_copy_cell_style() {
let mut model = new_empty_model();
let mut style = model.get_style_for_cell(0, 1, 1);
style.font.b = true;
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
let mut style = model.get_style_for_cell(0, 1, 2);
style.font.i = true;
assert!(model.set_cell_style(0, 1, 2, &style).is_ok());
assert!(model.copy_cell_style((0, 1, 1), (0, 1, 2)).is_ok());
let style = model.get_style_for_cell(0, 1, 1);
assert!(style.font.b);
assert!(!style.font.i);
let style = model.get_style_for_cell(0, 1, 2);
assert!(style.font.b);
assert!(!style.font.i);
}
#[test]
fn test_get_cell_style_index() {
let mut model = new_empty_model();
let mut style = model.get_style_for_cell(0, 1, 1);
let style_index = model.get_cell_style_index(0, 1, 1);
assert_eq!(style_index, 0);
assert!(!style.font.b);
style.font.b = true;
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
let style_index = model.get_cell_style_index(0, 1, 1);
assert_eq!(style_index, 1);
}
#[test]
fn test_model_set_cells_with_values_styles() {
let mut model = new_empty_model();
// Inputs
model.set_user_input(0, 1, 1, "21".to_string()); // A1
model.set_user_input(0, 2, 1, "2".to_string()); // A2
let style_index = model.get_cell_style_index(0, 1, 1);
assert_eq!(style_index, 0);
let mut style = model.get_style_for_cell(0, 1, 1);
style.font.b = true;
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
assert!(model.set_cell_style(0, 2, 1, &style).is_ok());
let style_index = model.get_cell_style_index(0, 1, 1);
assert_eq!(style_index, 1);
let style_index = model.get_cell_style_index(0, 2, 1);
assert_eq!(style_index, 1);
model.update_cell_with_number(0, 1, 2, 1.0);
model.update_cell_with_number(0, 2, 1, 2.0);
model.evaluate();
// Styles are not modified
let style_index = model.get_cell_style_index(0, 1, 1);
assert_eq!(style_index, 1);
let style_index = model.get_cell_style_index(0, 2, 1);
assert_eq!(style_index, 1);
}
#[test]
fn test_style_fmt_id() {
let mut model = new_empty_model();
let mut style = model.get_style_for_cell(0, 1, 1);
style.num_fmt = "#.##".to_string();
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
let style = model.get_style_for_cell(0, 1, 1);
assert_eq!(style.num_fmt, "#.##");
let mut style = model.get_style_for_cell(0, 10, 1);
style.num_fmt = "$$#,##0.0000".to_string();
assert!(model.set_cell_style(0, 10, 1, &style).is_ok());
let style = model.get_style_for_cell(0, 10, 1);
assert_eq!(style.num_fmt, "$$#,##0.0000");
// Make sure old style is not touched
let style = model.get_style_for_cell(0, 1, 1);
assert_eq!(style.num_fmt, "#.##");
}
#[test]
fn test_set_sheet_color() {
let mut model = new_empty_model();
assert_eq!(model.workbook.worksheet(0).unwrap().color, None);
assert!(model.set_sheet_color(0, "#FFFAAA").is_ok());
// Test new tab color is properly set
assert_eq!(
model.workbook.worksheet(0).unwrap().color,
Some("#FFFAAA".to_string())
);
// Test we can remove it
assert!(model.set_sheet_color(0, "").is_ok());
assert_eq!(model.workbook.worksheet(0).unwrap().color, None);
}
#[test]
fn test_set_sheet_color_invalid_sheet() {
let mut model = new_empty_model();
assert_eq!(
model.set_sheet_color(10, "#FFFAAA"),
Err("Invalid sheet index".to_string())
);
}
#[test]
fn test_set_sheet_color_invalid() {
let mut model = new_empty_model();
// Boundaries
assert!(model.set_sheet_color(0, "#FFFFFF").is_ok());
assert!(model.set_sheet_color(0, "#000000").is_ok());
assert_eq!(
model.set_sheet_color(0, "#FFF"),
Err("Invalid color: #FFF".to_string())
);
assert_eq!(
model.set_sheet_color(0, "-#FFF"),
Err("Invalid color: -#FFF".to_string())
);
assert_eq!(
model.set_sheet_color(0, "#-FFF"),
Err("Invalid color: #-FFF".to_string())
);
assert_eq!(
model.set_sheet_color(0, "2FFFFFF"),
Err("Invalid color: 2FFFFFF".to_string())
);
assert_eq!(
model.set_sheet_color(0, "#FFFFFF1"),
Err("Invalid color: #FFFFFF1".to_string())
);
}
#[test]
fn set_input_autocomplete() {
let mut model = new_empty_model();
model._set("A1", "1");
model._set("A2", "2");
model.set_user_input(0, 3, 1, "=SUM(A1:A2".to_string());
// This will fail anyway
model.set_user_input(0, 4, 1, "=SUM(A1*".to_string());
model.evaluate();
assert_eq!(model._get_formula("A3"), "=SUM(A1:A2)");
assert_eq!(model._get_text("A3"), "3");
assert_eq!(model._get_formula("A4"), "=SUM(A1*");
assert_eq!(model._get_text("A4"), "#ERROR!");
}
#[test]
fn test_get_cell_value_by_ref() {
let mut model = new_empty_model();
model._set("A1", "1");
model._set("A2", "2");
model.evaluate();
// Correct
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A1"),
Ok(CellValue::Number(1.0))
);
// You need to specify full reference
assert_eq!(
model.get_cell_value_by_ref("A1"),
Err("Error parsing reference: 'A1'".to_string())
);
// Error, it has a trailing space
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A1 "),
Err("Error parsing reference: 'Sheet1!A1 '".to_string())
);
}
#[test]
fn test_get_formatted_cell_value() {
let mut model = new_empty_model();
model._set("A1", "foobar");
model._set("A2", "true");
model._set("A3", "");
model._set("A4", "123.456");
model._set("A5", "123.456");
// change A5 format
let mut style = model.get_style_for_cell(0, 5, 1);
style.num_fmt = "$#,##0.00".to_string();
model.set_cell_style(0, 5, 1, &style).unwrap();
model.evaluate();
assert_eq!(model.formatted_cell_value(0, 1, 1).unwrap(), "foobar");
assert_eq!(model.formatted_cell_value(0, 2, 1).unwrap(), "TRUE");
assert_eq!(model.formatted_cell_value(0, 3, 1).unwrap(), "");
assert_eq!(model.formatted_cell_value(0, 4, 1).unwrap(), "123.456");
assert_eq!(model.formatted_cell_value(0, 5, 1).unwrap(), "$123.46");
}
#[test]
fn test_cell_formula() {
let mut model = new_empty_model();
model._set("A1", "=1+2+3");
model._set("A2", "foobar");
model.evaluate();
assert_eq!(
model.cell_formula(0, 1, 1), // A1
Ok(Some("=1+2+3".to_string())),
);
assert_eq!(
model.cell_formula(0, 2, 1), // A2
Ok(None),
);
assert_eq!(
model.cell_formula(0, 3, 1), // A3 - empty cell
Ok(None),
);
assert_eq!(
model.cell_formula(42, 1, 1),
Err("Invalid sheet index".to_string()),
);
}
#[test]
fn test_xlfn() {
let mut model = new_empty_model();
model._set("A1", "=_xlfn.SIN(1)");
model._set("A2", "=_xlfn.SINY(1)");
model._set("A3", "=_xlfn.CONCAT(3, 4.0)");
model.evaluate();
// Only modern formulas strip the '_xlfn.'
assert_eq!(
model.cell_formula(0, 1, 1).unwrap(),
Some("=_xlfn.SIN(1)".to_string())
);
// unknown formulas keep the '_xlfn.' prefix
assert_eq!(
model.cell_formula(0, 2, 1).unwrap(),
Some("=_xlfn.SINY(1)".to_string())
);
assert_eq!(
model.cell_formula(0, 3, 1).unwrap(),
Some("=CONCAT(3,4)".to_string())
);
}
#[test]
fn test_letter_case() {
let mut model = new_empty_model();
model._set("A1", "=sin(1)");
model._set("A2", "=sIn(2)");
model.evaluate();
assert_eq!(
model.cell_formula(0, 1, 1).unwrap(),
Some("=SIN(1)".to_string())
);
assert_eq!(
model.cell_formula(0, 2, 1).unwrap(),
Some("=SIN(2)".to_string())
);
}

View File

@@ -0,0 +1,29 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_fn_sqrt_arguments() {
let mut model = new_empty_model();
model._set("A1", "=SQRT(4)");
model._set("A2", "=SQRT()");
model._set("A3", "=SQRT(4, 4)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"2");
assert_eq!(model._get_text("A2"), *"#ERROR!");
assert_eq!(model._get_text("A3"), *"#ERROR!");
}
#[test]
fn test_fn_sqrtpi_arguments() {
let mut model = new_empty_model();
model._set("A1", "=SQRTPI()");
model._set("A2", "=SQRTPI(4, 4)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"#ERROR!");
}

View File

@@ -0,0 +1,13 @@
use crate::test::util::new_empty_model;
#[test]
fn test_metadata_new_model() {
let mut model = new_empty_model();
model.set_user_input(0, 1, 1, "5.5".to_string());
model.evaluate();
let metadata = &model.workbook.metadata;
assert_eq!(metadata.application, "IronCalc Sheets");
// FIXME: This will need to be updated once we fix versioning
assert_eq!(metadata.app_version, "10.0000");
assert_eq!(metadata.last_modified, "2022-11-08T11:13:28Z");
}

View File

@@ -0,0 +1,54 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_delete_cell_non_existing_sheet() {
let mut model = new_empty_model();
assert_eq!(
model.delete_cell(13, 1, 1),
Err("Invalid sheet index".to_string())
);
}
#[test]
fn test_delete_cell_unset_cell() {
let mut model = new_empty_model();
assert!(model.delete_cell(0, 1, 1).is_ok());
}
#[test]
fn test_delete_cell_with_value() {
let mut model = new_empty_model();
model._set("A1", "hello");
model.evaluate();
assert_eq!(model._get_text_at(0, 1, 1), "hello");
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
model.delete_cell(0, 1, 1).unwrap();
model.evaluate();
assert_eq!(model._get_text_at(0, 1, 1), "");
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(true));
}
#[test]
fn test_delete_cell_referenced_elsewhere() {
let mut model = new_empty_model();
model._set("A1", "35");
model._set("A2", "=2*A1");
model.evaluate();
assert_eq!(model._get_text_at(0, 1, 1), "35");
assert_eq!(model._get_text_at(0, 2, 1), "70");
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
assert_eq!(model.is_empty_cell(0, 2, 1), Ok(false));
model.delete_cell(0, 1, 1).unwrap();
model.evaluate();
assert_eq!(model._get_text_at(0, 1, 1), "");
assert_eq!(model._get_text_at(0, 2, 1), "0");
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(true));
assert_eq!(model.is_empty_cell(0, 2, 1), Ok(false));
}

View File

@@ -0,0 +1,55 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_is_empty_cell_non_existing_sheet() {
let model = new_empty_model();
assert_eq!(
model.is_empty_cell(13, 1, 1),
Err("Invalid sheet index".to_string())
);
}
#[test]
fn test_is_empty_cell() {
let mut model = new_empty_model();
assert!(model.is_empty_cell(0, 3, 1).unwrap());
model.set_user_input(0, 3, 1, "Hello World".to_string());
assert!(!model.is_empty_cell(0, 3, 1).unwrap());
model.set_cell_empty(0, 3, 1).unwrap();
assert!(model.is_empty_cell(0, 3, 1).unwrap());
}
#[test]
fn test_is_empty_cell_unset_cell() {
let model = new_empty_model();
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(true));
}
#[test]
fn test_is_empty_cell_with_value() {
let mut model = new_empty_model();
model._set("A1", "hello");
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
}
#[test]
fn test_is_empty_cell_empty_string_not_empty() {
let mut model = new_empty_model();
model._set("A1", "");
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
}
#[test]
fn test_is_empty_cell_formula_that_evaluates_to_empty_string() {
let mut model = new_empty_model();
model._set("A1", "=A2");
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
}
#[test]
fn test_is_empty_cell_formula_that_evaluates_to_zero() {
let mut model = new_empty_model();
model._set("A1", "=2*A2");
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
}

View File

@@ -0,0 +1,57 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_set_cell_empty_non_existing_sheet() {
let mut model = new_empty_model();
assert_eq!(
model.set_cell_empty(13, 1, 1),
Err("Invalid sheet index".to_string())
);
}
#[test]
fn test_set_cell_empty_unset_cell() {
let mut model = new_empty_model();
model.set_cell_empty(0, 1, 1).unwrap();
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(true));
model.evaluate();
assert_eq!(model._get_text_at(0, 1, 1), "");
}
#[test]
fn test_set_cell_empty_with_value() {
let mut model = new_empty_model();
model._set("A1", "hello");
model.evaluate();
assert_eq!(model._get_text_at(0, 1, 1), "hello");
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
model.set_cell_empty(0, 1, 1).unwrap();
model.evaluate();
assert_eq!(model._get_text_at(0, 1, 1), "");
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(true));
}
#[test]
fn test_set_cell_empty_referenced_elsewhere() {
let mut model = new_empty_model();
model._set("A1", "35");
model._set("A2", "=2*A1");
model.evaluate();
assert_eq!(model._get_text_at(0, 1, 1), "35");
assert_eq!(model._get_text_at(0, 2, 1), "70");
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
assert_eq!(model.is_empty_cell(0, 2, 1), Ok(false));
model.set_cell_empty(0, 1, 1).unwrap();
model.evaluate();
assert_eq!(model._get_text_at(0, 1, 1), "");
assert_eq!(model._get_text_at(0, 2, 1), "0");
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(true));
assert_eq!(model.is_empty_cell(0, 2, 1), Ok(false));
}

View File

@@ -0,0 +1,230 @@
#![allow(clippy::unwrap_used)]
use crate::expressions::types::{Area, CellReferenceIndex};
use crate::test::util::new_empty_model;
#[test]
fn test_move_formula() {
let mut model = new_empty_model();
let source = &CellReferenceIndex {
sheet: 0,
column: 1,
row: 1,
};
let value = "=A2+3";
let target = &CellReferenceIndex {
sheet: 0,
column: 10,
row: 10,
};
// if we move just one point formula does ot change
let area = &Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
};
let t = model.move_cell_value_to_area(value, source, target, area);
assert!(t.is_ok());
assert_eq!(t.unwrap(), "=A2+3");
// if we move a 2x2 square formula does change
let area = &Area {
sheet: 0,
row: 1,
column: 1,
width: 2,
height: 2,
};
let t = model.move_cell_value_to_area(value, source, target, area);
assert!(t.is_ok());
assert_eq!(t.unwrap(), "=J11+3");
}
#[test]
fn test_move_formula_wrong_args() {
let mut model = new_empty_model();
let t = model.add_sheet("Sheet2");
assert!(t.is_ok());
let source = &CellReferenceIndex {
sheet: 0,
column: 5,
row: 5,
};
let value = "=A2+3";
let target = &CellReferenceIndex {
sheet: 0,
column: 10,
row: 10,
};
// different sheet
{
let area = &Area {
sheet: 1,
row: 5,
column: 5,
width: 1,
height: 1,
};
let t = model.move_cell_value_to_area(value, source, target, area);
assert_eq!(
t,
Err("Source and area are in different sheets".to_string())
);
}
// not in area
{
let area = &Area {
sheet: 0,
row: 6,
column: 4,
width: 5,
height: 5,
};
let t = model.move_cell_value_to_area(value, source, target, area);
assert_eq!(t, Err("Source is outside the area".to_string()));
}
{
let area = &Area {
sheet: 0,
row: 1,
column: 4,
width: 5,
height: 2,
};
let t = model.move_cell_value_to_area(value, source, target, area);
assert_eq!(t, Err("Source is outside the area".to_string()));
}
{
let area = &Area {
sheet: 0,
row: 1,
column: 6,
width: 20,
height: 5,
};
let t = model.move_cell_value_to_area(value, source, target, area);
assert_eq!(t, Err("Source is outside the area".to_string()));
}
// Invalid sheet indexes
assert_eq!(
model.move_cell_value_to_area(
value,
&CellReferenceIndex {
sheet: 0,
row: 1,
column: 4,
},
&CellReferenceIndex {
sheet: 16,
row: 1,
column: 1,
},
&Area {
sheet: 0,
row: 1,
column: 4,
width: 5,
height: 2,
}
),
Err("Could not find target worksheet: Invalid sheet index".to_string())
);
assert_eq!(
model.move_cell_value_to_area(
value,
&CellReferenceIndex {
sheet: 3,
column: 1,
row: 1,
},
target,
&Area {
sheet: 3,
row: 1,
column: 1,
width: 5,
height: 5,
},
),
Err("Could not find source worksheet: Invalid sheet index".to_string())
);
}
#[test]
fn test_move_formula_rectangle() {
let mut model = new_empty_model();
let value = "=B2+C2";
let target = &CellReferenceIndex {
sheet: 0,
column: 10,
row: 10,
};
// if we move just one point formula does not change
let area = &Area {
sheet: 0,
row: 1,
column: 1,
width: 2,
height: 20,
};
assert!(model
.move_cell_value_to_area(
value,
&CellReferenceIndex {
sheet: 0,
column: 3,
row: 1,
},
target,
area
)
.is_err());
assert!(model
.move_cell_value_to_area(
value,
&CellReferenceIndex {
sheet: 0,
column: 2,
row: 1,
},
target,
area
)
.is_ok());
assert!(model
.move_cell_value_to_area(
value,
&CellReferenceIndex {
sheet: 0,
column: 1,
row: 20,
},
target,
area
)
.is_ok());
assert!(model
.move_cell_value_to_area(
value,
&CellReferenceIndex {
sheet: 0,
column: 1,
row: 21,
},
target,
area
)
.is_err());
}

View File

@@ -0,0 +1,16 @@
#![allow(clippy::unwrap_used)]
use crate::number_format::format_number;
#[test]
fn test_simple_format() {
let formatted = format_number(2.3, "General", "en");
assert_eq!(formatted.text, "2.3".to_string());
}
#[test]
#[ignore = "not yet implemented"]
fn test_wrong_locale() {
let formatted = format_number(2.3, "General", "ens");
assert_eq!(formatted.text, "#ERROR!".to_string());
}

View File

@@ -0,0 +1,19 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn simple_example() {
let mut model = new_empty_model();
model._set("A1", "220");
model._set("B1", "=A1*10%");
model._set("C1", "=SIN(A1)%");
model.evaluate();
assert_eq!(model._get_formula("B1"), *"=A1*10%");
assert_eq!(model._get_text("B1"), *"22");
assert_eq!(model._get_formula("C1"), *"=SIN(A1)%");
assert_eq!(model._get_text("C1"), *"0.000883987");
}

View File

@@ -0,0 +1,146 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_quote_prefix_formula() {
let mut model = new_empty_model();
model._set("A1", "'= 1 + 3");
model.evaluate();
assert_eq!(model._get_text("A1"), *"= 1 + 3");
assert!(!model._has_formula("A1"));
}
#[test]
fn test_quote_prefix_number() {
let mut model = new_empty_model();
model._set("A1", "'13");
model._set("A2", "=ISNUMBER(A1)");
model._set("A3", "=A1+1");
model._set("A4", "=ISNUMBER(A3)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"13");
assert!(!model._has_formula("A1"));
assert_eq!(model._get_text("A2"), *"FALSE");
assert_eq!(model._get_text("A3"), *"14");
assert_eq!(model._get_text("A4"), *"TRUE");
}
#[test]
fn test_quote_prefix_error() {
let mut model = new_empty_model();
model._set("A1", "'#N/A");
model._set("A2", "=ISERROR(A1)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#N/A");
assert_eq!(model._get_text("A2"), *"FALSE");
}
#[test]
fn test_quote_prefix_boolean() {
let mut model = new_empty_model();
model._set("A1", "'FALSE");
model._set("A2", "=ISTEXT(A1)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"FALSE");
assert_eq!(model._get_text("A2"), *"TRUE");
}
#[test]
fn test_quote_prefix_enter() {
let mut model = new_empty_model();
model._set("A1", "'123");
model._set("A2", "=ISTEXT(A1)");
model.evaluate();
// We introduce a value with a "quote prefix" index
model.set_user_input(0, 1, 3, "'=A1".to_string());
model.evaluate();
assert_eq!(model._get_text("C1"), *"=A1");
// But if we enter with a quote_prefix but without the "'" it won't be quote_prefix
model.set_user_input(0, 1, 4, "=A1".to_string());
model.evaluate();
assert_eq!(model._get_text("D1"), *"123");
}
#[test]
fn test_quote_prefix_reenter() {
let mut model = new_empty_model();
model._set("A1", "'123");
model._set("A2", "=ISTEXT(A1)");
model.evaluate();
assert_eq!(model._get_text("A2"), *"TRUE");
// We introduce a value with a "quote prefix" index
model.set_user_input(0, 1, 1, "123".to_string());
model.evaluate();
assert_eq!(model._get_text("A2"), *"FALSE");
}
#[test]
fn test_update_cell_quote() {
let mut model = new_empty_model();
model.update_cell_with_text(0, 1, 1, "= 1 + 3");
model.evaluate();
assert_eq!(model._get_text("A1"), *"= 1 + 3");
assert!(!model._has_formula("A1"));
}
#[test]
fn test_update_quote_prefix_reenter() {
let mut model = new_empty_model();
model.update_cell_with_text(0, 1, 1, "123");
model._set("A2", "=ISTEXT(A1)");
model.evaluate();
assert_eq!(model._get_text("A2"), *"TRUE");
// We reenter as a number
model.update_cell_with_number(0, 1, 1, 123.0);
model.evaluate();
assert_eq!(model._get_text("A2"), *"FALSE");
}
#[test]
fn test_update_quote_prefix_reenter_bool() {
let mut model = new_empty_model();
model.update_cell_with_text(0, 1, 1, "TRUE");
model._set("A2", "=ISTEXT(A1)");
model.evaluate();
assert_eq!(model._get_text("A2"), *"TRUE");
// We enter a bool
model.update_cell_with_bool(0, 1, 1, true);
model.evaluate();
assert_eq!(model._get_text("A2"), *"FALSE");
}
#[test]
fn test_update_quote_prefix_reenter_text() {
let mut model = new_empty_model();
model.update_cell_with_text(0, 1, 1, "123");
model._set("A2", "=ISTEXT(A1)");
model.evaluate();
assert_eq!(model._get_text("A2"), *"TRUE");
assert!(model.get_style_for_cell(0, 1, 1).quote_prefix);
// We enter a string
model.update_cell_with_text(0, 1, 1, "Hello");
model.evaluate();
assert_eq!(model._get_text("A2"), *"TRUE");
assert!(!model.get_style_for_cell(0, 1, 1).quote_prefix);
}
#[test]
fn test_update_quote_prefix_reenter_text_2() {
let mut model = new_empty_model();
model.update_cell_with_text(0, 1, 1, "123");
model._set("A2", "=ISTEXT(A1)");
model.evaluate();
assert_eq!(model._get_text("A2"), *"TRUE");
assert!(model.get_style_for_cell(0, 1, 1).quote_prefix);
// We enter another number
model.update_cell_with_text(0, 1, 1, "42");
model.evaluate();
assert_eq!(model._get_text("A2"), *"TRUE");
assert!(model.get_style_for_cell(0, 1, 1).quote_prefix);
}

View File

@@ -0,0 +1,480 @@
#![allow(clippy::unwrap_used)]
use crate::{cell::CellValue, test::util::new_empty_model};
#[test]
fn test_currencies() {
let mut model = new_empty_model();
model.set_user_input(0, 1, 1, "$100.348".to_string());
model.set_user_input(0, 1, 2, "=ISNUMBER(A1)".to_string());
model.set_user_input(0, 2, 1, "$ 100.348".to_string());
model.set_user_input(0, 2, 2, "=ISNUMBER(A2)".to_string());
model.set_user_input(0, 3, 1, "100$".to_string());
model.set_user_input(0, 3, 2, "=ISNUMBER(A3)".to_string());
model.set_user_input(0, 4, 1, "3.1415926$".to_string());
model.evaluate();
// two decimal rounded up
assert_eq!(model._get_text("A1"), "$100.35");
assert_eq!(model._get_text("B1"), *"TRUE");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A1"),
Ok(CellValue::Number(100.348))
);
// No space
assert_eq!(model._get_text("A2"), "$100.35");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A2"),
Ok(CellValue::Number(100.348))
);
assert_eq!(model._get_text("B2"), *"TRUE");
// Dollar is on the right
assert_eq!(model._get_text("A3"), "100$");
assert_eq!(model._get_text("B3"), *"TRUE");
assert_eq!(model._get_text("A4"), "3.14$");
}
#[test]
fn scientific() {
let mut model = new_empty_model();
model.set_user_input(0, 1, 1, "3e-4".to_string());
model.set_user_input(0, 2, 1, "5e-4$".to_string());
model.set_user_input(0, 3, 1, "6e-4%".to_string());
model.evaluate();
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A1"),
Ok(CellValue::Number(0.0003))
);
assert_eq!(model._get_text("Sheet1!A1"), "3.00E-04");
assert_eq!(model._get_text("Sheet1!A2"), "5.00E-04");
assert_eq!(model._get_text("Sheet1!A3"), "6.00E-06");
}
#[test]
fn test_percentage() {
let mut model = new_empty_model();
model.set_user_input(0, 10, 1, "50%".to_string());
model.set_user_input(0, 10, 2, "=ISNUMBER(A10)".to_string());
model.set_user_input(0, 11, 1, "55.759%".to_string());
model.evaluate();
assert_eq!(model._get_text("B10"), *"TRUE");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A10"),
Ok(CellValue::Number(0.5))
);
// Two decimal places
assert_eq!(model._get_text("A11"), "55.76%");
}
#[test]
fn test_percentage_ops() {
let mut model = new_empty_model();
model._set("A1", "5%");
model._set("A2", "20%");
model.set_user_input(0, 3, 1, "=A1+A2".to_string());
model.set_user_input(0, 4, 1, "=A1*A2".to_string());
model.evaluate();
assert_eq!(model._get_text("A3"), *"25%");
assert_eq!(model._get_text("A4"), *"1.00%");
}
#[test]
fn test_numbers() {
let mut model = new_empty_model();
model.set_user_input(0, 1, 1, "1,000,000".to_string());
model.set_user_input(0, 20, 1, "50,123.549".to_string());
model.set_user_input(0, 21, 1, "50,12.549".to_string());
model.set_user_input(0, 22, 1, "1,234567".to_string());
model.evaluate();
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A1"),
Ok(CellValue::Number(1000000.0))
);
// Two decimal places
assert_eq!(model._get_text("A20"), "50,123.55");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A20"),
Ok(CellValue::Number(50123.549))
);
// This is a string
assert_eq!(model._get_text("A21"), "50,12.549");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A21"),
Ok(CellValue::String("50,12.549".to_string()))
);
// Commas in all places
assert_eq!(model._get_text("A22"), "1,234,567");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A22"),
Ok(CellValue::Number(1234567.0))
);
}
#[test]
fn test_negative_numbers() {
let mut model = new_empty_model();
model.set_user_input(0, 1, 1, "-100".to_string());
model.evaluate();
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A1"),
Ok(CellValue::Number(-100.0))
);
}
#[test]
fn test_negative_currencies() {
let mut model = new_empty_model();
model.set_user_input(0, 1, 1, "-$100".to_string());
model.set_user_input(0, 2, 1, "-$99.123".to_string());
// This is valid!
model.set_user_input(0, 3, 1, "$-345".to_string());
model.set_user_input(0, 1, 2, "-200$".to_string());
model.set_user_input(0, 2, 2, "-92.689$".to_string());
// This is valid!
model.set_user_input(0, 3, 2, "-22$".to_string());
model.evaluate();
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A1"),
Ok(CellValue::Number(-100.0))
);
assert_eq!(model._get_text("A1"), *"-$100");
assert_eq!(model._get_text("A2"), *"-$99.12");
assert_eq!(model._get_text("A3"), *"-$345");
assert_eq!(model._get_text("B1"), *"-200$");
assert_eq!(model._get_text("B2"), *"-92.69$");
assert_eq!(model._get_text("B3"), *"-22$");
}
#[test]
fn test_formulas() {
let mut model = new_empty_model();
model._set("A1", "$100");
model._set("A2", "$200");
model.set_user_input(0, 3, 1, "=A1+A2".to_string());
model.set_user_input(0, 4, 1, "=SUM(A1:A3)".to_string());
model.evaluate();
assert_eq!(model._get_text("A3"), *"$300");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A3"),
Ok(CellValue::Number(300.0))
);
assert_eq!(model._get_text("A4"), *"$600");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A4"),
Ok(CellValue::Number(600.0))
);
}
#[test]
fn test_product() {
let mut model = new_empty_model();
model._set("A1", "$100");
model._set("A2", "$5");
model._set("A3", "4");
model.set_user_input(0, 1, 2, "=A1*A2".to_string());
model.set_user_input(0, 2, 2, "=A1*A3".to_string());
model.set_user_input(0, 3, 2, "=A1*3".to_string());
model.evaluate();
assert_eq!(model._get_text("B1"), *"500");
assert_eq!(model._get_text("B2"), *"$400");
assert_eq!(model._get_text("B3"), *"$300");
}
#[test]
fn test_division() {
let mut model = new_empty_model();
model._set("A1", "$100");
model._set("A2", "$5");
model._set("A3", "4");
model.set_user_input(0, 1, 2, "=A1/A2".to_string());
model.set_user_input(0, 2, 2, "=A1/A3".to_string());
model.set_user_input(0, 3, 2, "=A1/2".to_string());
model.set_user_input(0, 4, 2, "=100/A2".to_string());
model.evaluate();
assert_eq!(model._get_text("B1"), *"20");
assert_eq!(model._get_text("B2"), *"$25");
assert_eq!(model._get_text("B3"), *"$50");
assert_eq!(model._get_text("B4"), *"20");
}
#[test]
fn test_some_complex_examples() {
let mut model = new_empty_model();
// $3.00 / 2 = $1.50
model._set("A1", "$3.00");
model._set("A2", "2");
model.set_user_input(0, 3, 1, "=A1/A2".to_string());
// $3 / 2 = $1
model._set("B1", "$3");
model._set("B2", "2");
model.set_user_input(0, 3, 2, "=B1/B2".to_string());
// $5.00 * 25% = 25% * $5.00 = $1.25
model._set("C1", "$5.00");
model._set("C2", "25%");
model.set_user_input(0, 3, 3, "=C1*C2".to_string());
model.set_user_input(0, 4, 3, "=C2*C1".to_string());
// $5 * 75% = 75% * $5 = $1
model._set("D1", "$5");
model._set("D2", "75%");
model.set_user_input(0, 3, 4, "=D1*D2".to_string());
model.set_user_input(0, 4, 4, "=D2*D1".to_string());
// $10 + $9.99 = $9.99 + $10 = $19.99
model._set("E1", "$10");
model._set("E2", "$9.99");
model.set_user_input(0, 3, 5, "=E1+E2".to_string());
model.set_user_input(0, 4, 5, "=E2+E1".to_string());
// $2 * 2 = 2 * $2 = $4
model._set("F1", "$2");
model._set("F2", "2");
model.set_user_input(0, 3, 6, "=F1*F2".to_string());
model.set_user_input(0, 4, 6, "=F2*F1".to_string());
// $2.50 * 2 = 2 * $2.50 = $5.00
model._set("G1", "$2.50");
model._set("G2", "2");
model.set_user_input(0, 3, 7, "=G1*G2".to_string());
model.set_user_input(0, 4, 7, "=G2*G1".to_string());
// $2 * 2.5 = 2.5 * $2 = $5
model._set("H1", "$2");
model._set("H2", "2.5");
model.set_user_input(0, 3, 8, "=H1*H2".to_string());
model.set_user_input(0, 4, 8, "=H2*H1".to_string());
// 10% * 1,000 = 1,000 * 10% = 100
model._set("I1", "10%");
model._set("I2", "1,000");
model.set_user_input(0, 3, 9, "=I1*I2".to_string());
model.set_user_input(0, 4, 9, "=I2*I1".to_string());
model.evaluate();
assert_eq!(model._get_text("A3"), *"$1.50");
assert_eq!(model._get_text("B3"), *"$2");
assert_eq!(model._get_text("C3"), *"$1.25");
assert_eq!(model._get_text("C4"), *"$1.25");
assert_eq!(model._get_text("D3"), *"$3.75");
assert_eq!(model._get_text("D4"), *"$3.75");
assert_eq!(model._get_text("E3"), *"$19.99");
assert_eq!(model._get_text("E4"), *"$19.99");
assert_eq!(model._get_text("F3"), *"$4");
assert_eq!(model._get_text("F4"), *"$4");
assert_eq!(model._get_text("G3"), *"$5.00");
assert_eq!(model._get_text("G4"), *"$5.00");
assert_eq!(model._get_text("H3"), *"$5");
assert_eq!(model._get_text("H4"), *"$5");
assert_eq!(model._get_text("I3"), *"100");
assert_eq!(model._get_text("I4"), *"100");
}
#[test]
fn test_financial_functions() {
// Some functions imply a currency formatting even on error
let mut model = new_empty_model();
model._set("A2", "8%");
model._set("A3", "10");
model._set("A4", "$10,000");
model.set_user_input(0, 5, 1, "=PMT(A2/12,A3,A4)".to_string());
model.set_user_input(0, 6, 1, "=PMT(A2/12,A3,A4,,1)".to_string());
model.set_user_input(0, 7, 1, "=PMT(0.2, 3, -200)".to_string());
model.evaluate();
// This two are negative numbers
assert_eq!(model._get_text("A5"), *"-$1,037.03");
assert_eq!(model._get_text("A6"), *"-$1,030.16");
// This is a positive number
assert_eq!(model._get_text("A7"), *"$94.95");
}
#[test]
fn test_sum_function() {
let mut model = new_empty_model();
model._set("A1", "$100");
model._set("A2", "$300");
model.set_user_input(0, 1, 2, "=SUM(A:A)".to_string());
model.set_user_input(0, 2, 2, "=SUM(A1:A2)".to_string());
model.set_user_input(0, 3, 2, "=SUM(A1, A2, A3)".to_string());
model.evaluate();
assert_eq!(model._get_text("B1"), *"$400");
assert_eq!(model._get_text("B2"), *"$400");
assert_eq!(model._get_text("B3"), *"$400");
}
#[test]
fn test_number() {
let mut model = new_empty_model();
model.set_user_input(0, 1, 1, "3".to_string());
model.evaluate();
assert_eq!(model._get_text("A1"), *"3");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A1"),
Ok(CellValue::Number(3.0))
);
}
#[test]
fn test_currencies_eur_prefix() {
let mut model = new_empty_model();
model.set_user_input(0, 1, 1, "€100.348".to_string());
model.evaluate();
assert_eq!(model._get_text("A1"), "€100.35");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A1"),
Ok(CellValue::Number(100.348))
);
}
#[test]
fn test_currencies_eur_suffix() {
let mut model = new_empty_model();
model.set_user_input(0, 1, 1, "100.348€".to_string());
model.set_user_input(0, 2, 1, "25€".to_string());
// negatives
model.set_user_input(0, 1, 2, "-123.348€".to_string());
model.set_user_input(0, 2, 2, "-42€".to_string());
// with a space
model.set_user_input(0, 1, 3, "101.348 €".to_string());
model.set_user_input(0, 2, 3, "26 €".to_string());
model.set_user_input(0, 1, 4, "-12.348 €".to_string());
model.set_user_input(0, 2, 4, "-45 €".to_string());
model.evaluate();
assert_eq!(model._get_text("A1"), "100.35€");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A1"),
Ok(CellValue::Number(100.348))
);
assert_eq!(model._get_text("A2"), "25€");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A2"),
Ok(CellValue::Number(25.0))
);
assert_eq!(model._get_text("B1"), "-123.35€");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!B1"),
Ok(CellValue::Number(-123.348))
);
assert_eq!(model._get_text("B2"), "-42€");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!B2"),
Ok(CellValue::Number(-42.0))
);
// with a space
assert_eq!(model._get_text("C1"), "101.35€");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!C1"),
Ok(CellValue::Number(101.348))
);
assert_eq!(model._get_text("C2"), "26€");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!C2"),
Ok(CellValue::Number(26.0))
);
assert_eq!(model._get_text("D1"), "-12.35€");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!D1"),
Ok(CellValue::Number(-12.348))
);
assert_eq!(model._get_text("D2"), "-45€");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!D2"),
Ok(CellValue::Number(-45.0))
);
}
#[test]
fn test_sum_function_eur() {
let mut model = new_empty_model();
model._set("A1", "€100");
model._set("A2", "€300");
model.set_user_input(0, 1, 2, "=SUM(A:A)".to_string());
model.set_user_input(0, 2, 2, "=SUM(A1:A2)".to_string());
model.set_user_input(0, 3, 2, "=SUM(A1, A2, A3)".to_string());
model.evaluate();
assert_eq!(model._get_text("B1"), *"€400");
assert_eq!(model._get_text("B2"), *"€400");
assert_eq!(model._get_text("B3"), *"€400");
}
#[test]
fn input_dates() {
let mut model = new_empty_model();
model.set_user_input(0, 1, 1, "3/4/2025".to_string());
model.evaluate();
assert_eq!(model._get_text("A1"), "3/4/2025");
assert_eq!(
model.get_cell_value_by_ref("Sheet1!A1"),
Ok(CellValue::Number(45750.0))
);
// further date assignments do not change the format
model.set_user_input(0, 1, 1, "08-08-2028".to_string());
model.evaluate();
assert_eq!(model._get_text("A1"), "8/8/2028");
}

View File

@@ -0,0 +1,27 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_sheet_markup() {
let mut model = new_empty_model();
model._set("A1", "Item");
model._set("B1", "Cost");
model._set("A2", "Rent");
model._set("B2", "$600");
model._set("A3", "Electricity");
model._set("B3", "$200");
model._set("A4", "Total");
model._set("B4", "=SUM(B2:B3)");
let mut style = model.get_style_for_cell(0, 1, 1);
style.font.b = true;
model.set_cell_style(0, 1, 1, &style).unwrap();
model.set_cell_style(0, 1, 2, &style).unwrap();
model.set_cell_style(0, 4, 1, &style).unwrap();
assert_eq!(
model.sheet_markup(0),
Ok("**Item**|**Cost**\nRent|$600\nElectricity|$200\n**Total**|=SUM(B2:B3)".to_string()),
)
}

View File

@@ -0,0 +1,238 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_add_remove_sheets() {
let mut model = new_empty_model();
model._set("A1", "7");
model._set("A2", "=Sheet2!C3");
model.evaluate();
assert_eq!(model.workbook.get_worksheet_names(), ["Sheet1"]);
assert_eq!(model._get_text("A2"), "#REF!");
// Add a sheet
model.new_sheet();
assert_eq!(model.workbook.get_worksheet_names(), ["Sheet1", "Sheet2"]);
assert_eq!(model._get_text("A2"), "0");
model._set("Sheet2!A1", "=Sheet1!A1");
model.evaluate();
assert_eq!(model._get_text("Sheet2!A1"), "7");
// Rename the first sheet
let r = model.rename_sheet("Sheet1", "Ricci");
assert!(r.is_ok());
assert_eq!(model.workbook.get_worksheet_names(), ["Ricci", "Sheet2"]);
assert_eq!(model._get_text("Sheet2!A1"), "7");
assert_eq!(model._get_formula("Sheet2!A1"), "=Ricci!A1");
// Remove the first sheet
let r = model.delete_sheet_by_name("Ricci");
assert!(r.is_ok());
assert_eq!(model.workbook.get_worksheet_names(), ["Sheet2"]);
assert_eq!(model._get_text("Sheet2!A1"), "#REF!");
}
#[test]
fn test_rename_delete_to_existing() {
let mut model = new_empty_model();
model.new_sheet();
// Cannot rename to an existing one
let r = model.rename_sheet("Sheet1", "Sheet2");
assert!(r.is_err());
// Not every name is valid
let r = model.rename_sheet("Sheet1", "Invalid[]");
assert!(r.is_err());
// Cannot delete something that does not exist
let r = model.delete_sheet_by_name("NonExists");
assert!(r.is_err());
}
#[test]
fn test_rename_one_sheet() {
let mut model = new_empty_model();
let r = model.rename_sheet("Sheet1", "Sheet2");
assert!(r.is_ok());
model.new_sheet();
assert_eq!(model.workbook.get_worksheet_names(), ["Sheet2", "Sheet1"]);
}
#[test]
fn test_rename_and_formula() {
let mut model = new_empty_model();
model._set("A1", "=A2*3");
model._set("A2", "42");
model.evaluate();
let r = model.rename_sheet("Sheet1", "Sheet2");
assert!(r.is_ok());
model.new_sheet();
assert_eq!(model.workbook.get_worksheet_names(), ["Sheet2", "Sheet1"]);
model._set("Sheet2!A3", "= A1 * 3");
model.evaluate();
assert_eq!(model._get_formula("Sheet2!A3"), "=A1*3");
}
#[test]
fn test_correct_quoting() {
let mut model = new_empty_model();
model.new_sheet();
model._set("Sheet2!B3", "400");
model._set("A1", "=Sheet2!B3*2");
model.evaluate();
assert_eq!(model._get_text("A1"), "800");
let r = model.rename_sheet("Sheet2", "New Sheet");
assert!(r.is_ok());
assert_eq!(model._get_text("A1"), "800");
assert_eq!(model._get_formula("A1"), "='New Sheet'!B3*2")
}
#[test]
fn test_cannot_delete_last_sheet() {
let mut model = new_empty_model();
let r = model.delete_sheet_by_name("Sheet1");
assert_eq!(r, Err("Cannot delete only sheet".to_string()));
model.new_sheet();
let r = model.delete_sheet_by_name("Sheet10");
assert_eq!(r, Err("Sheet not found".to_string()));
let r = model.delete_sheet_by_name("Sheet1");
assert!(r.is_ok());
}
#[test]
fn test_ranges() {
let mut model = new_empty_model();
model._set("A1", "=SUM(Sheet2!A1:C3)*Sheet3!A2");
model.evaluate();
assert_eq!(model._get_text("A1"), "#REF!");
model.new_sheet();
assert_eq!(model._get_text("A1"), "#REF!");
model.new_sheet();
assert_eq!(model._get_text("A1"), "0");
model._set("Sheet3!A2", "42");
model._set("Sheet2!A1", "2");
model.evaluate();
assert_eq!(model._get_text("A1"), "84");
let r = model.rename_sheet("Sheet2", "Other Sheet");
assert!(r.is_ok());
assert_eq!(
model._get_formula("A1"),
"=SUM('Other Sheet'!A1:C3)*Sheet3!A2"
);
}
#[test]
fn test_insert_sheet() {
// Set a formula with a wrong sheet
let mut model = new_empty_model();
model._set("A1", "=Bacchus!A3");
model._set("A2", "=Dionysus!A3");
model.evaluate();
assert_eq!(model._get_text("A1"), "#REF!");
assert_eq!(model._get_text("A2"), "#REF!");
// Insert the sheet at the end and check the formula
assert!(model.insert_sheet("Bacchus", 1, None).is_ok());
model.set_user_input(1, 3, 1, "42".to_string());
model.evaluate();
assert_eq!(model._get_text("A1"), "42");
assert_eq!(model._get_text("A2"), "#REF!");
// Insert a sheet in between the other two
assert!(model.insert_sheet("Dionysus", 1, None).is_ok());
model.set_user_input(1, 3, 1, "111".to_string());
model.evaluate();
assert_eq!(model._get_text("A1"), "42");
assert_eq!(model._get_text("A2"), "111");
assert_eq!(
model.workbook.get_worksheet_names(),
["Sheet1", "Dionysus", "Bacchus"]
);
// Insert a sheet out of bounds
assert!(model.insert_sheet("OutOfBounds", 4, None).is_err());
model.evaluate();
assert_eq!(
model.workbook.get_worksheet_names(),
["Sheet1", "Dionysus", "Bacchus"]
);
// Insert at the beginning
assert!(model.insert_sheet("FirstSheet", 0, None).is_ok());
model.evaluate();
assert_eq!(
model.workbook.get_worksheet_names(),
["FirstSheet", "Sheet1", "Dionysus", "Bacchus"]
);
}
#[test]
fn test_rename_sheet() {
let mut model = new_empty_model();
model.new_sheet();
model._set("A1", "=NewSheet!A3");
model.set_user_input(1, 3, 1, "25".to_string());
model.evaluate();
assert_eq!(model._get_text("A1"), "#REF!");
assert!(model.rename_sheet("Sheet2", "NewSheet").is_ok());
model.evaluate();
assert_eq!(model._get_text("A1"), "25");
}
#[test]
fn test_rename_sheet_by_index() {
let mut model = new_empty_model();
model.new_sheet();
model._set("A1", "=NewSheet!A1");
model.set_user_input(1, 1, 1, "25".to_string());
model.evaluate();
assert_eq!(model._get_text("A1"), "#REF!");
assert!(model.rename_sheet_by_index(1, "NewSheet").is_ok());
model.evaluate();
assert_eq!(model._get_text("A1"), "25");
}
#[test]
fn test_rename_sheet_by_index_error() {
let mut model = new_empty_model();
model.new_sheet();
assert!(model.rename_sheet_by_index(0, "OldSheet").is_ok());
assert!(model.rename_sheet_by_index(2, "NewSheet").is_err());
}
#[test]
fn test_delete_sheet_by_index() {
let mut model = new_empty_model();
model._set("A1", "7");
model._set("A2", "=Sheet2!C3");
model.evaluate();
assert_eq!(model.workbook.get_worksheet_names(), ["Sheet1"]);
assert_eq!(model._get_text("A2"), "#REF!");
// Add a sheet
model.new_sheet();
assert_eq!(model.workbook.get_worksheet_names(), ["Sheet1", "Sheet2"]);
assert_eq!(model._get_text("A2"), "0");
model._set("Sheet2!A1", "=Sheet1!A1");
model.evaluate();
assert_eq!(model._get_text("Sheet2!A1"), "7");
// Rename the first sheet
let r = model.rename_sheet("Sheet1", "Ricci");
assert!(r.is_ok());
assert_eq!(model.workbook.get_worksheet_names(), ["Ricci", "Sheet2"]);
assert_eq!(model._get_text("Sheet2!A1"), "7");
assert_eq!(model._get_formula("Sheet2!A1"), "=Ricci!A1");
// Remove the first sheet
let r = model.delete_sheet_by_name("Ricci");
assert!(r.is_ok());
assert_eq!(model.workbook.get_worksheet_names(), ["Sheet2"]);
assert_eq!(model._get_text("Sheet2!A1"), "#REF!");
}

View File

@@ -0,0 +1,64 @@
#![allow(clippy::unwrap_used)]
use crate::model::Style;
use crate::test::util::new_empty_model;
#[test]
fn test_model_set_cells_with_values_styles() {
let mut model = new_empty_model();
// Inputs
model.set_user_input(0, 1, 1, "21".to_string()); // A1
model.set_user_input(0, 2, 1, "42".to_string()); // A2
let style_base = model.get_style_for_cell(0, 1, 1);
let mut style = style_base.clone();
style.font.b = true;
style.num_fmt = "#,##0.00".to_string();
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
let mut style = style_base;
style.num_fmt = "#,##0.00".to_string();
assert!(model.set_cell_style(0, 2, 1, &style).is_ok());
let style: Style = model.get_style_for_cell(0, 2, 1);
assert_eq!(style.num_fmt, "#,##0.00".to_string());
}
#[test]
fn test_named_styles() {
let mut model = new_empty_model();
model._set("A1", "42");
let mut style = model.get_style_for_cell(0, 1, 1);
style.font.b = true;
assert!(model.set_cell_style(0, 1, 1, &style).is_ok());
let bold_style_index = model.get_cell_style_index(0, 1, 1);
let e = model
.workbook
.styles
.add_named_cell_style("bold", bold_style_index);
assert!(e.is_ok());
model._set("A2", "420");
let a2_style_index = model.get_cell_style_index(0, 2, 1);
assert!(a2_style_index != bold_style_index);
let e = model.set_cell_style_by_name(0, 2, 1, "bold");
assert!(e.is_ok());
assert_eq!(model.get_cell_style_index(0, 2, 1), bold_style_index);
}
#[test]
fn test_create_named_style() {
let mut model = new_empty_model();
model._set("A1", "42");
let mut style = model.get_style_for_cell(0, 1, 1);
assert!(!style.font.b);
style.font.b = true;
let e = model.workbook.styles.create_named_style("bold", &style);
assert!(e.is_ok());
let e = model.set_cell_style_by_name(0, 1, 1, "bold");
assert!(e.is_ok());
let style = model.get_style_for_cell(0, 1, 1);
assert!(style.font.b);
}

View File

@@ -0,0 +1,50 @@
#![allow(clippy::unwrap_used)]
use crate::mock_time;
use crate::model::Model;
use crate::test::util::new_empty_model;
// 14:44 20 Mar 2023 Berlin
const TIMESTAMP_2023: i64 = 1679319865208;
#[test]
fn today_basic() {
let mut model = new_empty_model();
model._set("A1", "=TODAY()");
model._set("A2", "=TEXT(A1, \"yyyy/m/d\")");
model.evaluate();
assert_eq!(model._get_text("A1"), *"08/11/2022");
assert_eq!(model._get_text("A2"), *"2022/11/8");
}
#[test]
fn today_with_wrong_tz() {
let model = Model::new_empty("model", "en", "Wrong Timezone");
assert!(model.is_err());
}
#[test]
fn now_basic_utc() {
mock_time::set_mock_time(TIMESTAMP_2023);
let mut model = Model::new_empty("model", "en", "UTC").unwrap();
model._set("A1", "=TODAY()");
model._set("A2", "=NOW()");
model.evaluate();
assert_eq!(model._get_text("A1"), *"20/03/2023");
assert_eq!(model._get_text("A2"), *"45005.572511574");
}
#[test]
fn now_basic_europe_berlin() {
mock_time::set_mock_time(TIMESTAMP_2023);
let mut model = Model::new_empty("model", "en", "Europe/Berlin").unwrap();
model._set("A1", "=TODAY()");
model._set("A2", "=NOW()");
model.evaluate();
assert_eq!(model._get_text("A1"), *"20/03/2023");
// This is UTC + 1 hour: 45005.572511574 + 1/24
assert_eq!(model._get_text("A2"), *"45005.614178241");
}

View File

@@ -0,0 +1,98 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_fn_pi_arguments() {
let mut model = new_empty_model();
model._set("A1", "=PI(1)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
}
#[test]
fn test_fn_atan2_arguments() {
let mut model = new_empty_model();
model._set("A1", "=ATAN2(1)");
model._set("A2", "=ATAN2(1,1)");
model._set("A3", "=ATAN2(1,1,1)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"0.785398163");
assert_eq!(model._get_text("A3"), *"#ERROR!");
}
#[test]
fn test_fn_trigonometric_arguments() {
let mut model = new_empty_model();
model._set("A1", "=SIN()");
model._set("A2", "=COS()");
model._set("A3", "=TAN()");
model._set("A5", "=ASIN()");
model._set("A6", "=ACOS()");
model._set("A7", "=ATAN()");
model._set("A9", "=SINH()");
model._set("A10", "=COSH()");
model._set("A11", "=TANH()");
model._set("A13", "=ASINH()");
model._set("A14", "=ACOSH()");
model._set("A15", "=ATANH()");
model._set("B1", "=SIN(1,2)");
model._set("B2", "=COS(1,2)");
model._set("B3", "=TAN(1,2)");
model._set("B5", "=ASIN(1,2)");
model._set("B6", "=ACOS(1,2)");
model._set("B7", "=ATAN(1,2)");
model._set("B9", "=SINH(1,2)");
model._set("B10", "=COSH(1,2)");
model._set("B11", "=TANH(1,2)");
model._set("B13", "=ASINH(1,2)");
model._set("B14", "=ACOSH(1,2)");
model._set("B15", "=ATANH(1,2)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"#ERROR!");
assert_eq!(model._get_text("A3"), *"#ERROR!");
assert_eq!(model._get_text("A5"), *"#ERROR!");
assert_eq!(model._get_text("A6"), *"#ERROR!");
assert_eq!(model._get_text("A7"), *"#ERROR!");
assert_eq!(model._get_text("A9"), *"#ERROR!");
assert_eq!(model._get_text("A10"), *"#ERROR!");
assert_eq!(model._get_text("A11"), *"#ERROR!");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
assert_eq!(model._get_text("B3"), *"#ERROR!");
assert_eq!(model._get_text("B5"), *"#ERROR!");
assert_eq!(model._get_text("B6"), *"#ERROR!");
assert_eq!(model._get_text("B7"), *"#ERROR!");
assert_eq!(model._get_text("B9"), *"#ERROR!");
assert_eq!(model._get_text("B10"), *"#ERROR!");
assert_eq!(model._get_text("B11"), *"#ERROR!");
}
#[test]
fn test_fn_tan_pi2() {
let mut model = new_empty_model();
model._set("A1", "=TAN(PI()/2)");
model.evaluate();
// This is consistent with IEEE 754 but inconsistent with Excel
assert_eq!(model._get_text("A1"), *"1.63312E+16");
}

View File

@@ -0,0 +1,275 @@
#![allow(clippy::unwrap_used)]
use crate::{
constants::{LAST_COLUMN, LAST_ROW},
test::util::new_empty_model,
worksheet::{NavigationDirection, WorksheetDimension},
};
#[test]
fn test_worksheet_dimension_empty_sheet() {
let model = new_empty_model();
assert_eq!(
model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension {
min_row: 1,
min_column: 1,
max_row: 1,
max_column: 1
}
);
}
#[test]
fn test_worksheet_dimension_single_cell() {
let mut model = new_empty_model();
model._set("W11", "1");
assert_eq!(
model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension {
min_row: 11,
min_column: 23,
max_row: 11,
max_column: 23
}
);
}
#[test]
fn test_worksheet_dimension_single_cell_set_empty() {
let mut model = new_empty_model();
model._set("W11", "1");
model.set_cell_empty(0, 11, 23).unwrap();
assert_eq!(
model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension {
min_row: 11,
min_column: 23,
max_row: 11,
max_column: 23
}
);
}
#[test]
fn test_worksheet_dimension_single_cell_deleted() {
let mut model = new_empty_model();
model._set("W11", "1");
model.delete_cell(0, 11, 23).unwrap();
assert_eq!(
model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension {
min_row: 1,
min_column: 1,
max_row: 1,
max_column: 1
}
);
}
#[test]
fn test_worksheet_dimension_multiple_cells() {
let mut model = new_empty_model();
model._set("W11", "1");
model._set("E11", "1");
model._set("AA17", "1");
model._set("G17", "1");
model._set("B19", "1");
model.delete_cell(0, 11, 23).unwrap();
assert_eq!(
model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension {
min_row: 11,
min_column: 2,
max_row: 19,
max_column: 27
}
);
}
#[test]
fn test_worksheet_dimension_progressive() {
let mut model = new_empty_model();
assert_eq!(
model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension {
min_row: 1,
min_column: 1,
max_row: 1,
max_column: 1
}
);
model.set_user_input(0, 30, 50, "Hello World".to_string());
assert_eq!(
model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension {
min_row: 30,
min_column: 50,
max_row: 30,
max_column: 50
}
);
model.set_user_input(0, 10, 15, "Hello World".to_string());
assert_eq!(
model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension {
min_row: 10,
min_column: 15,
max_row: 30,
max_column: 50
}
);
model.set_user_input(0, 5, 25, "Hello World".to_string());
assert_eq!(
model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension {
min_row: 5,
min_column: 15,
max_row: 30,
max_column: 50
}
);
model.set_user_input(0, 10, 250, "Hello World".to_string());
assert_eq!(
model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension {
min_row: 5,
min_column: 15,
max_row: 30,
max_column: 250
}
);
}
#[test]
fn test_worksheet_navigate_to_edge_in_direction() {
let inline_spreadsheet = [
[0, 0, 0, 0, 0, 0, 0, 0], // row 1
[0, 1, 0, 1, 1, 1, 0, 1], // row 2
[0, 1, 0, 1, 1, 0, 0, 0], // row 3
[0, 1, 0, 1, 1, 0, 0, 0], // row 4
[0, 0, 0, 1, 0, 0, 0, 0], // row 5
[0, 1, 1, 0, 1, 0, 0, 0], // row 6
[0, 0, 0, 0, 0, 0, 0, 0], // row 7
];
// 1, 2, 3, 4, 5, 6, 7, 8 - columns
let mut model = new_empty_model();
for (row_index, row) in inline_spreadsheet.into_iter().enumerate() {
for (column_index, value) in row.into_iter().enumerate() {
if value != 0 {
model.update_cell_with_number(
0,
(row_index as i32) + 1,
(column_index as i32) + 1,
value.into(),
);
}
}
}
let worksheet = model.workbook.worksheet(0).unwrap();
// Simple alias for readability of tests
let navigate = |row, column, direction| {
worksheet
.navigate_to_edge_in_direction(row, column, direction)
.unwrap()
};
assert_eq!(navigate(1, 1, NavigationDirection::Up), (1, 1));
assert_eq!(navigate(1, 1, NavigationDirection::Left), (1, 1));
assert_eq!(navigate(1, 1, NavigationDirection::Down), (LAST_ROW, 1));
assert_eq!(navigate(1, 1, NavigationDirection::Right), (1, LAST_COLUMN));
assert_eq!(navigate(LAST_ROW, 1, NavigationDirection::Up), (1, 1));
assert_eq!(
navigate(LAST_ROW, 1, NavigationDirection::Left),
(LAST_ROW, 1)
);
assert_eq!(
navigate(LAST_ROW, 1, NavigationDirection::Down),
(LAST_ROW, 1)
);
assert_eq!(
navigate(LAST_ROW, 1, NavigationDirection::Right),
(LAST_ROW, LAST_COLUMN)
);
assert_eq!(
navigate(1, LAST_COLUMN, NavigationDirection::Up),
(1, LAST_COLUMN)
);
assert_eq!(navigate(1, LAST_COLUMN, NavigationDirection::Left), (1, 1));
assert_eq!(
navigate(1, LAST_COLUMN, NavigationDirection::Down),
(LAST_ROW, LAST_COLUMN)
);
assert_eq!(
navigate(1, LAST_COLUMN, NavigationDirection::Right),
(1, LAST_COLUMN)
);
assert_eq!(
navigate(LAST_ROW, LAST_COLUMN, NavigationDirection::Up),
(1, LAST_COLUMN)
);
assert_eq!(
navigate(LAST_ROW, LAST_COLUMN, NavigationDirection::Left),
(LAST_ROW, 1)
);
assert_eq!(
navigate(LAST_ROW, LAST_COLUMN, NavigationDirection::Down),
(LAST_ROW, LAST_COLUMN)
);
assert_eq!(
navigate(LAST_ROW, LAST_COLUMN, NavigationDirection::Right),
(LAST_ROW, LAST_COLUMN)
);
// Direction = right
assert_eq!(navigate(2, 1, NavigationDirection::Right), (2, 2));
assert_eq!(navigate(2, 2, NavigationDirection::Right), (2, 4));
assert_eq!(navigate(2, 4, NavigationDirection::Right), (2, 6));
assert_eq!(navigate(2, 6, NavigationDirection::Right), (2, 8));
assert_eq!(navigate(2, 8, NavigationDirection::Right), (2, LAST_COLUMN));
assert_eq!(navigate(2, 3, NavigationDirection::Right), (2, 4));
assert_eq!(navigate(5, 1, NavigationDirection::Right), (5, 4));
assert_eq!(navigate(5, 2, NavigationDirection::Right), (5, 4));
// Direction = left
assert_eq!(navigate(2, LAST_COLUMN, NavigationDirection::Left), (2, 8));
assert_eq!(navigate(2, 8, NavigationDirection::Left), (2, 6));
assert_eq!(navigate(2, 6, NavigationDirection::Left), (2, 4));
assert_eq!(navigate(2, 4, NavigationDirection::Left), (2, 2));
assert_eq!(navigate(2, 2, NavigationDirection::Left), (2, 1));
assert_eq!(navigate(2, 3, NavigationDirection::Left), (2, 2));
assert_eq!(navigate(5, 8, NavigationDirection::Left), (5, 4));
assert_eq!(navigate(5, 7, NavigationDirection::Left), (5, 4));
// Direction = down
assert_eq!(navigate(1, 5, NavigationDirection::Down), (2, 5));
assert_eq!(navigate(2, 5, NavigationDirection::Down), (4, 5));
assert_eq!(navigate(4, 5, NavigationDirection::Down), (6, 5));
assert_eq!(navigate(6, 5, NavigationDirection::Down), (LAST_ROW, 5));
assert_eq!(navigate(2, 3, NavigationDirection::Down), (6, 3));
assert_eq!(navigate(3, 3, NavigationDirection::Down), (6, 3));
assert_eq!(navigate(5, 3, NavigationDirection::Down), (6, 3));
// Direction = up
assert_eq!(navigate(LAST_ROW, 5, NavigationDirection::Up), (6, 5));
assert_eq!(navigate(6, 5, NavigationDirection::Up), (4, 5));
assert_eq!(navigate(4, 5, NavigationDirection::Up), (2, 5));
assert_eq!(navigate(2, 5, NavigationDirection::Up), (1, 5));
assert_eq!(navigate(7, 3, NavigationDirection::Up), (6, 3));
assert_eq!(navigate(8, 3, NavigationDirection::Up), (6, 3));
assert_eq!(navigate(9, 3, NavigationDirection::Up), (6, 3));
}

52
base/src/test/util.rs Normal file
View File

@@ -0,0 +1,52 @@
#![allow(clippy::unwrap_used)]
use crate::calc_result::CellReference;
use crate::model::Model;
use crate::types::Cell;
pub fn new_empty_model() -> Model {
Model::new_empty("model", "en", "UTC").unwrap()
}
impl Model {
fn _parse_reference(&self, cell: &str) -> CellReference {
if cell.contains('!') {
self.parse_reference(cell).unwrap()
} else {
self.parse_reference(&format!("Sheet1!{}", cell)).unwrap()
}
}
pub fn _set(&mut self, cell: &str, value: &str) {
let cell_reference = self._parse_reference(cell);
let column = cell_reference.column;
let row = cell_reference.row;
self.set_user_input(cell_reference.sheet, row, column, value.to_string());
}
pub fn _has_formula(&self, cell: &str) -> bool {
self._get_formula_opt(cell).is_some()
}
pub fn _get_formula(&self, cell: &str) -> String {
self._get_formula_opt(cell).unwrap_or_default()
}
fn _get_formula_opt(&self, cell: &str) -> Option<String> {
let cell_reference = self._parse_reference(cell);
let column = cell_reference.column;
let row = cell_reference.row;
self.cell_formula(cell_reference.sheet, row, column)
.unwrap()
}
pub fn _get_text_at(&self, sheet: u32, row: i32, column: i32) -> String {
self.formatted_cell_value(sheet, row, column).unwrap()
}
pub fn _get_text(&self, cell: &str) -> String {
let CellReference { sheet, row, column } = self._parse_reference(cell);
self._get_text_at(sheet, row, column)
}
pub fn _get_cell(&self, cell: &str) -> &Cell {
let cell_reference = self._parse_reference(cell);
let worksheet = self.workbook.worksheet(cell_reference.sheet).unwrap();
worksheet
.cell(cell_reference.row, cell_reference.column)
.unwrap()
}
}