use super::common::get_vertex_ids_in_order;
use crate::engine::{DependencyGraph, VertexKind};
use formualizer_common::LiteralValue;
use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
#[test]
fn test_mark_dirty_propagation() {
let mut graph = DependencyGraph::new();
graph
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(10))
.unwrap();
let ast_ref_a1 = ASTNode {
node_type: ASTNodeType::Reference {
original: "A1".to_string(),
reference: ReferenceType::cell(None, 1, 1),
},
source_token: None,
contains_volatile: false,
};
graph.set_cell_formula("Sheet1", 2, 1, ast_ref_a1).unwrap();
let ast_ref_a2 = ASTNode {
node_type: ASTNodeType::Reference {
original: "A2".to_string(),
reference: ReferenceType::cell(None, 2, 1),
},
source_token: None,
contains_volatile: false,
};
graph.set_cell_formula("Sheet1", 3, 1, ast_ref_a2).unwrap();
let ast_ref_a3 = ASTNode {
node_type: ASTNodeType::Reference {
original: "A3".to_string(),
reference: ReferenceType::cell(None, 3, 1),
},
source_token: None,
contains_volatile: false,
};
graph.set_cell_formula("Sheet1", 4, 1, ast_ref_a3).unwrap();
let all_vertex_ids = get_vertex_ids_in_order(&graph);
graph.clear_dirty_flags(&all_vertex_ids);
for &vertex_id in &all_vertex_ids {
assert!(
!graph.is_dirty(vertex_id),
"Vertex should be clean after clearing"
);
}
let summary = graph
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(20))
.unwrap();
assert_eq!(summary.affected_vertices.len(), 4);
let vertex_ids = get_vertex_ids_in_order(&graph);
assert!(
!graph.is_dirty(vertex_ids[0]),
"A1 should be clean, as it is a value"
);
assert!(
graph.get_vertex_kind(vertex_ids[0]) == VertexKind::Cell,
"A1 should be a value"
);
for (idx, &vertex_id) in vertex_ids.iter().enumerate().skip(1).take(3) {
assert!(
graph.is_dirty(vertex_id),
"A{} should be dirty after A1 changed",
idx + 1
);
assert!(
graph.get_vertex_kind(vertex_id) == VertexKind::FormulaScalar,
"A{} should be a formula",
idx + 1
);
}
let eval_vertices = graph.get_evaluation_vertices();
assert!(eval_vertices.len() >= 3); }
#[test]
fn test_mark_dirty_diamond_dependency() {
let mut graph = DependencyGraph::new();
graph
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(10))
.unwrap();
let ast_ref_a1 = ASTNode {
node_type: ASTNodeType::Reference {
original: "A1".to_string(),
reference: ReferenceType::cell(None, 1, 1),
},
source_token: None,
contains_volatile: false,
};
graph
.set_cell_formula("Sheet1", 2, 1, ast_ref_a1.clone())
.unwrap();
graph.set_cell_formula("Sheet1", 3, 1, ast_ref_a1).unwrap();
let ast_sum = ASTNode {
node_type: ASTNodeType::BinaryOp {
op: "+".to_string(),
left: Box::new(ASTNode {
node_type: ASTNodeType::Reference {
original: "A2".to_string(),
reference: ReferenceType::cell(None, 2, 1),
},
source_token: None,
contains_volatile: false,
}),
right: Box::new(ASTNode {
node_type: ASTNodeType::Reference {
original: "A3".to_string(),
reference: ReferenceType::cell(None, 3, 1),
},
source_token: None,
contains_volatile: false,
}),
},
source_token: None,
contains_volatile: false,
};
graph.set_cell_formula("Sheet1", 4, 1, ast_sum).unwrap();
let all_vertex_ids = get_vertex_ids_in_order(&graph);
graph.clear_dirty_flags(&all_vertex_ids);
let summary = graph
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(20))
.unwrap();
assert_eq!(summary.affected_vertices.len(), 4);
assert!(graph.is_dirty(all_vertex_ids[3]), "A4 should be dirty");
assert!(
graph.get_vertex_kind(all_vertex_ids[3]) == VertexKind::FormulaScalar,
"A4 should be a formula"
);
}
#[test]
fn test_dirty_flag_clearing() {
let mut graph = DependencyGraph::new();
graph
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(10))
.unwrap();
let ast_ref_a1 = ASTNode {
node_type: ASTNodeType::Reference {
original: "A1".to_string(),
reference: ReferenceType::cell(None, 1, 1),
},
source_token: None,
contains_volatile: false,
};
graph.set_cell_formula("Sheet1", 2, 1, ast_ref_a1).unwrap();
let all_vertex_ids = get_vertex_ids_in_order(&graph);
assert!(
graph.is_dirty(all_vertex_ids[1]),
"A2 should be dirty after creation"
);
assert!(
graph.get_vertex_kind(all_vertex_ids[1]) == VertexKind::FormulaScalar,
"A2 should be a formula"
);
let vertex_ids = vec![all_vertex_ids[1]]; graph.clear_dirty_flags(&vertex_ids);
assert!(
!graph.is_dirty(all_vertex_ids[1]),
"A2 should be clean after clearing"
);
let eval_vertices = graph.get_evaluation_vertices();
let all_vertex_ids = get_vertex_ids_in_order(&graph);
assert!(!eval_vertices.contains(&all_vertex_ids[1]));
graph
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(30))
.unwrap();
assert!(
graph.is_dirty(all_vertex_ids[1]),
"A2 should be dirty after A1 changed"
);
}
#[test]
fn test_volatile_vertex_handling() {
let mut graph = DependencyGraph::new();
crate::builtins::random::register_builtins();
let volatile_ast = ASTNode {
node_type: ASTNodeType::Function {
name: "RAND".to_string(),
args: vec![],
},
source_token: None,
contains_volatile: true,
};
graph
.set_cell_formula("Sheet1", 1, 1, volatile_ast)
.unwrap();
let all_vertex_ids = get_vertex_ids_in_order(&graph);
let a1_id = all_vertex_ids[0];
let eval_vertices = graph.get_evaluation_vertices();
assert!(eval_vertices.contains(&a1_id));
graph.clear_dirty_flags(&[a1_id]);
let eval_vertices_after_clear = graph.get_evaluation_vertices();
assert!(eval_vertices_after_clear.contains(&a1_id));
}
#[test]
fn test_evaluation_vertices_combined() {
let mut graph = DependencyGraph::new();
graph
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(10))
.unwrap();
let ast_literal = ASTNode {
node_type: ASTNodeType::Literal(LiteralValue::Int(20)),
source_token: None,
contains_volatile: false,
};
graph.set_cell_formula("Sheet1", 2, 1, ast_literal).unwrap();
let ast_ref = ASTNode {
node_type: ASTNodeType::Reference {
original: "A1".to_string(),
reference: ReferenceType::cell(None, 1, 1),
},
source_token: None,
contains_volatile: false,
};
graph.set_cell_formula("Sheet1", 3, 1, ast_ref).unwrap();
let eval_vertices = graph.get_evaluation_vertices();
assert!(eval_vertices.len() >= 2);
let mut sorted_eval = eval_vertices.clone();
sorted_eval.sort();
assert_eq!(eval_vertices, sorted_eval);
}
#[test]
fn test_dirty_propagation_performance() {
let mut graph = DependencyGraph::new();
graph
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(1))
.unwrap();
for i in 2..=20 {
let ast_ref = ASTNode {
node_type: ASTNodeType::Reference {
original: format!("A{}", i - 1),
reference: ReferenceType::cell(None, 1, 1),
},
source_token: None,
contains_volatile: false,
};
graph.set_cell_formula("Sheet1", i, 1, ast_ref).unwrap();
}
let all_vertex_ids = get_vertex_ids_in_order(&graph);
graph.clear_dirty_flags(&all_vertex_ids);
let start = std::time::Instant::now();
let summary = graph
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(100))
.unwrap();
let elapsed = start.elapsed();
assert_eq!(summary.affected_vertices.len(), 20);
assert!(
elapsed < std::time::Duration::from_millis(10),
"Dirty propagation took too long: {elapsed:?}"
);
for (idx, &vertex_id) in all_vertex_ids.iter().enumerate().skip(1) {
assert!(graph.is_dirty(vertex_id), "A{} should be dirty", idx + 1);
assert!(
graph.get_vertex_kind(vertex_id) == VertexKind::FormulaScalar,
"A{} should be a formula",
idx + 1
);
}
}