//! Integration tests for the adaptive view sync flow. //! //! These tests exercise the full serialize→save→load→verify cycle //! that backs the Desktop↔Mobile spreadsheet state sync, using only //! persistence and workbook functions (no makepad widget runtime). use spreadsheet_engine::data::SpreadsheetData; use spreadsheet_engine::persistence::{load_saved_spreadsheet_state, save_spreadsheet_state}; use spreadsheet_engine::sheet::Sheet; use spreadsheet_engine::workbook::{deserialize_workbook, serialize_workbook}; /// Helper: build a 3-sheet workbook with distinct cell data per sheet. fn make_3_sheet_workbook() -> Vec { let mut budget = SpreadsheetData::default(); budget.set_cell(0, 0, "100"); budget.set_cell(1, 0, "=A1+50"); budget.set_cell(0, 1, "Rent"); let mut q3 = SpreadsheetData::default(); q3.set_cell(0, 0, "Revenue"); q3.set_cell(1, 0, "50000"); q3.set_cell(2, 0, "Costs"); q3.set_cell(3, 0, "=B1-B2"); let mut roi = SpreadsheetData::default(); roi.set_cell(0, 0, "Investment"); roi.set_cell(0, 1, "10000"); roi.set_cell(1, 0, "Return"); roi.set_cell(1, 1, "15000"); vec![ Sheet::new("Budget", budget), Sheet::new("Q3 Projections", q3), Sheet::new("ROI Analysis", roi), ] } // ── Full sync round-trip tests ───────────────────────────────── /// Desktop→Mobile→Desktop: data survives a full round-trip. #[test] fn full_sync_round_trip_preserves_all_sheets() { // 1. Desktop creates a workbook with data on all 3 sheets let sheets = make_3_sheet_workbook(); let saved_desktop = serialize_workbook(&sheets, 1); // 2. Mobile loads the workbook, reads the active sheet (idx=1) let (mut mobile_sheets, mobile_active) = deserialize_workbook(&saved_desktop).unwrap(); assert_eq!(mobile_active, 1); assert_eq!(mobile_sheets.len(), 3); assert_eq!(mobile_sheets[1].name, "Q3 Projections"); assert_eq!(mobile_sheets[1].data.get_raw(0, 0), "Revenue"); // 3. Mobile edits the active sheet mobile_sheets[mobile_active] .data .set_cell(5, 0, "mobile_edit"); let saved_mobile = serialize_workbook(&mobile_sheets, mobile_active); // 4. Desktop reloads let (restored, restored_active) = deserialize_workbook(&saved_mobile).unwrap(); assert_eq!(restored.len(), 3); assert_eq!(restored_active, 1); assert_eq!(restored[0].name, "Budget"); assert_eq!(restored[1].name, "Q3 Projections"); assert_eq!(restored[2].name, "ROI Analysis"); assert_eq!(restored[1].data.get_raw(5, 0), "mobile_edit"); // Other sheet data intact assert_eq!(restored[0].data.get_raw(0, 0), "100"); assert_eq!(restored[2].data.get_raw(1, 1), "15000"); } /// Multiple switches back and forth accumulate edits correctly. #[test] fn multiple_switches_accumulate_edits() { let sheets = make_3_sheet_workbook(); let mut current = serialize_workbook(&sheets, 0); for i in 0..5 { // Load let (mut loaded, active) = deserialize_workbook(¤t).unwrap(); // Edit active sheet loaded[active].data.set_cell(0, 0, &format!("edit_{}", i)); // Switch to next sheet let next_active = (active + 1) % loaded.len(); current = serialize_workbook(&loaded, next_active); } // Final load: verify all edits landed on correct sheets let (final_loaded, final_active) = deserialize_workbook(¤t).unwrap(); assert_eq!(final_loaded.len(), 3); // After 5 switches starting at 0: 0→1→2→0→1→2, active=2 assert_eq!(final_active, 2); // Sheet 0 was last written at i=3 (edit_3) assert_eq!(final_loaded[0].data.get_raw(0, 0), "edit_3"); // Sheet 1 was last written at i=4 (edit_4) assert_eq!(final_loaded[1].data.get_raw(0, 0), "edit_4"); // Sheet 2 was last written at i=2 (edit_2) assert_eq!(final_loaded[2].data.get_raw(0, 0), "edit_2"); } /// Formula computed values update correctly after round-trip. #[test] fn formula_recalculates_after_sync() { let mut data = SpreadsheetData::default(); data.set_cell(0, 0, "10"); data.set_cell(1, 0, "=A1*3"); // should compute 30 let sheets = vec![Sheet::new("Formulas", data)]; let saved = serialize_workbook(&sheets, 0); let (loaded, _) = deserialize_workbook(&saved).unwrap(); assert_eq!(loaded[0].data.get_display_value(1, 0), "30"); // Edit A1 and verify formula updates let mut sheets2 = loaded; sheets2[0].data.set_cell(0, 0, "25"); let saved2 = serialize_workbook(&sheets2, 0); let (loaded2, _) = deserialize_workbook(&saved2).unwrap(); assert_eq!(loaded2[0].data.get_display_value(1, 0), "75"); } // ── Disk persistence tests ───────────────────────────────────── /// Write to disk, read back, verify workbook integrity. #[test] fn disk_persistence_round_trip() { let sheets = make_3_sheet_workbook(); let serialized = serialize_workbook(&sheets, 2); // Save to disk save_spreadsheet_state(&serialized).expect("save failed"); // Load from disk let loaded = load_saved_spreadsheet_state().expect("load returned None"); let (restored, active) = deserialize_workbook(&loaded).unwrap(); assert_eq!(restored.len(), 3); assert_eq!(active, 2); assert_eq!(restored[0].name, "Budget"); assert_eq!(restored[1].name, "Q3 Projections"); assert_eq!(restored[2].name, "ROI Analysis"); assert_eq!(restored[0].data.get_raw(0, 0), "100"); assert_eq!(restored[1].data.get_raw(0, 0), "Revenue"); assert_eq!(restored[2].data.get_raw(0, 0), "Investment"); } /// Simulate Desktop→Mobile: save full workbook, then mobile reads it. /// Uses in-memory serialization (no disk) to avoid parallel test interference. #[test] fn simulate_desktop_to_mobile_sync() { // Desktop saves let sheets = make_3_sheet_workbook(); let saved = serialize_workbook(&sheets, 1); // Mobile reads from the serialized string (same as reading from disk) let (mobile_sheets, mobile_active) = deserialize_workbook(&saved).unwrap(); assert_eq!(mobile_active, 1); assert_eq!(mobile_sheets.len(), 3); // Mobile can access the active sheet's data let active_sheet = &mobile_sheets[mobile_active]; assert_eq!(active_sheet.name, "Q3 Projections"); assert_eq!(active_sheet.data.get_raw(0, 0), "Revenue"); assert_eq!(active_sheet.data.get_raw(1, 0), "50000"); } /// Simulate Mobile→Desktop: mobile edits, writes back, desktop reloads. /// Uses in-memory serialization (no disk) to avoid parallel test interference. #[test] fn simulate_mobile_to_desktop_sync() { // Desktop had saved this workbook let sheets = make_3_sheet_workbook(); let saved = serialize_workbook(&sheets, 0); // Mobile loads (from serialized string, equivalent to disk read) let (mut mobile_sheets, mobile_active) = deserialize_workbook(&saved).unwrap(); // Mobile edits mobile_sheets[mobile_active] .data .set_cell(10, 0, "mobile_was_here"); // Mobile writes back: update active sheet, save full workbook let updated = serialize_workbook(&mobile_sheets, mobile_active); // Desktop reloads (from serialized string) let (desktop_sheets, desktop_active) = deserialize_workbook(&updated).unwrap(); assert_eq!(desktop_sheets.len(), 3); assert_eq!(desktop_active, 0); assert_eq!(desktop_sheets[0].data.get_raw(10, 0), "mobile_was_here"); // Other sheets untouched assert_eq!(desktop_sheets[1].data.get_raw(0, 0), "Revenue"); assert_eq!(desktop_sheets[2].data.get_raw(0, 0), "Investment"); } /// Empty workbook round-trip. #[test] fn empty_workbook_round_trip() { let sheets: Vec = vec![]; let serialized = serialize_workbook(&sheets, 0); // An empty workbook should still produce valid output let result = deserialize_workbook(&serialized); assert!(result.is_some()); let (restored, active) = result.unwrap(); assert!(!restored.is_empty() || active == 0); } /// Single-sheet workbook (legacy format fallback). #[test] fn single_sheet_workbook_round_trip() { let mut data = SpreadsheetData::default(); data.set_cell(0, 0, "solo"); let sheets = vec![Sheet::new("OnlySheet", data)]; let serialized = serialize_workbook(&sheets, 0); let (restored, active) = deserialize_workbook(&serialized).unwrap(); assert_eq!(restored.len(), 1); assert_eq!(restored[0].name, "OnlySheet"); assert_eq!(restored[0].data.get_raw(0, 0), "solo"); assert_eq!(active, 0); } /// Unicode in sheet names survives round-trip. #[test] fn unicode_sheet_names_round_trip() { let sheets = vec![ Sheet::new("预算表", SpreadsheetData::default()), Sheet::new("Übersicht", SpreadsheetData::default()), Sheet::new("日本語シート", SpreadsheetData::default()), ]; let serialized = serialize_workbook(&sheets, 1); let (restored, active) = deserialize_workbook(&serialized).unwrap(); assert_eq!(restored.len(), 3); assert_eq!(restored[0].name, "预算表"); assert_eq!(restored[1].name, "Übersicht"); assert_eq!(restored[2].name, "日本語シート"); assert_eq!(active, 1); }