use std::collections::BTreeMap;
use oxc_coverage_instrument::{
FileCoverage, InstrumentOptions, SourceMapStore, instrument, parse_coverage_map,
remap_coverage, remap_coverage_map, remap_coverage_map_with_loader, remap_coverage_with_loader,
};
fn three_line_inputs() -> (String, String, String) {
let original_ts = "const x: number = 1;\nconst y: number = 2;\nconst z: number = 3;\n";
let intermediate_js = "const x = 1;\nconst y = 2;\nconst z = 3;\n";
let input_sm = format!(
r#"{{"version":3,"sources":["src/app.ts"],"sourcesContent":[{original_ts:?}],"mappings":"AAAA;AACA;AACA","names":[]}}"#,
);
(original_ts.to_string(), intermediate_js.to_string(), input_sm)
}
#[test]
fn remap_coverage_rewrites_path_to_original_source() {
let (_, intermediate, input_sm) = three_line_inputs();
let opts =
InstrumentOptions { input_source_map: Some(input_sm), ..InstrumentOptions::default() };
let result = instrument(&intermediate, "intermediate.js", &opts).unwrap();
let remapped = remap_coverage(&result.coverage_map)
.expect("remap returns Some when inputSourceMap present");
assert_eq!(remapped.path, "src/app.ts");
assert!(
remapped.input_source_map.is_none(),
"remapped coverage clears the consumed inputSourceMap"
);
}
#[test]
fn remap_coverage_resolves_statement_positions_to_original_lines() {
let (_, intermediate, input_sm) = three_line_inputs();
let opts =
InstrumentOptions { input_source_map: Some(input_sm), ..InstrumentOptions::default() };
let result = instrument(&intermediate, "intermediate.js", &opts).unwrap();
let remapped = remap_coverage(&result.coverage_map).expect("remap succeeds");
let mut lines: Vec<u32> = remapped.statement_map.values().map(|loc| loc.start.line).collect();
lines.sort_unstable();
assert_eq!(lines, vec![1, 2, 3], "statementMap lines after remap, got: {lines:?}");
}
#[test]
fn remap_coverage_applies_source_root() {
let input_sm = r#"{"version":3,"sourceRoot":"project/","sources":["src/app.ts"],"mappings":"AAAA","names":[]}"#;
let opts = InstrumentOptions {
input_source_map: Some(input_sm.to_string()),
..InstrumentOptions::default()
};
let result = instrument("const x = 1;", "intermediate.js", &opts).unwrap();
let remapped = remap_coverage(&result.coverage_map).expect("remap succeeds");
assert_eq!(remapped.path, "project/src/app.ts");
}
#[test]
fn remap_coverage_applies_source_root_without_trailing_slash() {
let input_sm = r#"{"version":3,"sourceRoot":"project","sources":["src/app.ts"],"mappings":"AAAA","names":[]}"#;
let opts = InstrumentOptions {
input_source_map: Some(input_sm.to_string()),
..InstrumentOptions::default()
};
let result = instrument("const x = 1;", "intermediate.js", &opts).unwrap();
let remapped = remap_coverage(&result.coverage_map).expect("remap succeeds");
assert_eq!(remapped.path, "project/src/app.ts");
}
#[test]
fn remap_coverage_returns_none_without_input_source_map() {
let result = instrument("const x = 1;", "test.js", &InstrumentOptions::default()).unwrap();
assert!(
remap_coverage(&result.coverage_map).is_none(),
"remap returns None when no inputSourceMap is attached"
);
}
#[test]
fn remap_coverage_returns_none_when_input_sources_empty() {
let input_sm = r#"{"version":3,"sources":[],"mappings":"","names":[]}"#;
let opts = InstrumentOptions {
input_source_map: Some(input_sm.to_string()),
..InstrumentOptions::default()
};
let result = instrument("const x = 1;", "test.js", &opts).unwrap();
assert!(remap_coverage(&result.coverage_map).is_none());
}
#[test]
fn remap_coverage_map_rewrites_keys_to_original_paths() {
let (_, intermediate, input_sm) = three_line_inputs();
let opts =
InstrumentOptions { input_source_map: Some(input_sm), ..InstrumentOptions::default() };
let result = instrument(&intermediate, "intermediate.js", &opts).unwrap();
let mut coverage: BTreeMap<String, FileCoverage> = BTreeMap::new();
coverage.insert("intermediate.js".to_string(), result.coverage_map);
let remapped = remap_coverage_map(&coverage);
assert!(
remapped.contains_key("src/app.ts"),
"remapped coverage map should be keyed by the resolved original path"
);
assert!(
!remapped.contains_key("intermediate.js"),
"remapped coverage map should not retain the intermediate path"
);
}
#[test]
fn remap_coverage_map_passes_through_entries_without_input_map() {
let result = instrument("const x = 1;", "plain.js", &InstrumentOptions::default()).unwrap();
let mut coverage: BTreeMap<String, FileCoverage> = BTreeMap::new();
coverage.insert("plain.js".to_string(), result.coverage_map);
let remapped = remap_coverage_map(&coverage);
assert!(remapped.contains_key("plain.js"), "passthrough preserves the key");
}
#[test]
fn remap_coverage_round_trips_through_parse_coverage_map() {
let (_, intermediate, input_sm) = three_line_inputs();
let opts =
InstrumentOptions { input_source_map: Some(input_sm), ..InstrumentOptions::default() };
let result = instrument(&intermediate, "intermediate.js", &opts).unwrap();
let original_statement_count = result.coverage_map.statement_map.len();
let coverage_json = format!(
r#"{{"intermediate.js":{}}}"#,
serde_json::to_string(&result.coverage_map).unwrap()
);
let parsed = parse_coverage_map(&coverage_json).expect("parse coverage-final.json shape");
let remapped = remap_coverage_map(&parsed);
let fc = remapped.get("src/app.ts").expect("remapped under original path");
assert_eq!(
fc.statement_map.len(),
original_statement_count,
"statementMap entries must survive serialize-parse-remap"
);
assert_eq!(fc.s.len(), original_statement_count, "hit-count slots must survive the round-trip");
assert!(fc.input_source_map.is_none(), "inputSourceMap should be consumed during remap");
}
#[test]
fn remap_coverage_with_loader_falls_back_to_external_map() {
let intermediate_js = "const x = 1;\nconst y = 2;\nconst z = 3;\n";
let original_ts = "const x: number = 1;\nconst y: number = 2;\nconst z: number = 3;\n";
let input_sm = format!(
r#"{{"version":3,"sources":["src/app.ts"],"sourcesContent":[{original_ts:?}],"mappings":"AAAA;AACA;AACA","names":[]}}"#,
);
let result =
instrument(intermediate_js, "intermediate.js", &InstrumentOptions::default()).unwrap();
assert!(
result.coverage_map.input_source_map.is_none(),
"precondition: no inputSourceMap on the FileCoverage"
);
let remapped = remap_coverage_with_loader(&result.coverage_map, |path| {
if path == "intermediate.js" { Some(input_sm.clone()) } else { None }
})
.expect("loader supplies the missing map");
assert_eq!(remapped.path, "src/app.ts");
let mut lines: Vec<u32> = remapped.statement_map.values().map(|loc| loc.start.line).collect();
lines.sort_unstable();
assert_eq!(lines, vec![1, 2, 3], "statementMap lines after loader-supplied remap");
}
#[test]
fn remap_coverage_with_loader_prefers_embedded_map() {
let intermediate_js = "const x = 1;\nconst y = 2;\nconst z = 3;\n";
let original_ts = "const x: number = 1;\nconst y: number = 2;\nconst z: number = 3;\n";
let input_sm = format!(
r#"{{"version":3,"sources":["src/app.ts"],"sourcesContent":[{original_ts:?}],"mappings":"AAAA;AACA;AACA","names":[]}}"#,
);
let opts =
InstrumentOptions { input_source_map: Some(input_sm), ..InstrumentOptions::default() };
let result = instrument(intermediate_js, "intermediate.js", &opts).unwrap();
let remapped =
remap_coverage_with_loader(&result.coverage_map, |_| panic!("loader must not run"))
.expect("embedded map is used");
assert_eq!(remapped.path, "src/app.ts");
}
#[test]
fn remap_coverage_map_with_loader_handles_mixed_entries() {
let intermediate_js = "const x = 1;\n";
let ts_map = r#"{"version":3,"sources":["src/embedded.ts"],"mappings":"AAAA","names":[]}"#;
let loader_map = r#"{"version":3,"sources":["src/loader.ts"],"mappings":"AAAA","names":[]}"#;
let embedded_result = instrument(
intermediate_js,
"embedded.js",
&InstrumentOptions {
input_source_map: Some(ts_map.to_string()),
..InstrumentOptions::default()
},
)
.unwrap();
let needs_loader =
instrument(intermediate_js, "needs-loader.js", &InstrumentOptions::default()).unwrap();
let plain = instrument(intermediate_js, "plain.js", &InstrumentOptions::default()).unwrap();
let mut coverage: BTreeMap<String, FileCoverage> = BTreeMap::new();
coverage.insert("embedded.js".to_string(), embedded_result.coverage_map);
coverage.insert("needs-loader.js".to_string(), needs_loader.coverage_map);
coverage.insert("plain.js".to_string(), plain.coverage_map);
let remapped = remap_coverage_map_with_loader(&coverage, |path| {
if path == "needs-loader.js" { Some(loader_map.to_string()) } else { None }
});
assert!(remapped.contains_key("src/embedded.ts"), "embedded path remapped");
assert!(remapped.contains_key("src/loader.ts"), "loader-supplied path remapped");
assert!(remapped.contains_key("plain.js"), "no-map entry passes through");
}
#[test]
fn source_map_store_transforms_via_registered_map() {
let intermediate_js = "const x = 1;\nconst y = 2;\nconst z = 3;\n";
let original_ts = "const x: number = 1;\nconst y: number = 2;\nconst z: number = 3;\n";
let input_sm_json = format!(
r#"{{"version":3,"sources":["src/app.ts"],"sourcesContent":[{original_ts:?}],"mappings":"AAAA;AACA;AACA","names":[]}}"#,
);
let result =
instrument(intermediate_js, "intermediate.js", &InstrumentOptions::default()).unwrap();
let mut store = SourceMapStore::new();
assert!(store.is_empty(), "freshly constructed store is empty");
store.add_map(
"intermediate.js",
serde_json::from_str::<serde_json::Value>(&input_sm_json).unwrap(),
);
assert_eq!(store.len(), 1);
assert!(store.contains("intermediate.js"));
let remapped =
store.transform_coverage(&result.coverage_map).expect("store-supplied map applies");
assert_eq!(remapped.path, "src/app.ts");
}
#[test]
fn source_map_store_overrides_embedded_input_map() {
let intermediate_js = "const x = 1;\n";
let embedded_map_json =
r#"{"version":3,"sources":["src/embedded.ts"],"mappings":"AAAA","names":[]}"#;
let store_map_json = r#"{"version":3,"sources":["src/store.ts"],"mappings":"AAAA","names":[]}"#;
let opts = InstrumentOptions {
input_source_map: Some(embedded_map_json.to_string()),
..InstrumentOptions::default()
};
let result = instrument(intermediate_js, "intermediate.js", &opts).unwrap();
let mut store = SourceMapStore::new();
store.add_map("intermediate.js", serde_json::from_str(store_map_json).unwrap());
let remapped = store.transform_coverage(&result.coverage_map).expect("store applies");
assert_eq!(remapped.path, "src/store.ts", "store map wins over embedded map");
}
#[test]
fn source_map_store_falls_back_to_embedded_map_when_unregistered() {
let intermediate_js = "const x = 1;\n";
let embedded_map_json =
r#"{"version":3,"sources":["src/embedded.ts"],"mappings":"AAAA","names":[]}"#;
let opts = InstrumentOptions {
input_source_map: Some(embedded_map_json.to_string()),
..InstrumentOptions::default()
};
let result = instrument(intermediate_js, "intermediate.js", &opts).unwrap();
let store = SourceMapStore::new();
let remapped = store
.transform_coverage(&result.coverage_map)
.expect("embedded map is used when store has no entry");
assert_eq!(remapped.path, "src/embedded.ts");
}
#[test]
fn source_map_store_passes_through_when_no_map_available() {
let result = instrument("const x = 1;\n", "plain.js", &InstrumentOptions::default()).unwrap();
let store = SourceMapStore::new();
assert!(
store.transform_coverage(&result.coverage_map).is_none(),
"no map -> None mirrors remap_coverage"
);
let mut coverage: BTreeMap<String, FileCoverage> = BTreeMap::new();
coverage.insert("plain.js".to_string(), result.coverage_map);
let remapped = store.transform_coverage_map(&coverage);
assert!(remapped.contains_key("plain.js"), "passthrough preserves key");
}