//! Integration tests covering real-world sheet scenarios. //! //! These exercise the full pipeline (lexer -> parser -> interpreter) through //! the eval_line / eval_sheet / SheetContext public APIs, verifying end-to-end //! behavior rather than individual module internals. use calcpad_engine::context::EvalContext; use calcpad_engine::pipeline::{eval_line, eval_sheet}; use calcpad_engine::types::ResultType; use calcpad_engine::SheetContext; // ========================================================================= // Variable assignment and reference across lines // ========================================================================= #[test] fn sheet_variable_assignment_and_reference() { let mut ctx = EvalContext::new(); let results = eval_sheet( &["price = 100", "quantity = 5", "total = price * quantity"], &mut ctx, ); assert_eq!(results.len(), 3); assert_eq!(results[0].metadata.display, "100"); assert_eq!(results[1].metadata.display, "5"); assert_eq!(results[2].metadata.display, "500"); } #[test] fn sheet_variable_used_in_multiple_lines() { let mut ctx = EvalContext::new(); let results = eval_sheet( &[ "rate = 25", "hours = 8", "daily = rate * hours", "weekly = daily * 5", ], &mut ctx, ); assert_eq!(results[2].metadata.display, "200"); assert_eq!(results[3].metadata.display, "1000"); } // ========================================================================= // Chained dependencies // ========================================================================= #[test] fn sheet_chained_dependencies() { let mut ctx = EvalContext::new(); let results = eval_sheet( &["a = 10", "b = a + 5", "c = b * 2", "d = c - a"], &mut ctx, ); assert_eq!(results[0].metadata.display, "10"); assert_eq!(results[1].metadata.display, "15"); assert_eq!(results[2].metadata.display, "30"); assert_eq!(results[3].metadata.display, "20"); } #[test] fn sheet_deep_chain() { let mut ctx = EvalContext::new(); let results = eval_sheet( &[ "v1 = 1", "v2 = v1 + 1", "v3 = v2 + 1", "v4 = v3 + 1", "v5 = v4 + 1", ], &mut ctx, ); assert_eq!(results[4].metadata.display, "5"); } // ========================================================================= // Forward references and undefined variables // ========================================================================= #[test] fn sheet_forward_reference_error() { let mut ctx = EvalContext::new(); let results = eval_sheet(&["a = b + 1", "b = 10"], &mut ctx); // eval_sheet processes sequentially; b isn't defined yet when line 0 runs assert_eq!(results[0].result_type(), ResultType::Error); assert_eq!(results[1].metadata.display, "10"); } #[test] fn sheet_undefined_variable_in_chain() { let mut ctx = EvalContext::new(); let results = eval_sheet(&["x = undefined_var + 1"], &mut ctx); assert_eq!(results[0].result_type(), ResultType::Error); } // ========================================================================= // Unit conversion across lines // ========================================================================= #[test] fn sheet_unit_arithmetic_same_unit() { let mut ctx = EvalContext::new(); let results = eval_sheet(&["a = 5kg", "b = 3kg", "total = a + b"], &mut ctx); assert_eq!(results[2].result_type(), ResultType::UnitValue); assert_eq!(results[2].metadata.raw_value, Some(8.0)); } // ========================================================================= // Mixed expressions: comments, empty lines, text // ========================================================================= #[test] fn sheet_with_comments() { let mut ctx = EvalContext::new(); let results = eval_sheet( &[ "// Budget calculation", "income = 5000", "expenses = 3000", "savings = income - expenses", ], &mut ctx, ); assert_eq!(results[0].result_type(), ResultType::NonCalculable); // comment assert_eq!(results[3].metadata.display, "2000"); } #[test] fn sheet_with_empty_lines() { let mut ctx = EvalContext::new(); let results = eval_sheet(&["x = 10", "", "y = x + 5"], &mut ctx); assert_eq!(results[0].metadata.display, "10"); assert_eq!(results[1].result_type(), ResultType::Empty); // empty line assert_eq!(results[2].metadata.display, "15"); } #[test] fn sheet_mixed_comments_and_calcs() { let mut ctx = EvalContext::new(); let results = eval_sheet( &[ "// Shopping list", "apples = 3 * 2", "// bananas are on sale", "bananas = 5 * 1", "total = apples + bananas", ], &mut ctx, ); assert_eq!(results[1].metadata.display, "6"); assert_eq!(results[3].metadata.display, "5"); assert_eq!(results[4].metadata.display, "11"); } // ========================================================================= // Variable reassignment / shadowing // ========================================================================= #[test] fn sheet_variable_reassignment() { let mut ctx = EvalContext::new(); let results = eval_sheet(&["x = 5", "y = x * 2", "x = 20", "z = x * 2"], &mut ctx); assert_eq!(results[0].metadata.display, "5"); assert_eq!(results[1].metadata.display, "10"); assert_eq!(results[2].metadata.display, "20"); assert_eq!(results[3].metadata.display, "40"); // uses reassigned x=20 } // ========================================================================= // Real-world calculation scenarios // ========================================================================= #[test] fn sheet_invoice_calculation() { let mut ctx = EvalContext::new(); let results = eval_sheet( &[ "// Invoice", "subtotal = 1500", "tax_rate = 8.5", "tax = subtotal * tax_rate / 100", "total = subtotal + tax", ], &mut ctx, ); assert_eq!(results[1].metadata.display, "1500"); assert_eq!(results[3].metadata.display, "127.5"); assert_eq!(results[4].metadata.display, "1627.5"); } #[test] fn sheet_tip_calculation() { let mut ctx = EvalContext::new(); let results = eval_sheet( &["bill = 85", "tip = bill + 20%", "per_person = tip / 4"], &mut ctx, ); assert_eq!(results[0].metadata.display, "85"); assert_eq!(results[1].metadata.display, "102"); assert!((results[2].metadata.raw_value.unwrap() - 25.5).abs() < 0.01); } #[test] fn sheet_accumulator_pattern() { let mut ctx = EvalContext::new(); let lines: Vec = (0..20) .map(|i| { if i == 0 { "acc = 0".to_string() } else { format!("acc = acc + {}", i) } }) .collect(); let refs: Vec<&str> = lines.iter().map(|s| s.as_str()).collect(); let results = eval_sheet(&refs, &mut ctx); // Sum of 1..19 = 190 let last = results.last().unwrap(); assert_eq!(last.metadata.raw_value, Some(190.0)); } // ========================================================================= // Context isolation // ========================================================================= #[test] fn context_variables_independent() { let mut ctx1 = EvalContext::new(); let mut ctx2 = EvalContext::new(); eval_line("x = 100", &mut ctx1); let r = eval_line("x", &mut ctx2); assert_eq!(r.result_type(), ResultType::Error); // x not defined in ctx2 } // ========================================================================= // SheetContext: incremental evaluation // ========================================================================= #[test] fn sheet_context_incremental_independent_lines() { let mut sheet = SheetContext::new(); sheet.set_line(0, "a = 10"); sheet.set_line(1, "b = 20"); sheet.set_line(2, "c = a + 5"); sheet.set_line(3, "d = b + 5"); let results = sheet.eval(); assert_eq!(results.len(), 4); // Change only b: a and c should be unaffected sheet.set_line(1, "b = 99"); let results = sheet.eval(); assert_eq!(results[0].metadata.raw_value, Some(10.0)); assert_eq!(results[1].metadata.raw_value, Some(99.0)); assert_eq!(results[2].metadata.raw_value, Some(15.0)); // a + 5, unchanged assert_eq!(results[3].metadata.raw_value, Some(104.0)); // b + 5 } #[test] fn sheet_context_aggregator_invoice() { let mut sheet = SheetContext::new(); sheet.set_line(0, "## Monthly Expenses"); sheet.set_line(1, "1200"); // rent sheet.set_line(2, "150"); // utilities sheet.set_line(3, "400"); // groceries sheet.set_line(4, "subtotal"); sheet.set_line(5, "## One-Time Costs"); sheet.set_line(6, "500"); // furniture sheet.set_line(7, "200"); // electronics sheet.set_line(8, "subtotal"); sheet.set_line(9, "grand total"); let results = sheet.eval(); assert_eq!(results[4].metadata.raw_value, Some(1750.0)); assert_eq!(results[8].metadata.raw_value, Some(700.0)); assert_eq!(results[9].metadata.raw_value, Some(2450.0)); } #[test] fn sheet_context_prev_through_sections() { let mut sheet = SheetContext::new(); sheet.set_line(0, "100"); sheet.set_line(1, "prev + 50"); // 150 sheet.set_line(2, "prev * 2"); // 300 let results = sheet.eval(); assert_eq!(results[0].metadata.raw_value, Some(100.0)); assert_eq!(results[1].metadata.raw_value, Some(150.0)); assert_eq!(results[2].metadata.raw_value, Some(300.0)); } #[test] fn sheet_context_comparison_result() { let mut ctx = EvalContext::new(); let results = eval_sheet( &["budget = 1000", "spent = 750", "budget - spent > 0"], &mut ctx, ); assert_eq!(results[2].result_type(), ResultType::Boolean); assert_eq!(results[2].metadata.display, "true"); } #[test] fn sheet_context_percentage_discount() { let mut ctx = EvalContext::new(); let results = eval_sheet( &["original = $200", "discounted = $200 - 30%"], &mut ctx, ); assert_eq!(results[0].result_type(), ResultType::CurrencyValue); assert_eq!(results[1].result_type(), ResultType::CurrencyValue); assert!((results[1].metadata.raw_value.unwrap() - 140.0).abs() < 0.01); }