use super::common::abs_cell_ref;
use crate::engine::{DependencyGraph, VertexKind};
use formualizer_common::{ExcelErrorKind, LiteralValue};
use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
#[test]
fn test_dependency_extraction_from_ast() {
let mut graph = DependencyGraph::new();
graph
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(10))
.unwrap();
graph
.set_cell_value("Sheet1", 2, 2, LiteralValue::Int(20))
.unwrap();
let ast_with_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, 3, ast_with_ref)
.unwrap();
assert_eq!(graph.vertex_len(), 3);
let c3_vertex_id = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 3, 3))
.unwrap();
assert_eq!(graph.get_dependencies(c3_vertex_id).len(), 1);
let a1_addr = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 1, 1))
.unwrap();
assert_eq!(graph.get_dependencies(c3_vertex_id)[0], a1_addr);
let a1_vertex_id = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 1, 1))
.unwrap();
assert_eq!(graph.get_dependents(a1_vertex_id).len(), 1);
let c3_addr = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 3, 3))
.unwrap();
assert_eq!(graph.get_dependents(a1_vertex_id)[0], c3_addr);
}
#[test]
fn test_dependency_extraction_multiple_references() {
let mut graph = DependencyGraph::new();
graph
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(10))
.unwrap();
graph
.set_cell_value("Sheet1", 1, 2, LiteralValue::Int(20))
.unwrap();
let ast_binary = ASTNode {
node_type: ASTNodeType::BinaryOp {
op: "+".to_string(),
left: Box::new(ASTNode {
node_type: ASTNodeType::Reference {
original: "A1".to_string(),
reference: ReferenceType::cell(None, 1, 1),
},
source_token: None,
contains_volatile: false,
}),
right: Box::new(ASTNode {
node_type: ASTNodeType::Reference {
original: "B1".to_string(),
reference: ReferenceType::cell(None, 1, 2),
},
source_token: None,
contains_volatile: false,
}),
},
source_token: None,
contains_volatile: false,
};
graph.set_cell_formula("Sheet1", 2, 1, ast_binary).unwrap();
let a2_vertex_id = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 2, 1))
.unwrap();
let dependencies = graph.get_dependencies(a2_vertex_id);
assert_eq!(dependencies.len(), 2);
let a1_addr = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 1, 1))
.unwrap();
let b1_addr = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 1, 2))
.unwrap();
assert!(dependencies.contains(&a1_addr));
assert!(dependencies.contains(&b1_addr));
}
#[test]
fn test_dependency_edge_management() {
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 a1_vertex_id = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 1, 1))
.unwrap();
let a2_vertex_id = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 2, 1))
.unwrap();
assert_eq!(graph.get_dependencies(a2_vertex_id).len(), 1);
assert_eq!(graph.get_dependents(a1_vertex_id).len(), 1);
graph
.set_cell_value("Sheet1", 1, 2, LiteralValue::Int(20))
.unwrap();
let ast_ref_b1 = ASTNode {
node_type: ASTNodeType::Reference {
original: "B1".to_string(),
reference: ReferenceType::cell(None, 1, 2),
},
source_token: None,
contains_volatile: false,
};
graph.set_cell_formula("Sheet1", 2, 1, ast_ref_b1).unwrap();
let a1_vertex_id = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 1, 1))
.unwrap();
let a2_vertex_id = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 2, 1))
.unwrap();
let b1_vertex_id = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 1, 2))
.unwrap();
assert_eq!(graph.get_dependents(a1_vertex_id).len(), 0);
let b1_addr = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 1, 2))
.unwrap();
assert_eq!(graph.get_dependencies(a2_vertex_id).len(), 1);
assert_eq!(graph.get_dependencies(a2_vertex_id)[0], b1_addr);
assert_eq!(graph.get_dependents(b1_vertex_id).len(), 1);
}
#[test]
fn test_circular_dependency_detection() {
let mut graph = DependencyGraph::new();
let ast_self_ref = ASTNode {
node_type: ASTNodeType::Reference {
original: "A1".to_string(),
reference: ReferenceType::cell(None, 1, 1),
},
source_token: None,
contains_volatile: false,
};
let result = graph.set_cell_formula("Sheet1", 1, 1, ast_self_ref);
assert!(result.is_err());
match result.unwrap_err().kind {
ExcelErrorKind::Circ => {} other => panic!("Expected circular reference error, got {other:?}"),
}
assert_eq!(graph.vertex_len(), 1);
let a1_vertex_id = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 1, 1))
.unwrap();
match &graph.get_vertex_kind(a1_vertex_id) {
VertexKind::Empty => {} other => {
panic!("A1 should be an Empty vertex after failed formula update, but was {other:?}")
}
}
}
#[test]
fn test_complex_circular_dependency() {
let mut graph = DependencyGraph::new();
let ast_ref_b1 = ASTNode {
node_type: ASTNodeType::Reference {
original: "B1".to_string(),
reference: ReferenceType::cell(None, 1, 2),
},
source_token: None,
contains_volatile: false,
};
graph.set_cell_formula("Sheet1", 1, 1, ast_ref_b1).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,
};
let result = graph.set_cell_formula("Sheet1", 1, 2, ast_ref_a1);
assert!(result.is_ok());
assert_eq!(graph.vertex_len(), 2);
let a1_vertex_id = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 1, 1))
.unwrap();
let b1_vertex_id = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 1, 2))
.unwrap();
assert_eq!(graph.get_dependencies(a1_vertex_id).len(), 1);
assert_eq!(graph.get_dependencies(b1_vertex_id).len(), 1);
assert_eq!(graph.get_dependencies(b1_vertex_id).len(), 1);
assert_eq!(graph.get_dependencies(a1_vertex_id).len(), 1);
assert_eq!(graph.get_dependents(a1_vertex_id).len(), 1);
assert_eq!(graph.get_dependents(b1_vertex_id).len(), 1);
assert_eq!(graph.get_dependents(b1_vertex_id).len(), 1);
assert_eq!(graph.get_dependents(a1_vertex_id).len(), 1);
}
#[test]
fn test_cross_sheet_dependencies() {
let mut graph = DependencyGraph::new();
graph
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(10))
.unwrap();
let ast_cross_sheet = ASTNode {
node_type: ASTNodeType::Reference {
original: "Sheet1!A1".to_string(),
reference: ReferenceType::cell(Some("Sheet1".to_string()), 1, 1),
},
source_token: None,
contains_volatile: false,
};
graph
.set_cell_formula("Sheet2", 1, 1, ast_cross_sheet)
.unwrap();
assert_eq!(graph.vertex_len(), 2);
let sheet1_addr = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 1, 1))
.unwrap();
let sheet2_addr = *graph
.get_vertex_id_for_address(&abs_cell_ref(1, 1, 1))
.unwrap();
let sheet2_vertex_id = *graph
.get_vertex_id_for_address(&abs_cell_ref(1, 1, 1))
.unwrap();
let sheet1_vertex_id = *graph
.get_vertex_id_for_address(&abs_cell_ref(0, 1, 1))
.unwrap();
assert_eq!(graph.get_dependencies(sheet2_vertex_id).len(), 1);
assert_eq!(graph.get_dependencies(sheet2_vertex_id)[0], sheet1_addr);
assert_eq!(graph.get_dependents(sheet1_vertex_id).len(), 1);
assert_eq!(graph.get_dependents(sheet1_vertex_id)[0], sheet2_addr);
}
#[test]
fn test_relative_sheet_dependency() {
let mut graph = DependencyGraph::new();
graph
.set_cell_value("Sheet2", 1, 1, LiteralValue::Int(10))
.unwrap();
let ast_relative_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("Sheet2", 1, 2, ast_relative_ref)
.unwrap();
assert_eq!(graph.vertex_len(), 2);
let sheet2_a1_id = *graph
.get_vertex_id_for_address(&abs_cell_ref(1, 1, 1))
.unwrap();
let sheet2_b1_id = *graph
.get_vertex_id_for_address(&abs_cell_ref(1, 1, 2))
.unwrap();
let sheet2_b1_vertex_id = *graph
.get_vertex_id_for_address(&abs_cell_ref(1, 1, 2))
.unwrap();
assert_eq!(graph.get_dependencies(sheet2_b1_vertex_id).len(), 1);
assert_eq!(graph.get_dependencies(sheet2_b1_vertex_id)[0], sheet2_a1_id);
}