formatting commit addressing testcase failures adding one more scenario to case adding one more scenario to case Adding update and unmerge functions for merge cell handling adding one more case to testcase adding testcases to base code adding testcase for import/export adding documentation to some of the PUB function fixing warnings and test warnings adding merge cell part cell update restriction to public sytle set fns addressing reviwers comment : Changed Mergedcell structure and its side effercts reverting it back to non pub. renaming update_merge_cells to just merge_cells in model renaming *unmerge_merged_cell* to *unmerge_cells* addressing other reviewer's comment + cosmetica naming adjustments cosmetic changes
567 lines
18 KiB
Rust
567 lines
18 KiB
Rust
#![allow(clippy::unwrap_used)]
|
|
#![allow(clippy::panic)]
|
|
|
|
use std::io::Read;
|
|
use std::{env, fs, io};
|
|
use uuid::Uuid;
|
|
|
|
use ironcalc::compare::{test_file, test_load_and_saving};
|
|
use ironcalc::export::save_to_xlsx;
|
|
use ironcalc::import::{load_from_icalc, load_from_xlsx, load_from_xlsx_bytes};
|
|
use ironcalc_base::types::{HorizontalAlignment, VerticalAlignment};
|
|
use ironcalc_base::Model;
|
|
|
|
// This is a functional test.
|
|
// We check that the output of example.xlsx is what we expect.
|
|
#[test]
|
|
fn test_example() {
|
|
let model = load_from_xlsx("tests/example.xlsx", "en", "UTC").unwrap();
|
|
// We should use the API once it is in place
|
|
let workbook = model.workbook;
|
|
let ws = &workbook.worksheets;
|
|
let expected_names = vec![
|
|
"Sheet1".to_string(),
|
|
"Second".to_string(),
|
|
"Sheet4".to_string(),
|
|
"shared".to_string(),
|
|
"Table".to_string(),
|
|
"Sheet2".to_string(),
|
|
"Created fourth".to_string(),
|
|
"Frozen".to_string(),
|
|
"Split".to_string(),
|
|
"Hidden".to_string(),
|
|
];
|
|
let names: Vec<String> = ws.iter().map(|s| s.name.clone()).collect();
|
|
|
|
// One is not not imported and one is hidden
|
|
assert_eq!(expected_names, names);
|
|
|
|
assert_eq!(workbook.views[&0].sheet, 7);
|
|
|
|
// Test selection:
|
|
// First sheet (Sheet1)
|
|
// E13 and E13:N20
|
|
assert_eq!(ws[0].frozen_rows, 0);
|
|
assert_eq!(ws[0].frozen_columns, 0);
|
|
assert_eq!(ws[0].views[&0].row, 13);
|
|
assert_eq!(ws[0].views[&0].column, 5);
|
|
assert_eq!(ws[0].views[&0].range, [13, 5, 20, 14]);
|
|
|
|
let model2 = load_from_icalc("tests/example.ic").unwrap();
|
|
let s = bitcode::encode(&model2.workbook);
|
|
assert_eq!(workbook, model2.workbook, "{:?}", s);
|
|
}
|
|
|
|
#[test]
|
|
fn test_load_from_xlsx_bytes() {
|
|
let file_path = std::path::Path::new("tests/example.xlsx");
|
|
let mut file = fs::File::open(file_path).unwrap();
|
|
let mut bytes = Vec::new();
|
|
file.read_to_end(&mut bytes).unwrap();
|
|
let workbook = load_from_xlsx_bytes(&bytes, "home", "en", "UTC").unwrap();
|
|
assert_eq!(workbook.views[&0].sheet, 7);
|
|
}
|
|
|
|
#[test]
|
|
fn no_grid() {
|
|
let model = load_from_xlsx("tests/NoGrid.xlsx", "en", "UTC").unwrap();
|
|
{
|
|
let workbook = &model.workbook;
|
|
let ws = &workbook.worksheets;
|
|
|
|
// NoGrid does not show grid lines
|
|
let no_grid_sheet = &ws[0];
|
|
assert_eq!(no_grid_sheet.name, "NoGrid".to_string());
|
|
assert!(!no_grid_sheet.show_grid_lines);
|
|
|
|
let sheet2 = &ws[1];
|
|
assert_eq!(no_grid_sheet.name, "NoGrid".to_string());
|
|
assert!(sheet2.show_grid_lines);
|
|
|
|
let no_grid_no_headers_sheet = &ws[2];
|
|
assert_eq!(no_grid_sheet.name, "NoGrid".to_string());
|
|
// There is also no headers
|
|
assert!(!no_grid_no_headers_sheet.show_grid_lines);
|
|
}
|
|
{
|
|
// save it and check again
|
|
let temp_file_name = "temp_file_no_grid.xlsx";
|
|
save_to_xlsx(&model, temp_file_name).unwrap();
|
|
let model = load_from_xlsx(temp_file_name, "en", "UTC").unwrap();
|
|
let workbook = &model.workbook;
|
|
let ws = &workbook.worksheets;
|
|
|
|
// NoGrid does not show grid lines
|
|
let no_grid_sheet = &ws[0];
|
|
assert_eq!(no_grid_sheet.name, "NoGrid".to_string());
|
|
assert!(!no_grid_sheet.show_grid_lines);
|
|
|
|
let sheet2 = &ws[1];
|
|
assert_eq!(no_grid_sheet.name, "NoGrid".to_string());
|
|
assert!(sheet2.show_grid_lines);
|
|
|
|
let no_grid_no_headers_sheet = &ws[2];
|
|
assert_eq!(no_grid_sheet.name, "NoGrid".to_string());
|
|
// There is also no headers
|
|
assert!(!no_grid_no_headers_sheet.show_grid_lines);
|
|
fs::remove_file(temp_file_name).unwrap();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_save_to_xlsx() {
|
|
let mut model = load_from_xlsx("tests/example.xlsx", "en", "UTC").unwrap();
|
|
model.evaluate();
|
|
let temp_file_name = "temp_file_example.xlsx";
|
|
// test can safe
|
|
save_to_xlsx(&model, temp_file_name).unwrap();
|
|
// test can open
|
|
let model = load_from_xlsx(temp_file_name, "en", "UTC").unwrap();
|
|
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");
|
|
|
|
let workbook = model.workbook;
|
|
let ws = &workbook.worksheets;
|
|
|
|
assert_eq!(workbook.views[&0].sheet, 7);
|
|
|
|
// Test selection:
|
|
// First sheet (Sheet1)
|
|
// E13 and E13:N20
|
|
assert_eq!(ws[0].frozen_rows, 0);
|
|
assert_eq!(ws[0].frozen_columns, 0);
|
|
assert_eq!(ws[0].views[&0].row, 13);
|
|
assert_eq!(ws[0].views[&0].column, 5);
|
|
assert_eq!(ws[0].views[&0].range, [13, 5, 20, 14]);
|
|
// TODO: can we show it is the 'same' model?
|
|
fs::remove_file(temp_file_name).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_freeze() {
|
|
// freeze has 3 frozen columns and 2 frozen rows
|
|
let model = load_from_xlsx("tests/freeze.xlsx", "en", "UTC")
|
|
.unwrap()
|
|
.workbook;
|
|
assert_eq!(model.worksheets[0].frozen_rows, 2);
|
|
assert_eq!(model.worksheets[0].frozen_columns, 3);
|
|
}
|
|
|
|
#[test]
|
|
fn test_split() {
|
|
// We test that a workbook with split panes do not produce frozen rows and columns
|
|
let model = load_from_xlsx("tests/split.xlsx", "en", "UTC")
|
|
.unwrap()
|
|
.workbook;
|
|
assert_eq!(model.worksheets[0].frozen_rows, 0);
|
|
assert_eq!(model.worksheets[0].frozen_columns, 0);
|
|
}
|
|
|
|
fn test_model_has_correct_styles(model: &Model) {
|
|
// A1 is bold
|
|
let style_a1 = model.get_style_for_cell(0, 1, 1).unwrap();
|
|
assert!(style_a1.font.b);
|
|
assert!(!style_a1.font.i);
|
|
assert!(!style_a1.font.u);
|
|
|
|
// B1 is Italics
|
|
let style_b1 = model.get_style_for_cell(0, 1, 2).unwrap();
|
|
assert!(style_b1.font.i);
|
|
assert!(!style_b1.font.b);
|
|
assert!(!style_b1.font.u);
|
|
|
|
// C1 Underlined
|
|
let style_c1 = model.get_style_for_cell(0, 1, 3).unwrap();
|
|
assert!(style_c1.font.u);
|
|
assert!(!style_c1.font.b);
|
|
assert!(!style_c1.font.i);
|
|
|
|
// D1 Bold and Italics
|
|
let style_d1 = model.get_style_for_cell(0, 1, 4).unwrap();
|
|
assert!(style_d1.font.b);
|
|
assert!(style_d1.font.i);
|
|
assert!(!style_d1.font.u);
|
|
|
|
// E1 Bold, italics and underlined
|
|
let style_e1 = model.get_style_for_cell(0, 1, 5).unwrap();
|
|
assert!(style_e1.font.b);
|
|
assert!(style_e1.font.i);
|
|
assert!(style_e1.font.u);
|
|
assert!(!style_e1.font.strike);
|
|
|
|
// F1 strikethrough
|
|
let style_f1 = model.get_style_for_cell(0, 1, 6).unwrap();
|
|
assert!(style_f1.font.strike);
|
|
|
|
// G1 Double underlined just get simple underlined
|
|
let style_g1 = model.get_style_for_cell(0, 1, 7).unwrap();
|
|
assert!(style_g1.font.u);
|
|
|
|
let height_row_3 = model.workbook.worksheet(0).unwrap().row_height(3).unwrap();
|
|
assert_eq!(height_row_3, 136.0);
|
|
|
|
let height_row_5 = model.workbook.worksheet(0).unwrap().row_height(5).unwrap();
|
|
assert_eq!(height_row_5, 62.0);
|
|
|
|
// Second sheet has alignment
|
|
// Horizontal
|
|
let alignment = model.get_style_for_cell(1, 2, 1).unwrap().alignment;
|
|
assert_eq!(alignment, None);
|
|
|
|
let alignment = model
|
|
.get_style_for_cell(1, 3, 1)
|
|
.unwrap()
|
|
.alignment
|
|
.unwrap();
|
|
assert_eq!(alignment.horizontal, HorizontalAlignment::Left);
|
|
|
|
let alignment = model
|
|
.get_style_for_cell(1, 4, 1)
|
|
.unwrap()
|
|
.alignment
|
|
.unwrap();
|
|
assert_eq!(alignment.horizontal, HorizontalAlignment::Distributed);
|
|
|
|
let alignment = model
|
|
.get_style_for_cell(1, 5, 1)
|
|
.unwrap()
|
|
.alignment
|
|
.unwrap();
|
|
assert_eq!(alignment.horizontal, HorizontalAlignment::Right);
|
|
|
|
let alignment = model
|
|
.get_style_for_cell(1, 6, 1)
|
|
.unwrap()
|
|
.alignment
|
|
.unwrap();
|
|
assert_eq!(alignment.horizontal, HorizontalAlignment::Center);
|
|
|
|
let alignment = model
|
|
.get_style_for_cell(1, 7, 1)
|
|
.unwrap()
|
|
.alignment
|
|
.unwrap();
|
|
assert_eq!(alignment.horizontal, HorizontalAlignment::Fill);
|
|
|
|
let alignment = model
|
|
.get_style_for_cell(1, 8, 1)
|
|
.unwrap()
|
|
.alignment
|
|
.unwrap();
|
|
assert_eq!(alignment.horizontal, HorizontalAlignment::Justify);
|
|
|
|
// Vertical
|
|
let alignment = model.get_style_for_cell(1, 2, 2).unwrap().alignment;
|
|
assert_eq!(alignment, None);
|
|
|
|
let alignment = model.get_style_for_cell(1, 3, 2).unwrap().alignment;
|
|
assert_eq!(alignment, None);
|
|
|
|
let alignment = model
|
|
.get_style_for_cell(1, 4, 2)
|
|
.unwrap()
|
|
.alignment
|
|
.unwrap();
|
|
assert_eq!(alignment.vertical, VerticalAlignment::Top);
|
|
|
|
let alignment = model
|
|
.get_style_for_cell(1, 5, 2)
|
|
.unwrap()
|
|
.alignment
|
|
.unwrap();
|
|
assert_eq!(alignment.vertical, VerticalAlignment::Center);
|
|
|
|
let alignment = model
|
|
.get_style_for_cell(1, 6, 2)
|
|
.unwrap()
|
|
.alignment
|
|
.unwrap();
|
|
assert_eq!(alignment.vertical, VerticalAlignment::Justify);
|
|
|
|
let alignment = model
|
|
.get_style_for_cell(1, 7, 2)
|
|
.unwrap()
|
|
.alignment
|
|
.unwrap();
|
|
assert_eq!(alignment.vertical, VerticalAlignment::Distributed);
|
|
}
|
|
|
|
#[test]
|
|
fn test_simple_text() {
|
|
let model = load_from_xlsx("tests/basic_text.xlsx", "en", "UTC").unwrap();
|
|
|
|
test_model_has_correct_styles(&model);
|
|
|
|
let temp_file_name = "temp_file_test_named_styles.xlsx";
|
|
save_to_xlsx(&model, temp_file_name).unwrap();
|
|
|
|
let model = load_from_xlsx(temp_file_name, "en", "UTC").unwrap();
|
|
fs::remove_file(temp_file_name).unwrap();
|
|
test_model_has_correct_styles(&model);
|
|
}
|
|
|
|
#[test]
|
|
fn test_defined_names_casing() {
|
|
let test_file_path = "tests/calc_tests/defined_names_for_unit_test.xlsx";
|
|
let loaded_workbook = load_from_xlsx(test_file_path, "en", "UTC")
|
|
.unwrap()
|
|
.workbook;
|
|
let mut model = Model::from_bytes(&bitcode::encode(&loaded_workbook)).unwrap();
|
|
|
|
let (row, column) = (2, 13); // B13
|
|
let test_cases = [
|
|
("=named1", "11"),
|
|
("=NAMED1", "11"),
|
|
("=NaMeD1", "11"),
|
|
("=named2", "22"),
|
|
("=NAMED2", "22"),
|
|
("=NaMeD2", "22"),
|
|
("=named3", "33"),
|
|
("=NAMED3", "33"),
|
|
("=NaMeD3", "33"),
|
|
];
|
|
for (formula, expected_value) in test_cases {
|
|
model
|
|
.set_user_input(0, row, column, formula.to_string())
|
|
.unwrap();
|
|
model.evaluate();
|
|
assert_eq!(
|
|
model.get_formatted_cell_value(0, row, column).unwrap(),
|
|
expected_value
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_xlsx() {
|
|
let mut entries = fs::read_dir("tests/calc_tests/")
|
|
.unwrap()
|
|
.map(|res| res.map(|e| e.path()))
|
|
.collect::<Result<Vec<_>, io::Error>>()
|
|
.unwrap();
|
|
entries.sort();
|
|
let temp_folder = env::temp_dir();
|
|
let path = format!("{}", Uuid::new_v4());
|
|
let dir = temp_folder.join(path);
|
|
fs::create_dir(&dir).unwrap();
|
|
for file_path in entries {
|
|
let file_name_str = file_path.file_name().unwrap().to_str().unwrap();
|
|
let file_path_str = file_path.to_str().unwrap();
|
|
println!("Testing file: {}", file_path_str);
|
|
if file_name_str.ends_with(".xlsx") && !file_name_str.starts_with('~') {
|
|
if let Err(message) = test_file(file_path_str) {
|
|
println!("{}", message);
|
|
panic!("Model was evaluated inconsistently with XLSX data.")
|
|
}
|
|
assert!(test_load_and_saving(file_path_str, &dir).is_ok());
|
|
} else {
|
|
println!("skipping");
|
|
}
|
|
}
|
|
fs::remove_dir_all(&dir).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn no_export() {
|
|
let mut entries = fs::read_dir("tests/calc_test_no_export/")
|
|
.unwrap()
|
|
.map(|res| res.map(|e| e.path()))
|
|
.collect::<Result<Vec<_>, io::Error>>()
|
|
.unwrap();
|
|
entries.sort();
|
|
let temp_folder = env::temp_dir();
|
|
let path = format!("{}", Uuid::new_v4());
|
|
let dir = temp_folder.join(path);
|
|
fs::create_dir(&dir).unwrap();
|
|
for file_path in entries {
|
|
let file_name_str = file_path.file_name().unwrap().to_str().unwrap();
|
|
let file_path_str = file_path.to_str().unwrap();
|
|
println!("Testing file: {}", file_path_str);
|
|
if file_name_str.ends_with(".xlsx") && !file_name_str.starts_with('~') {
|
|
if let Err(message) = test_file(file_path_str) {
|
|
println!("{}", message);
|
|
panic!("Model was evaluated inconsistently with XLSX data.")
|
|
}
|
|
} else {
|
|
println!("skipping");
|
|
}
|
|
}
|
|
fs::remove_dir_all(&dir).unwrap();
|
|
}
|
|
|
|
// This test verifies whether exporting the merged cells functionality is happening properly or not.
|
|
// It first loads the Excel having the merged cell and exports it to another xlsx and verifies whether merged
|
|
// cell node is same in both of the xlsx file or not.
|
|
#[test]
|
|
fn test_exporting_merged_cells() {
|
|
let temp_file_name = "temp_file_test_export_merged_cells.xlsx";
|
|
let expected_merge_cell_ref = {
|
|
// loading the xlsx file containing merged cells
|
|
let example_file_name = "tests/example.xlsx";
|
|
let mut model = load_from_xlsx(example_file_name, "en", "UTC").unwrap();
|
|
let expected_merge_cell_ref = model
|
|
.workbook
|
|
.worksheets
|
|
.first()
|
|
.unwrap()
|
|
.merged_cells_list
|
|
.clone();
|
|
// exporting and saving it in another xlsx
|
|
model.evaluate();
|
|
save_to_xlsx(&model, temp_file_name).unwrap();
|
|
expected_merge_cell_ref
|
|
};
|
|
{
|
|
let mut temp_model = load_from_xlsx(temp_file_name, "en", "UTC").unwrap();
|
|
{
|
|
// loading the previous file back and verifying whether
|
|
// merged cells got exported properly or not
|
|
let got_merge_cell_ref = &temp_model
|
|
.workbook
|
|
.worksheets
|
|
.first()
|
|
.unwrap()
|
|
.merged_cells_list
|
|
.clone();
|
|
assert_eq!(expected_merge_cell_ref, *got_merge_cell_ref);
|
|
fs::remove_file(temp_file_name).unwrap();
|
|
}
|
|
{
|
|
// this block is to verify that if there are no
|
|
// merged cells, exported xml should not have the
|
|
// <mergeCells/> xml node
|
|
temp_model
|
|
.workbook
|
|
.worksheets
|
|
.get_mut(0)
|
|
.unwrap()
|
|
.merged_cells_list
|
|
.clear();
|
|
|
|
save_to_xlsx(&temp_model, temp_file_name).unwrap();
|
|
let temp_model2 = load_from_xlsx(temp_file_name, "en", "UTC").unwrap();
|
|
let got_merge_cell_ref_cnt = &temp_model2
|
|
.workbook
|
|
.worksheets
|
|
.first()
|
|
.unwrap()
|
|
.merged_cells_list
|
|
.len();
|
|
assert!(*got_merge_cell_ref_cnt == 0);
|
|
}
|
|
}
|
|
|
|
fs::remove_file(temp_file_name).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_documentation_xlsx() {
|
|
let mut entries = fs::read_dir("tests/docs/")
|
|
.unwrap()
|
|
.map(|res| res.map(|e| e.path()))
|
|
.collect::<Result<Vec<_>, io::Error>>()
|
|
.unwrap();
|
|
entries.sort();
|
|
// We can't test volatiles
|
|
let skip = ["DATE.xlsx", "DAY.xlsx", "MONTH.xlsx", "YEAR.xlsx"];
|
|
let skip = skip.map(|s| format!("tests/docs/{s}"));
|
|
println!("{:?}", skip);
|
|
// dumb counter to make sure we are actually testing the files
|
|
assert_eq!(entries.len(), 7);
|
|
let temp_folder = env::temp_dir();
|
|
let path = format!("{}", Uuid::new_v4());
|
|
let dir = temp_folder.join(path);
|
|
fs::create_dir(&dir).unwrap();
|
|
for file_path in entries {
|
|
let file_name_str = file_path.file_name().unwrap().to_str().unwrap();
|
|
let file_path_str = file_path.to_str().unwrap();
|
|
if skip.contains(&file_path_str.to_string()) {
|
|
println!("Skipping file: {}", file_path_str);
|
|
continue;
|
|
}
|
|
println!("Testing file: {}", file_path_str);
|
|
if file_name_str.ends_with(".xlsx") && !file_name_str.starts_with('~') {
|
|
if let Err(message) = test_file(file_path_str) {
|
|
println!("{}", message);
|
|
panic!("Model was evaluated inconsistently with XLSX data.")
|
|
}
|
|
assert!(test_load_and_saving(file_path_str, &dir).is_ok());
|
|
} else {
|
|
println!("skipping");
|
|
}
|
|
}
|
|
fs::remove_dir_all(&dir).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_merge_cell_import_export_behaviors() {
|
|
// loading the xlsx file containing merged cells
|
|
let example_file_name = "tests/Merged_cells.xlsx";
|
|
let mut model = load_from_xlsx(example_file_name, "en", "UTC").unwrap();
|
|
|
|
// Case1 : To check whether Merge cells structures got imported properly or not
|
|
let imported_merge_cell_vec = model.workbook.worksheet(0).unwrap().get_merged_cells_list();
|
|
|
|
assert_eq!(imported_merge_cell_vec.len(), 5);
|
|
let range_refs_of_merge_cell: Vec<String> = imported_merge_cell_vec
|
|
.iter()
|
|
.map(|cell| cell.get_merged_cells_str_ref().unwrap())
|
|
.collect();
|
|
assert_eq!(
|
|
range_refs_of_merge_cell,
|
|
[
|
|
"C1:D3".to_string(),
|
|
"A1:B4".to_string(),
|
|
"G1:H7".to_string(),
|
|
"D8:E9".to_string(),
|
|
"D4:F6".to_string()
|
|
]
|
|
);
|
|
|
|
// Create one More Merge cell which Overlaps with 3 More
|
|
model.merge_cells(0, "A1:D5").unwrap();
|
|
model
|
|
.set_user_input(0, 1, 1, "New overlapped Merge cell".to_string())
|
|
.unwrap();
|
|
|
|
let mut style = model.get_style_for_cell(0, 1, 1).unwrap();
|
|
style.font.b = true;
|
|
assert_eq!(
|
|
model.workbook.styles.create_named_style("bold", &style),
|
|
Ok(())
|
|
);
|
|
|
|
model.set_cell_style_by_name(0, 1, 1, "bold").unwrap();
|
|
|
|
// Lets export to different Excell
|
|
let exported_merge_cell_xlsx = "temporary_exported_mergecells.xlsx";
|
|
save_to_xlsx(&model, exported_merge_cell_xlsx).unwrap();
|
|
|
|
{
|
|
let temp_model = load_from_xlsx(exported_merge_cell_xlsx, "en", "UTC").unwrap();
|
|
// Loading the exported sheet back and verifying whether it got exported properly or not
|
|
let imported_merge_cell_vec = temp_model
|
|
.workbook
|
|
.worksheet(0)
|
|
.unwrap()
|
|
.get_merged_cells_list();
|
|
|
|
assert_eq!(imported_merge_cell_vec.len(), 3);
|
|
let range_refs_of_merge_cell: Vec<String> = imported_merge_cell_vec
|
|
.iter()
|
|
.map(|cell| cell.get_merged_cells_str_ref().unwrap())
|
|
.collect();
|
|
assert_eq!(
|
|
range_refs_of_merge_cell,
|
|
[
|
|
"G1:H7".to_string(),
|
|
"D8:E9".to_string(),
|
|
"A1:D5".to_string()
|
|
]
|
|
);
|
|
}
|
|
fs::remove_file(exported_merge_cell_xlsx).unwrap();
|
|
}
|