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 )
545 lines
15 KiB
Rust
545 lines
15 KiB
Rust
#![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())
|
|
.unwrap();
|
|
model
|
|
.set_user_input(0, 1, 2, "=ISNUMBER(A1)".to_string())
|
|
.unwrap();
|
|
|
|
model
|
|
.set_user_input(0, 2, 1, "$ 100.348".to_string())
|
|
.unwrap();
|
|
model
|
|
.set_user_input(0, 2, 2, "=ISNUMBER(A2)".to_string())
|
|
.unwrap();
|
|
|
|
model.set_user_input(0, 3, 1, "100$".to_string()).unwrap();
|
|
model
|
|
.set_user_input(0, 3, 2, "=ISNUMBER(A3)".to_string())
|
|
.unwrap();
|
|
|
|
model
|
|
.set_user_input(0, 4, 1, "3.1415926$".to_string())
|
|
.unwrap();
|
|
|
|
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()).unwrap();
|
|
model.set_user_input(0, 2, 1, "5e-4$".to_string()).unwrap();
|
|
model.set_user_input(0, 3, 1, "6e-4%".to_string()).unwrap();
|
|
|
|
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()).unwrap();
|
|
model
|
|
.set_user_input(0, 10, 2, "=ISNUMBER(A10)".to_string())
|
|
.unwrap();
|
|
model
|
|
.set_user_input(0, 11, 1, "55.759%".to_string())
|
|
.unwrap();
|
|
|
|
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()).unwrap();
|
|
model.set_user_input(0, 4, 1, "=A1*A2".to_string()).unwrap();
|
|
|
|
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())
|
|
.unwrap();
|
|
|
|
model
|
|
.set_user_input(0, 20, 1, "50,123.549".to_string())
|
|
.unwrap();
|
|
model
|
|
.set_user_input(0, 21, 1, "50,12.549".to_string())
|
|
.unwrap();
|
|
model
|
|
.set_user_input(0, 22, 1, "1,234567".to_string())
|
|
.unwrap();
|
|
|
|
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()).unwrap();
|
|
|
|
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()).unwrap();
|
|
model
|
|
.set_user_input(0, 2, 1, "-$99.123".to_string())
|
|
.unwrap();
|
|
// This is valid!
|
|
model.set_user_input(0, 3, 1, "$-345".to_string()).unwrap();
|
|
|
|
model.set_user_input(0, 1, 2, "-200$".to_string()).unwrap();
|
|
model
|
|
.set_user_input(0, 2, 2, "-92.689$".to_string())
|
|
.unwrap();
|
|
// This is valid!
|
|
model.set_user_input(0, 3, 2, "-22$".to_string()).unwrap();
|
|
|
|
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()).unwrap();
|
|
model
|
|
.set_user_input(0, 4, 1, "=SUM(A1:A3)".to_string())
|
|
.unwrap();
|
|
|
|
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()).unwrap();
|
|
model.set_user_input(0, 2, 2, "=A1*A3".to_string()).unwrap();
|
|
model.set_user_input(0, 3, 2, "=A1*3".to_string()).unwrap();
|
|
|
|
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()).unwrap();
|
|
model.set_user_input(0, 2, 2, "=A1/A3".to_string()).unwrap();
|
|
model.set_user_input(0, 3, 2, "=A1/2".to_string()).unwrap();
|
|
model
|
|
.set_user_input(0, 4, 2, "=100/A2".to_string())
|
|
.unwrap();
|
|
|
|
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()).unwrap();
|
|
|
|
// $3 / 2 = $1
|
|
model._set("B1", "$3");
|
|
model._set("B2", "2");
|
|
model.set_user_input(0, 3, 2, "=B1/B2".to_string()).unwrap();
|
|
|
|
// $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()).unwrap();
|
|
model.set_user_input(0, 4, 3, "=C2*C1".to_string()).unwrap();
|
|
|
|
// $5 * 75% = 75% * $5 = $1
|
|
model._set("D1", "$5");
|
|
model._set("D2", "75%");
|
|
model.set_user_input(0, 3, 4, "=D1*D2".to_string()).unwrap();
|
|
model.set_user_input(0, 4, 4, "=D2*D1".to_string()).unwrap();
|
|
|
|
// $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()).unwrap();
|
|
model.set_user_input(0, 4, 5, "=E2+E1".to_string()).unwrap();
|
|
|
|
// $2 * 2 = 2 * $2 = $4
|
|
model._set("F1", "$2");
|
|
model._set("F2", "2");
|
|
model.set_user_input(0, 3, 6, "=F1*F2".to_string()).unwrap();
|
|
model.set_user_input(0, 4, 6, "=F2*F1".to_string()).unwrap();
|
|
|
|
// $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()).unwrap();
|
|
model.set_user_input(0, 4, 7, "=G2*G1".to_string()).unwrap();
|
|
|
|
// $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()).unwrap();
|
|
model.set_user_input(0, 4, 8, "=H2*H1".to_string()).unwrap();
|
|
|
|
// 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()).unwrap();
|
|
model.set_user_input(0, 4, 9, "=I2*I1".to_string()).unwrap();
|
|
|
|
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())
|
|
.unwrap();
|
|
model
|
|
.set_user_input(0, 6, 1, "=PMT(A2/12,A3,A4,,1)".to_string())
|
|
.unwrap();
|
|
model
|
|
.set_user_input(0, 7, 1, "=PMT(0.2, 3, -200)".to_string())
|
|
.unwrap();
|
|
|
|
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())
|
|
.unwrap();
|
|
model
|
|
.set_user_input(0, 2, 2, "=SUM(A1:A2)".to_string())
|
|
.unwrap();
|
|
model
|
|
.set_user_input(0, 3, 2, "=SUM(A1, A2, A3)".to_string())
|
|
.unwrap();
|
|
|
|
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()).unwrap();
|
|
|
|
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())
|
|
.unwrap();
|
|
|
|
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())
|
|
.unwrap();
|
|
model.set_user_input(0, 2, 1, "25€".to_string()).unwrap();
|
|
|
|
// negatives
|
|
model
|
|
.set_user_input(0, 1, 2, "-123.348€".to_string())
|
|
.unwrap();
|
|
model.set_user_input(0, 2, 2, "-42€".to_string()).unwrap();
|
|
|
|
// with a space
|
|
model
|
|
.set_user_input(0, 1, 3, "101.348 €".to_string())
|
|
.unwrap();
|
|
model.set_user_input(0, 2, 3, "26 €".to_string()).unwrap();
|
|
|
|
model
|
|
.set_user_input(0, 1, 4, "-12.348 €".to_string())
|
|
.unwrap();
|
|
model.set_user_input(0, 2, 4, "-45 €".to_string()).unwrap();
|
|
|
|
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())
|
|
.unwrap();
|
|
model
|
|
.set_user_input(0, 2, 2, "=SUM(A1:A2)".to_string())
|
|
.unwrap();
|
|
model
|
|
.set_user_input(0, 3, 2, "=SUM(A1, A2, A3)".to_string())
|
|
.unwrap();
|
|
|
|
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())
|
|
.unwrap();
|
|
|
|
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())
|
|
.unwrap();
|
|
model.evaluate();
|
|
assert_eq!(model._get_text("A1"), "8/8/2028");
|
|
}
|