#![allow(clippy::unwrap_used)] use crate::constants::{DEFAULT_ROW_HEIGHT, LAST_COLUMN, LAST_ROW}; use crate::model::Model; use crate::test::util::new_empty_model; use crate::types::Col; #[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!( (DEFAULT_ROW_HEIGHT - 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!( (DEFAULT_ROW_HEIGHT - 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!( (DEFAULT_ROW_HEIGHT - 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!( (DEFAULT_ROW_HEIGHT - 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_column_width() { let mut model = new_empty_model(); let (sheet, column) = (0, 5); let normal_width = model.get_column_width(sheet, column).unwrap(); // Set the width of one column to 5 times the normal width assert!(model .set_column_width(sheet, column, normal_width * 5.0) .is_ok()); // delete it assert!(model.delete_columns(sheet, column, 1).is_ok()); // all the columns around have the expected width assert_eq!( model.get_column_width(sheet, column - 1).unwrap(), normal_width ); assert_eq!(model.get_column_width(sheet, column).unwrap(), normal_width); assert_eq!( model.get_column_width(sheet, column + 1).unwrap(), normal_width ); } #[test] // We set the style of columns 4 to 7 and delete column 4 // We check that columns 4 to 6 have the new style fn test_delete_first_column_width() { let mut model = new_empty_model(); model.workbook.worksheets[0].cols = vec![Col { min: 4, max: 7, width: 300.0, custom_width: true, style: None, }]; let (sheet, column) = (0, 4); assert!(model.delete_columns(sheet, column, 1).is_ok()); let cols = &model.workbook.worksheets[0].cols; assert_eq!(cols.len(), 1); assert_eq!( cols[0], Col { min: 4, max: 6, width: 300.0, custom_width: true, style: None } ); } #[test] // Delete the last column in the range fn test_delete_last_column_width() { let mut model = new_empty_model(); model.workbook.worksheets[0].cols = vec![Col { min: 4, max: 7, width: 300.0, custom_width: true, style: None, }]; let (sheet, column) = (0, 7); assert!(model.delete_columns(sheet, column, 1).is_ok()); let cols = &model.workbook.worksheets[0].cols; assert_eq!(cols.len(), 1); assert_eq!( cols[0], Col { min: 4, max: 6, width: 300.0, custom_width: true, style: None } ); } #[test] // Deletes columns at the end fn test_delete_last_few_columns_width() { let mut model = new_empty_model(); model.workbook.worksheets[0].cols = vec![Col { min: 4, max: 17, width: 300.0, custom_width: true, style: None, }]; let (sheet, column) = (0, 13); assert!(model.delete_columns(sheet, column, 10).is_ok()); let cols = &model.workbook.worksheets[0].cols; assert_eq!(cols.len(), 1); assert_eq!( cols[0], Col { min: 4, max: 12, width: 300.0, custom_width: true, style: None } ); } #[test] fn test_delete_columns_non_overlapping_left() { let mut model = new_empty_model(); model.workbook.worksheets[0].cols = vec![Col { min: 10, max: 17, width: 300.0, custom_width: true, style: None, }]; let (sheet, column) = (0, 3); assert!(model.delete_columns(sheet, column, 4).is_ok()); let cols = &model.workbook.worksheets[0].cols; assert_eq!(cols.len(), 1); assert_eq!( cols[0], Col { min: 6, max: 13, width: 300.0, custom_width: true, style: None } ); } #[test] fn test_delete_columns_overlapping_left() { let mut model = new_empty_model(); model.workbook.worksheets[0].cols = vec![Col { min: 10, max: 20, width: 300.0, custom_width: true, style: None, }]; let (sheet, column) = (0, 8); assert!(model.delete_columns(sheet, column, 4).is_ok()); let cols = &model.workbook.worksheets[0].cols; assert_eq!(cols.len(), 1); assert_eq!( cols[0], Col { min: 8, max: 16, width: 300.0, custom_width: true, style: None } ); } #[test] fn test_delete_columns_non_overlapping_right() { let mut model = new_empty_model(); model.workbook.worksheets[0].cols = vec![Col { min: 10, max: 17, width: 300.0, custom_width: true, style: None, }]; let (sheet, column) = (0, 23); assert!(model.delete_columns(sheet, column, 4).is_ok()); let cols = &model.workbook.worksheets[0].cols; assert_eq!(cols.len(), 1); assert_eq!( cols[0], Col { min: 10, max: 17, width: 300.0, custom_width: true, style: None } ); } #[test] // deletes some columns in the middle of the range fn test_delete_middle_column_width() { let mut model = new_empty_model(); // styled columns [4, 17] model.workbook.worksheets[0].cols = vec![Col { min: 4, max: 17, width: 300.0, custom_width: true, style: None, }]; // deletes columns 10, 11, 12 let (sheet, column) = (0, 10); assert!(model.delete_columns(sheet, column, 3).is_ok()); let cols = &model.workbook.worksheets[0].cols; assert_eq!(cols.len(), 1); assert_eq!( cols[0], Col { min: 4, max: 14, width: 300.0, custom_width: true, style: None } ); } #[test] // the range is inside the deleted columns fn delete_range_in_columns() { let mut model = new_empty_model(); // styled columns [6, 10] model.workbook.worksheets[0].cols = vec![Col { min: 6, max: 10, width: 300.0, custom_width: true, style: None, }]; // deletes columns [4, 17] let (sheet, column) = (0, 4); assert!(model.delete_columns(sheet, column, 8).is_ok()); let cols = &model.workbook.worksheets[0].cols; assert_eq!(cols.len(), 0); } #[test] fn test_delete_columns_error() { let mut model = new_empty_model(); let (sheet, column) = (0, 5); assert!(model.delete_columns(sheet, column, -1).is_err()); assert!(model.delete_columns(sheet, column, 0).is_err()); assert!(model.delete_columns(sheet, column, 1).is_ok()); } #[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)"); // Data moved as well assert_eq!(model._get_text("G1"), "1"); assert_eq!(model._get_text("H1"), "3"); } #[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()); } #[test] fn test_move_row_down() { let mut model = new_empty_model(); populate_table(&mut model); // Formulas referencing rows 3 and 4 model._set("E3", "=G3"); model._set("E4", "=G4"); model._set("E5", "=SUM(G3:J3)"); model._set("E6", "=SUM(G3:G3)"); model._set("E7", "=SUM(G4:G4)"); model.evaluate(); // Move row 3 down by one position let result = model.move_row_action(0, 3, 1); assert!(result.is_ok()); model.evaluate(); assert_eq!(model._get_formula("E3"), "=G3"); assert_eq!(model._get_formula("E4"), "=G4"); assert_eq!(model._get_formula("E5"), "=SUM(G4:J4)"); assert_eq!(model._get_formula("E6"), "=SUM(G4:G4)"); assert_eq!(model._get_formula("E7"), "=SUM(G3:G3)"); // Data moved as well assert_eq!(model._get_text("G4"), "-2"); assert_eq!(model._get_text("G3"), ""); } #[test] fn test_move_row_up() { let mut model = new_empty_model(); populate_table(&mut model); // Formulas referencing rows 4 and 5 model._set("E4", "=G4"); model._set("E5", "=G5"); model._set("E6", "=SUM(G4:J4)"); model._set("E7", "=SUM(G4:G4)"); model._set("E8", "=SUM(G5:G5)"); model.evaluate(); // Move row 5 up by one position let result = model.move_row_action(0, 5, -1); assert!(result.is_ok()); model.evaluate(); assert_eq!(model._get_formula("E4"), "=G4"); assert_eq!(model._get_formula("E5"), "=G5"); assert_eq!(model._get_formula("E6"), "=SUM(G5:J5)"); assert_eq!(model._get_formula("E7"), "=SUM(G5:G5)"); assert_eq!(model._get_formula("E8"), "=SUM(G4:G4)"); // Data moved as well assert_eq!(model._get_text("G4"), ""); assert_eq!(model._get_text("G5"), ""); } #[test] fn test_move_row_error() { let mut model = new_empty_model(); model.evaluate(); let result = model.move_row_action(0, 7, -10); assert!(result.is_err()); let result = model.move_row_action(0, -7, 20); assert!(result.is_err()); let result = model.move_row_action(0, LAST_ROW, 1); assert!(result.is_err()); let result = model.move_row_action(0, LAST_ROW + 1, -10); assert!(result.is_err()); // This works let result = model.move_row_action(0, LAST_ROW, -1); assert!(result.is_ok()); } #[test] fn test_move_row_down_absolute_refs() { let mut model = new_empty_model(); populate_table(&mut model); // Absolute references model._set("E3", "=$G$3"); model._set("E4", "=$G$4"); model._set("E5", "=SUM($G$3:$J$3)"); model._set("E6", "=SUM($G$3:$G$3)"); model._set("E7", "=SUM($G$4:$G$4)"); model.evaluate(); assert!(model.move_row_action(0, 3, 1).is_ok()); model.evaluate(); assert_eq!(model._get_formula("E3"), "=$G$3"); assert_eq!(model._get_formula("E4"), "=$G$4"); assert_eq!(model._get_formula("E5"), "=SUM($G$4:$J$4)"); assert_eq!(model._get_formula("E6"), "=SUM($G$4:$G$4)"); assert_eq!(model._get_formula("E7"), "=SUM($G$3:$G$3)"); } #[test] fn test_move_column_right_absolute_refs() { let mut model = new_empty_model(); populate_table(&mut model); // Absolute references model._set("E3", "=$G$3"); model._set("E4", "=$H$3"); model._set("E5", "=SUM($G$3:$J$7)"); model._set("E6", "=SUM($G$3:$G$7)"); model._set("E7", "=SUM($H$3:$H$7)"); model.evaluate(); assert!(model.move_column_action(0, 7, 1).is_ok()); model.evaluate(); assert_eq!(model._get_formula("E3"), "=$H$3"); assert_eq!(model._get_formula("E4"), "=$G$3"); assert_eq!(model._get_formula("E5"), "=SUM($H$3:$J$7)"); assert_eq!(model._get_formula("E6"), "=SUM($H$3:$H$7)"); assert_eq!(model._get_formula("E7"), "=SUM($G$3:$G$7)"); } #[test] fn test_move_row_down_mixed_refs() { let mut model = new_empty_model(); populate_table(&mut model); model._set("E3", "=$G3"); // absolute col, relative row model._set("E4", "=$G4"); model._set("E5", "=SUM($G3:$J3)"); model._set("E6", "=SUM($G3:$G3)"); model._set("E7", "=SUM($G4:$G4)"); model._set("F3", "=H$3"); // relative col, absolute row model._set("F4", "=G$3"); model.evaluate(); assert!(model.move_row_action(0, 3, 1).is_ok()); model.evaluate(); assert_eq!(model._get_formula("E3"), "=$G3"); assert_eq!(model._get_formula("E4"), "=$G4"); assert_eq!(model._get_formula("E5"), "=SUM($G4:$J4)"); assert_eq!(model._get_formula("E6"), "=SUM($G4:$G4)"); assert_eq!(model._get_formula("E7"), "=SUM($G3:$G3)"); assert_eq!(model._get_formula("F3"), "=G$4"); assert_eq!(model._get_formula("F4"), "=H$4"); } #[test] fn test_move_column_right_mixed_refs() { 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._set("F3", "=H$3"); model._set("F4", "=H$3"); model.evaluate(); assert!(model.move_column_action(0, 7, 1).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)"); assert_eq!(model._get_formula("F3"), "=G$3"); assert_eq!(model._get_formula("F4"), "=G$3"); } #[test] fn test_move_row_height() { let mut model = new_empty_model(); let sheet = 0; let custom_height = DEFAULT_ROW_HEIGHT * 2.0; // Set a custom height for row 3 model .workbook .worksheet_mut(sheet) .unwrap() .set_row_height(3, custom_height) .unwrap(); // Record the original height of row 4 (should be the default) let original_row4_height = model.get_row_height(sheet, 4).unwrap(); // Move row 3 down by one position assert!(model.move_row_action(sheet, 3, 1).is_ok()); // The custom height should now be on row 4 assert_eq!(model.get_row_height(sheet, 4), Ok(custom_height)); // Row 3 should now have the previous height of row 4 assert_eq!(model.get_row_height(sheet, 3), Ok(original_row4_height)); } /// Moving a row down by two positions should shift formulas on intermediate /// rows by only one (the row that gets skipped), not by the full delta ‒ this /// guards against the regression fixed in the RowMove displacement logic. #[test] fn test_row_move_down_two_updates_intermediate_refs_by_one() { let mut model = new_empty_model(); populate_table(&mut model); // Set up formulas to verify intermediate rows shift by 1 (not full delta). model._set("E3", "=G3"); // target row model._set("E4", "=G4"); // intermediate row model._set("E5", "=SUM(G3:J3)"); model.evaluate(); // Move row 3 down by two positions (row 3 -> row 5) assert!(model.move_row_action(0, 3, 2).is_ok()); model.evaluate(); // Assert that references for the moved row and intermediate row are correct. assert_eq!(model._get_formula("E3"), "=G3"); assert_eq!(model._get_formula("E5"), "=G5"); assert_eq!(model._get_formula("E4"), "=SUM(G5:J5)"); } /// Moving a column right by two positions should shift formulas on /// intermediate columns by only one, ensuring the ColumnMove displacement /// logic handles multi-position moves correctly. #[test] fn test_column_move_right_two_updates_intermediate_refs_by_one() { let mut model = new_empty_model(); populate_table(&mut model); // Set up formulas to verify intermediate columns shift by 1 (not full delta). model._set("E3", "=$G3"); // target column model._set("E4", "=$H3"); // intermediate column model._set("E5", "=SUM($G3:$J7)"); model.evaluate(); // Move column G (7) right by two positions (G -> I) assert!(model.move_column_action(0, 7, 2).is_ok()); model.evaluate(); // Assert that references for moved and intermediate columns are correct. assert_eq!(model._get_formula("E3"), "=$I3"); assert_eq!(model._get_formula("E4"), "=$G3"); assert_eq!(model._get_formula("E5"), "=SUM($I3:$J7)"); } // 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