Files
IronCalc/base/src/test/test_general.rs
Varun Hegde 2b03b3e3b9 Error Handling of public Set functions (#88)
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 )
2024-09-14 17:37:31 +02:00

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())
);
}