What are we trying to achieve ? ++ Currently all the major public set functions is panic prone and does not handle and return error. This PR tries to address to all those functions. What major errors that could happen in these functions ? ++ All the functions which are being made as error safe is being tested against invalid sheet, row and column values, which could given by user What are the list of functions whose return type has been altered ? **base/src/model.rs** 1. update_cell_with_text 2. update_cell_with_bool 3. update_cell_with_number 4. set_user_input 5. get_cell_style_index 6. get_style_for_cell 7. set_cell_with_string ++> New functions being added 1. set_cell_with_boolean 2. set_cell_with_number **base/src/styles.rs** 1. get_style_with_quote_prefix 3. get_style_with_format 4. get_style_without_quote_prefix 5. get_style **base/src/worksheet.rs** 1. update_cell 2. set_cell_style 3. set_cell_with_formula 4. set_cell_with_number 6. set_cell_with_string 8. set_cell_with_boolean 9. set_cell_with_error 10. cell_clear_contents 11. cell_clear_contents_with_style ++> Above is the comprehensive list of all functions being ( most are public, some are private ) altered for better error handling. As a side effect of changing function signature, there are many changes being done to other functions ( mostly adding "?" to enable to error propagation further )
511 lines
15 KiB
Rust
511 lines
15 KiB
Rust
#![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())
|
|
.unwrap();
|
|
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()).unwrap();
|
|
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()).unwrap(); // A1
|
|
model
|
|
.set_user_input(0, 1, 2, "= A1* 2-4".to_string())
|
|
.unwrap(); // 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())
|
|
.unwrap(); // 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())
|
|
.unwrap();
|
|
model.set_user_input(0, 1, 2, "=A1".to_string()).unwrap();
|
|
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()).unwrap();
|
|
model.set_user_input(0, 2, 1, "TRUE".to_string()).unwrap();
|
|
model.set_user_input(0, 3, 1, "True".to_string()).unwrap();
|
|
model.set_user_input(0, 4, 1, "false".to_string()).unwrap();
|
|
model.set_user_input(0, 5, 1, "FALSE".to_string()).unwrap();
|
|
model.set_user_input(0, 6, 1, "False".to_string()).unwrap();
|
|
|
|
model
|
|
.set_user_input(0, 1, 2, "=ISLOGICAL(A1)".to_string())
|
|
.unwrap();
|
|
model
|
|
.set_user_input(0, 2, 2, "=ISLOGICAL(A2)".to_string())
|
|
.unwrap();
|
|
model
|
|
.set_user_input(0, 3, 2, "=ISLOGICAL(A3)".to_string())
|
|
.unwrap();
|
|
model
|
|
.set_user_input(0, 4, 2, "=ISLOGICAL(A4)".to_string())
|
|
.unwrap();
|
|
model
|
|
.set_user_input(0, 5, 2, "=ISLOGICAL(A5)".to_string())
|
|
.unwrap();
|
|
model
|
|
.set_user_input(0, 6, 2, "=ISLOGICAL(A6)".to_string())
|
|
.unwrap();
|
|
|
|
model
|
|
.set_user_input(0, 1, 5, "=IF(false, True, FALSe)".to_string())
|
|
.unwrap();
|
|
|
|
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).unwrap();
|
|
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).unwrap();
|
|
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).unwrap();
|
|
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).unwrap();
|
|
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).unwrap();
|
|
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).unwrap();
|
|
assert!(style.font.b);
|
|
assert!(!style.font.i);
|
|
|
|
let style = model.get_style_for_cell(0, 1, 2).unwrap();
|
|
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).unwrap();
|
|
let style_index = model.get_cell_style_index(0, 1, 1).unwrap();
|
|
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).unwrap();
|
|
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()).unwrap(); // A1
|
|
model.set_user_input(0, 2, 1, "2".to_string()).unwrap(); // A2
|
|
|
|
let style_index = model.get_cell_style_index(0, 1, 1).unwrap();
|
|
assert_eq!(style_index, 0);
|
|
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
|
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).unwrap();
|
|
assert_eq!(style_index, 1);
|
|
let style_index = model.get_cell_style_index(0, 2, 1).unwrap();
|
|
assert_eq!(style_index, 1);
|
|
|
|
model.update_cell_with_number(0, 1, 2, 1.0).unwrap();
|
|
model.update_cell_with_number(0, 2, 1, 2.0).unwrap();
|
|
|
|
model.evaluate();
|
|
|
|
// Styles are not modified
|
|
let style_index = model.get_cell_style_index(0, 1, 1).unwrap();
|
|
assert_eq!(style_index, 1);
|
|
let style_index = model.get_cell_style_index(0, 2, 1).unwrap();
|
|
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).unwrap();
|
|
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).unwrap();
|
|
assert_eq!(style.num_fmt, "#.##");
|
|
|
|
let mut style = model.get_style_for_cell(0, 10, 1).unwrap();
|
|
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).unwrap();
|
|
assert_eq!(style.num_fmt, "$$#,##0.0000");
|
|
|
|
// Make sure old style is not touched
|
|
let style = model.get_style_for_cell(0, 1, 1).unwrap();
|
|
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())
|
|
.unwrap();
|
|
// This will fail anyway
|
|
model
|
|
.set_user_input(0, 4, 1, "=SUM(A1*".to_string())
|
|
.unwrap();
|
|
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).unwrap();
|
|
style.num_fmt = "$#,##0.00".to_string();
|
|
model.set_cell_style(0, 5, 1, &style).unwrap();
|
|
|
|
model.evaluate();
|
|
|
|
assert_eq!(model.get_formatted_cell_value(0, 1, 1).unwrap(), "foobar");
|
|
assert_eq!(model.get_formatted_cell_value(0, 2, 1).unwrap(), "TRUE");
|
|
assert_eq!(model.get_formatted_cell_value(0, 3, 1).unwrap(), "");
|
|
assert_eq!(model.get_formatted_cell_value(0, 4, 1).unwrap(), "123.456");
|
|
assert_eq!(model.get_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.get_cell_formula(0, 1, 1), // A1
|
|
Ok(Some("=1+2+3".to_string())),
|
|
);
|
|
assert_eq!(
|
|
model.get_cell_formula(0, 2, 1), // A2
|
|
Ok(None),
|
|
);
|
|
assert_eq!(
|
|
model.get_cell_formula(0, 3, 1), // A3 - empty cell
|
|
Ok(None),
|
|
);
|
|
|
|
assert_eq!(
|
|
model.get_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.get_cell_formula(0, 1, 1).unwrap(),
|
|
Some("=_xlfn.SIN(1)".to_string())
|
|
);
|
|
// unknown formulas keep the '_xlfn.' prefix
|
|
assert_eq!(
|
|
model.get_cell_formula(0, 2, 1).unwrap(),
|
|
Some("=_xlfn.SINY(1)".to_string())
|
|
);
|
|
assert_eq!(
|
|
model.get_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.get_cell_formula(0, 1, 1).unwrap(),
|
|
Some("=SIN(1)".to_string())
|
|
);
|
|
assert_eq!(
|
|
model.get_cell_formula(0, 2, 1).unwrap(),
|
|
Some("=SIN(2)".to_string())
|
|
);
|
|
}
|