use std::path::PathBuf;
use sdivi_config::PatternsConfig;
use sdivi_parsing::feature_record::{FeatureRecord, PatternHint};
use sdivi_patterns::build_catalog;
fn call_hint(text: &str, start_row: usize) -> PatternHint {
PatternHint {
node_kind: "call_expression".to_string(),
start_byte: 0,
end_byte: text.len(),
start_row,
start_col: 0,
text: text.to_string(),
}
}
fn record(path: &str, language: &str, hints: Vec<PatternHint>) -> FeatureRecord {
FeatureRecord {
path: PathBuf::from(path),
language: language.to_string(),
imports: vec![],
exports: vec![],
signatures: vec![],
pattern_hints: hints,
}
}
fn config_min1(scope_exclude: Vec<String>) -> PatternsConfig {
PatternsConfig {
min_pattern_nodes: 1,
scope_exclude,
..PatternsConfig::default()
}
}
#[test]
fn ts_describe_in_scope_populates_testing_bucket() {
let records = vec![record(
"src/app.test.ts",
"typescript",
vec![
call_hint("describe('suite', fn)", 0),
call_hint("it('does something', fn)", 1),
],
)];
let catalog = build_catalog(&records, &config_min1(vec![]));
assert!(
catalog.entries.contains_key("testing"),
"testing bucket must be present when describe/it calls are in-scope; \
got categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
}
#[test]
fn ts_expect_in_scope_populates_testing_bucket() {
let records = vec![record(
"src/__tests__/math.test.ts",
"typescript",
vec![call_hint("expect(x).toBe(1)", 5)],
)];
let catalog = build_catalog(&records, &config_min1(vec![]));
assert!(
catalog.entries.contains_key("testing"),
"testing bucket must be present for expect(x).toBe(1) in-scope; \
got categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
let count: u32 = catalog.entries["testing"].values().map(|s| s.count).sum();
assert!(count >= 1, "testing bucket count must be >= 1, got {count}");
}
#[test]
fn go_t_fatal_in_scope_populates_testing_bucket() {
let records = vec![record(
"pkg/auth/auth_test.go",
"go",
vec![
call_hint("t.Run(\"sub\", fn)", 10),
call_hint("t.Fatal(err)", 15),
],
)];
let catalog = build_catalog(&records, &config_min1(vec![]));
assert!(
catalog.entries.contains_key("testing"),
"testing bucket must be present for Go t.Run/t.Fatal in-scope; \
got categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
}
#[test]
fn python_self_assert_in_scope_populates_testing_bucket() {
let records = vec![record(
"tests/test_model.py",
"python",
vec![
call_hint("self.assertEqual(a, b)", 20),
call_hint("self.assertTrue(x)", 21),
],
)];
let catalog = build_catalog(&records, &config_min1(vec![]));
assert!(
catalog.entries.contains_key("testing"),
"testing bucket must be present for Python self.assert* in-scope; \
got categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
}
#[test]
fn jest_vi_helpers_in_scope_populate_testing_bucket() {
let records = vec![record(
"src/mocks/setup.ts",
"typescript",
vec![
call_hint("jest.mock('./module')", 0),
call_hint("vi.fn()", 1),
],
)];
let catalog = build_catalog(&records, &config_min1(vec![]));
assert!(
catalog.entries.contains_key("testing"),
"testing bucket must be present for jest.mock / vi.fn in-scope; \
got categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
}
#[test]
fn excluded_test_file_yields_empty_testing_bucket() {
let records = vec![record(
"src/app.test.ts",
"typescript",
vec![
call_hint("describe('suite', fn)", 0),
call_hint("expect(x).toBe(1)", 1),
],
)];
let catalog = build_catalog(&records, &config_min1(vec!["**/*.test.ts".to_string()]));
assert!(
!catalog.entries.contains_key("testing"),
"testing bucket must be absent when test file is excluded; \
got categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
}
#[test]
fn excluded_tests_dir_yields_empty_testing_bucket() {
let records = vec![record(
"src/__tests__/math.test.js",
"javascript",
vec![
call_hint("it('adds', fn)", 0),
call_hint("expect(1 + 1).toBe(2)", 1),
],
)];
let catalog = build_catalog(&records, &config_min1(vec!["src/__tests__/**".to_string()]));
assert!(
!catalog.entries.contains_key("testing"),
"testing bucket must be absent when __tests__ dir is excluded; \
got categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
}
#[test]
fn excluded_go_test_files_yield_empty_testing_bucket() {
let records = vec![record(
"pkg/auth/auth_test.go",
"go",
vec![call_hint("t.Run(\"case\", fn)", 5)],
)];
let catalog = build_catalog(&records, &config_min1(vec!["**/*_test.go".to_string()]));
assert!(
!catalog.entries.contains_key("testing"),
"testing bucket must be absent when *_test.go is excluded; \
got categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
}
#[test]
fn excluded_python_tests_dir_yields_empty_testing_bucket() {
let records = vec![record(
"tests/test_model.py",
"python",
vec![call_hint("self.assertEqual(a, b)", 10)],
)];
let catalog = build_catalog(&records, &config_min1(vec!["tests/**".to_string()]));
assert!(
!catalog.entries.contains_key("testing"),
"testing bucket must be absent when tests/ dir is excluded; \
got categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
}
#[test]
fn mixed_in_scope_and_excluded_test_files() {
let excluded = record(
"src/vendor/vendor.test.ts",
"typescript",
vec![call_hint("describe('vendor', fn)", 0)],
);
let in_scope = record(
"src/app.test.ts",
"typescript",
vec![call_hint("describe('app', fn)", 0)],
);
let records = vec![excluded, in_scope];
let catalog = build_catalog(&records, &config_min1(vec!["src/vendor/**".to_string()]));
assert!(
catalog.entries.contains_key("testing"),
"testing bucket must be present from in-scope test file; \
got categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
let excluded_path = PathBuf::from("src/vendor/vendor.test.ts");
for stats in catalog.entries["testing"].values() {
for loc in &stats.locations {
assert_ne!(
loc.file, excluded_path,
"excluded file must not appear in testing bucket locations"
);
}
}
}
#[test]
fn excluded_file_remains_in_feature_record_slice() {
let records = vec![record(
"src/app.test.ts",
"typescript",
vec![call_hint("describe('s', fn)", 0)],
)];
let _catalog = build_catalog(&records, &config_min1(vec!["**/*.test.ts".to_string()]));
let still_present = records
.iter()
.any(|r| r.path == PathBuf::from("src/app.test.ts"));
assert!(
still_present,
"excluded file must remain in the FeatureRecord slice after build_catalog"
);
}
#[test]
fn non_test_call_in_test_file_is_not_testing() {
let records = vec![record(
"src/app.test.ts",
"typescript",
vec![
call_hint("console.log(x)", 0), call_hint("describe('s', fn)", 1),
],
)];
let catalog = build_catalog(&records, &config_min1(vec![]));
if let Some(testing_entries) = catalog.entries.get("testing") {
for stats in testing_entries.values() {
let _ = stats; }
}
assert!(
catalog.entries.contains_key("logging"),
"console.log must resolve to logging, not testing"
);
assert!(
catalog.entries.contains_key("testing"),
"describe('s', fn) must still resolve to testing"
);
}