use super::*;
#[test]
fn imports_edge_has_target_not_to() {
let e = Edge::Imports {
from_file: "F.cs".into(),
from_line: 1,
target: "System".into(),
};
let json = serde_json::to_string(&e).unwrap();
assert_eq!(
json,
r#"{"kind":"imports","from_file":"F.cs","from_line":1,"target":"System"}"#
);
}
#[test]
fn uses_type_edge_shape() {
let e = Edge::UsesType {
from_file: "F.cs".into(),
from_line: 1,
to: "Ns.T".into(),
to_file: "Ns/T.cs".into(),
heuristic: false,
};
let json = serde_json::to_string(&e).unwrap();
assert_eq!(
json,
r#"{"kind":"uses-type","from_file":"F.cs","from_line":1,"to":"Ns.T","to_file":"Ns/T.cs"}"#
);
}
#[test]
fn heuristic_flag_is_appended_last_and_omitted_when_false() {
let precise = Edge::UsesMember {
from_file: "F.cs".into(),
from_line: 1,
to: "Ns.T".into(),
to_file: "Ns/T.cs".into(),
heuristic: false,
tier: None,
member: None,
source: None,
overload_signature: None,
};
assert_eq!(
serde_json::to_string(&precise).unwrap(),
r#"{"kind":"uses-member","from_file":"F.cs","from_line":1,"to":"Ns.T","to_file":"Ns/T.cs"}"#
);
let guess = Edge::UsesMember {
from_file: "F.cs".into(),
from_line: 1,
to: "Ns.T".into(),
to_file: "Ns/T.cs".into(),
heuristic: true,
tier: Some(HeuristicTier::Guess),
member: Some("M".into()),
source: None,
overload_signature: None,
};
assert_eq!(
serde_json::to_string(&guess).unwrap(),
r#"{"kind":"uses-member","from_file":"F.cs","from_line":1,"to":"Ns.T","to_file":"Ns/T.cs","heuristic":true,"tier":"guess","member":"M"}"#
);
let ext = Edge::UsesMember {
from_file: "F.cs".into(),
from_line: 1,
to: "Ns.T".into(),
to_file: "Ns/T.cs".into(),
heuristic: true,
tier: Some(HeuristicTier::Ext),
member: Some("M".into()),
source: None,
overload_signature: None,
};
assert_eq!(
serde_json::to_string(&ext).unwrap(),
r#"{"kind":"uses-member","from_file":"F.cs","from_line":1,"to":"Ns.T","to_file":"Ns/T.cs","heuristic":true,"tier":"ext","member":"M"}"#
);
assert_eq!(
serde_json::from_str::<Edge>(&serde_json::to_string(&guess).unwrap()).unwrap(),
guess
);
assert_eq!(
serde_json::from_str::<Edge>(&serde_json::to_string(&ext).unwrap()).unwrap(),
ext
);
assert_eq!(
serde_json::from_str::<Edge>(&serde_json::to_string(&precise).unwrap()).unwrap(),
precise
);
}
#[test]
fn uses_member_edge_appends_tier_then_member_after_heuristic_and_omits_both_when_precise() {
let precise = Edge::uses_member(
"F.cs".into(),
1,
"Ns.T".into(),
"Ns/T.cs".into(),
Some("M".into()),
None,
);
assert_eq!(
serde_json::to_string(&precise).unwrap(),
r#"{"kind":"uses-member","from_file":"F.cs","from_line":1,"to":"Ns.T","to_file":"Ns/T.cs","member":"M"}"#
);
assert!(!precise.is_heuristic(), "no tier means no guess");
assert_eq!(precise.tier(), None);
for (tier, word) in [(HeuristicTier::Ext, "ext"), (HeuristicTier::Guess, "guess")] {
let e = Edge::uses_member(
"F.cs".into(),
1,
"Ns.T".into(),
"Ns/T.cs".into(),
Some("M".into()),
Some(tier),
);
assert_eq!(
serde_json::to_string(&e).unwrap(),
format!(
r#"{{"kind":"uses-member","from_file":"F.cs","from_line":1,"to":"Ns.T","to_file":"Ns/T.cs","heuristic":true,"tier":"{word}","member":"M"}}"#
),
"heuristic, then tier, then member"
);
assert!(e.is_heuristic());
assert_eq!(e.tier(), Some(tier));
}
assert_eq!(
Edge::UsesType {
from_file: "F.cs".into(),
from_line: 1,
to: "Ns.T".into(),
to_file: "Ns/T.cs".into(),
heuristic: true,
}
.tier(),
None
);
}
#[test]
fn uses_member_semantic_appends_source_last_and_carries_neither_heuristic_nor_tier() {
let ladder = Edge::uses_member(
"F.cs".into(),
1,
"Ns.T".into(),
"Ns/T.cs".into(),
Some("M".into()),
None,
);
assert!(
!serde_json::to_string(&ladder).unwrap().contains("source"),
"an ordinary uses-member edge omits `source` entirely"
);
let confirmed = Edge::uses_member_semantic(
"F.cs".into(),
1,
"Ns.T".into(),
"Ns/T.cs".into(),
Some("M".into()),
SemanticProvenance::Semantic,
None,
);
assert!(!confirmed.is_heuristic());
assert_eq!(confirmed.tier(), None);
assert_eq!(confirmed.provenance(), Some(SemanticProvenance::Semantic));
assert_eq!(
serde_json::to_string(&confirmed).unwrap(),
r#"{"kind":"uses-member","from_file":"F.cs","from_line":1,"to":"Ns.T","to_file":"Ns/T.cs","member":"M","source":"semantic"}"#
);
let discovered = Edge::uses_member_semantic(
"F.cs".into(),
1,
"Ns.T".into(),
"Ns/T.cs".into(),
Some("M".into()),
SemanticProvenance::SemanticDiscovered,
None,
);
assert_eq!(
discovered.provenance(),
Some(SemanticProvenance::SemanticDiscovered)
);
assert!(serde_json::to_string(&discovered)
.unwrap()
.ends_with(r#""source":"semantic-discovered"}"#));
assert_eq!(
serde_json::from_str::<Edge>(&serde_json::to_string(&confirmed).unwrap()).unwrap(),
confirmed
);
}
#[test]
fn overload_signature_appends_last_after_source_and_is_omitted_when_absent() {
let no_signature = Edge::uses_member_semantic(
"F.cs".into(),
1,
"Ns.T".into(),
"Ns/T.cs".into(),
Some("M".into()),
SemanticProvenance::Semantic,
None,
);
assert!(
!serde_json::to_string(&no_signature)
.unwrap()
.contains("overload_signature"),
"an admitted occurrence naming no overload signature must not write the key at all"
);
let with_signature = Edge::uses_member_semantic(
"F.cs".into(),
1,
"Ns.T".into(),
"Ns/T.cs".into(),
Some("M".into()),
SemanticProvenance::Semantic,
Some("(bool)->void".into()),
);
assert_eq!(
serde_json::to_string(&with_signature).unwrap(),
r#"{"kind":"uses-member","from_file":"F.cs","from_line":1,"to":"Ns.T","to_file":"Ns/T.cs","member":"M","source":"semantic","overload_signature":"(bool)->void"}"#
);
assert_eq!(
serde_json::from_str::<Edge>(&serde_json::to_string(&with_signature).unwrap()).unwrap(),
with_signature
);
}
#[test]
fn ambiguous_edge_shape() {
let e = Edge::Ambiguous {
origin: "uses-type".into(),
from_file: "F.cs".into(),
from_line: 4,
raw: "Money".into(),
candidates: vec![Candidate {
id: "A.Money".into(),
file: "A/Money.cs".into(),
}],
candidate_count: 2,
};
let json = serde_json::to_string(&e).unwrap();
assert_eq!(
json,
r#"{"kind":"ambiguous","origin":"uses-type","from_file":"F.cs","from_line":4,"raw":"Money","candidates":[{"id":"A.Money","file":"A/Money.cs"}],"candidate_count":2}"#
);
}
#[test]
fn edges_by_kind_field_order_is_fixed_not_alphabetical() {
let s = EdgesByKind {
inherits: 1,
uses_type: 2,
imports: 3,
uses_member: 4,
ctor_di: 5,
implements: 6,
overrides: 7,
..Default::default()
};
let json = serde_json::to_string(&s).unwrap();
assert_eq!(
json,
r#"{"inherits":1,"uses-type":2,"imports":3,"uses-member":4,"ctor-di":5,"implements":6,"overrides":7}"#
);
}
#[test]
fn edges_by_kind_appends_the_four_ts_counts_after_ctor_di_when_present() {
let s = EdgesByKind {
inherits: 1,
uses_type: 2,
imports: 3,
uses_member: 4,
ctor_di: 5,
implements: 10,
overrides: 11,
import: Some(6),
call: Some(7),
jsx_use: Some(8),
dispatch: Some(9),
bus_hop: None,
};
let json = serde_json::to_string(&s).unwrap();
assert_eq!(
json,
r#"{"inherits":1,"uses-type":2,"imports":3,"uses-member":4,"ctor-di":5,"implements":10,"overrides":11,"import":6,"call":7,"jsx-use":8,"dispatch":9}"#
);
}