use super::*;
#[test]
fn gene_level_row_round_trips() {
let row = feature_row("ENSG00000141510_TP53", M6A, METHYLATED, None);
assert_eq!(row.as_ref(), "ENSG00000141510_TP53/m6a/methylated");
let parsed = parse_feature_row(&row).unwrap();
assert_eq!(parsed.gene, "ENSG00000141510_TP53");
assert_eq!(parsed.modality, M6A);
assert_eq!(parsed.channel, METHYLATED);
assert_eq!(parsed.subunit, None);
assert_eq!(parsed.unit().as_ref(), "ENSG00000141510_TP53");
}
#[test]
fn subunit_row_round_trips_and_keeps_the_gene_recoverable() {
let site = feature_row("GENE", M6A, UNMETHYLATED, Some("chr17:7668402-7687550"));
assert_eq!(site.as_ref(), "GENE/m6a/chr17:7668402-7687550/unmethylated");
let p = parse_feature_row(&site).unwrap();
assert_eq!(p.subunit, Some("chr17:7668402-7687550"));
assert_eq!(p.channel, UNMETHYLATED);
assert_eq!(p.unit().as_ref(), "GENE/m6a/chr17:7668402-7687550");
assert_eq!(p.unit().split('/').next().unwrap(), "GENE");
let comp = feature_row("GENE", ATOI, EDITED, Some("3"));
assert_eq!(comp.as_ref(), "GENE/atoi/3/edited");
let p = parse_feature_row(&comp).unwrap();
assert_eq!(p.modality, ATOI);
assert_eq!(p.channel, EDITED);
assert_eq!(p.unit().as_ref(), "GENE/atoi/3");
}
#[test]
fn a_units_two_channels_share_a_contiguous_prefix() {
let m = feature_row("GENE", M6A, METHYLATED, Some("0"));
let u = feature_row("GENE", M6A, UNMETHYLATED, Some("0"));
assert_eq!(m.as_ref(), "GENE/m6a/0/methylated");
assert_eq!(u.as_ref(), "GENE/m6a/0/unmethylated");
let (pm, pu) = (
parse_feature_row(&m).unwrap(),
parse_feature_row(&u).unwrap(),
);
assert_eq!(pm.unit(), pu.unit());
assert_eq!(pm.unit().as_ref(), "GENE/m6a/0");
}
#[test]
fn count_and_other_modalities_use_the_same_shape() {
assert_eq!(
feature_row("GENE", COUNT, SPLICED, None).as_ref(),
"GENE/count/spliced"
);
assert_eq!(
feature_row("GENE", APA, PROXIMAL, Some("chr1:100-200")).as_ref(),
"GENE/apa/chr1:100-200/proximal"
);
assert_eq!(
feature_row("chr1:200", BAF, ALT, None).as_ref(),
"chr1:200/baf/alt"
);
assert_eq!(
feature_row("chr1:200", BAF, DEPTH, None).as_ref(),
"chr1:200/baf/depth"
);
}
#[test]
fn baf_rows_parse_with_the_locus_as_the_unit() {
let parsed = parse_feature_row("chr1:200/baf/alt").expect("3-field row parses");
assert_eq!(parsed.gene, "chr1:200");
assert_eq!(parsed.modality, BAF);
assert_eq!(parsed.channel, ALT);
assert_eq!(parsed.subunit, None);
assert_eq!(parsed.unit().as_ref(), "chr1:200");
let depth = parse_feature_row("chr1:200/baf/depth").expect("3-field row parses");
assert_eq!(depth.unit(), parsed.unit());
}
#[test]
fn rows_outside_three_or_four_fields_are_rejected() {
assert!(parse_feature_row("GENE").is_none());
assert!(parse_feature_row("GENE/m6a").is_none());
assert!(parse_feature_row("GENE/m6a/methylated").is_some());
assert!(parse_feature_row("GENE/m6a/0/methylated").is_some());
assert!(parse_feature_row("GENE/m6a/0/x/methylated").is_none());
}
fn names(v: &[&str]) -> Vec<Box<str>> {
v.iter().map(|s| Box::<str>::from(*s)).collect()
}
#[test]
fn splits_count_rows_into_gene_and_track() {
assert_eq!(
split_count_row("ENSG001_GENE1/count/spliced"),
Some(("ENSG001_GENE1", false))
);
assert_eq!(
split_count_row("ENSG001_GENE1/count/unspliced"),
Some(("ENSG001_GENE1", true))
);
}
#[test]
fn non_count_rows_are_rejected_not_silently_called_spliced() {
assert_eq!(split_count_row("ENSG001_GENE1/m6a/methylated"), None);
assert_eq!(split_count_row("ENSG001_GENE1/count/total"), None);
assert_eq!(
split_count_row("ENSG001_GENE1/count/chr1:100/spliced"),
None
);
assert_eq!(split_count_row("weird_name"), None);
}
#[test]
fn both_tracks_of_a_gene_share_one_id() {
let rows = names(&[
"A/count/spliced",
"B/count/unspliced",
"A/count/unspliced",
"B/count/spliced",
"C/count/spliced",
]);
for policy in [UnparsedRowPolicy::OwnGene, UnparsedRowPolicy::Reject] {
let map = intern_count_rows(&rows, policy);
assert_eq!(map.n_genes(), 3, "{policy:?}");
assert_eq!(
map.gene_names.as_slice(),
names(&["A", "B", "C"]).as_slice()
);
assert_eq!(map.row_to_gene, vec![0, 1, 0, 1, 2]);
assert_eq!(
map.row_is_nascent,
vec![false, true, true, false, false],
"nascent flags must follow the /count/unspliced suffix"
);
assert!(map.unparsed.is_empty());
assert_eq!(map.n_nascent_rows(), 2);
}
}
#[test]
fn the_policies_differ_only_on_an_unparsable_row() {
let rows = names(&["A/count/spliced", "A/count/total", "A/count/unspliced"]);
let keep = intern_count_rows(&rows, UnparsedRowPolicy::OwnGene);
assert_eq!(keep.n_genes(), 2, "the total row gets its own id");
assert_eq!(keep.gene_names[1].as_ref(), "A/count/total");
assert_ne!(
keep.row_to_gene[1], keep.row_to_gene[0],
"a `total` row summed into the spliced track double-counts the gene"
);
let strict = intern_count_rows(&rows, UnparsedRowPolicy::Reject);
assert_eq!(strict.n_genes(), 1, "only gene A is on the axis");
assert_eq!(strict.row_to_gene[1], NO_GENE);
assert_eq!(strict.unparsed, vec![1]);
for map in [&keep, &strict] {
assert_eq!(map.n_rows(), rows.len());
assert_eq!(map.row_is_nascent.len(), rows.len());
assert!(!map.row_is_nascent[1]);
}
}
#[test]
fn a_spliced_only_matrix_interns_cleanly_with_no_nascent_rows() {
let rows = names(&["A/count/spliced", "B/count/spliced"]);
let map = intern_count_rows(&rows, UnparsedRowPolicy::Reject);
assert_eq!(map.n_genes(), 2);
assert_eq!(map.n_nascent_rows(), 0);
assert!(map.unparsed.is_empty());
}
#[test]
fn a_unit_may_contain_slashes_because_gene_symbols_do() {
let row = feature_row("ENSG001_GENE1/GENE1B", COUNT, SPLICED, None);
assert_eq!(row.as_ref(), "ENSG001_GENE1/GENE1B/count/spliced");
let parsed = parse_feature_row(&row).unwrap();
assert_eq!(parsed.gene, "ENSG001_GENE1/GENE1B");
assert_eq!(parsed.modality, COUNT);
assert_eq!(parsed.channel, SPLICED);
assert_eq!(parsed.subunit, None);
assert_eq!(
split_count_row("ENSG001_GENE1/GENE1B/count/unspliced"),
Some(("ENSG001_GENE1/GENE1B", true))
);
let site = feature_row("A/B", M6A, METHYLATED, Some("chr1:100"));
let parsed = parse_feature_row(&site).unwrap();
assert_eq!(parsed.gene, "A/B");
assert_eq!(parsed.modality, M6A);
assert_eq!(parsed.subunit, Some("chr1:100"));
assert_eq!(parsed.unit().as_ref(), "A/B/m6a/chr1:100");
}
#[test]
fn an_unknown_modality_falls_back_to_the_positional_rule() {
let three = parse_feature_row("GENE/pileup/forward").unwrap();
assert_eq!(
(three.gene, three.modality, three.channel),
("GENE", "pileup", "forward")
);
assert_eq!(three.subunit, None);
let four = parse_feature_row("GENE/pileup/7/forward").unwrap();
assert_eq!(
(four.gene, four.modality, four.channel),
("GENE", "pileup", "forward")
);
assert_eq!(four.subunit, Some("7"));
assert!(parse_feature_row("GENE/pileup/7/x/forward").is_none());
assert!(parse_feature_row("GENE/count/7/x/spliced").is_none());
}