use super::find_named;
use crate::chunk::{AtomChunk, AtomRecord, ChunkBuilder};
use crate::column::Presence;
use crate::element::Element;
use crate::optional::OptionalSymbol;
use crate::structure::handle::AtomRef;
use crate::symbol::AltId;
fn chain(residues: u32, target: u32) -> (Vec<crate::chunk::AtomChunk>, crate::symbol::Interner) {
let mut dictionary = crate::symbol::Interner::new();
let mut atoms = Vec::new();
for residue in 0..residues {
for name in ["N", "CA", "C", "O"] {
let Ok(atom_name) = dictionary.intern(name) else {
panic!("fixture name interns")
};
atoms.push(AtomRecord {
element: Element::CARBON,
atom_name,
auth_atom_name: OptionalSymbol::NONE,
alternate_component_id: OptionalSymbol::NONE,
alt_id: AltId::BLANK,
residue: crate::index::ResidueIndex::new(residue),
occupancy: (1.0, Presence::Present),
b_factor: (20.0, Presence::Present),
position: Some([0.0, 0.0, 0.0]),
formal_charge: (0, Presence::Unknown),
atom_site_id: residue,
});
}
}
let mut builder = ChunkBuilder::with_target(target);
builder.start_model(0);
for atom in atoms {
builder.push(atom);
}
let (chunks, _) = builder.finish();
(chunks, dictionary)
}
#[test]
fn the_lookup_finds_each_name_in_every_residue_across_many_chunks() {
let (chunks, dictionary) = chain(12, 7);
assert!(
chunks.len() > 1,
"the fixture must span several chunks, found {}",
chunks.len()
);
let Some(ca) = dictionary.get("CA") else {
panic!("the fixture interns CA")
};
let Some(absent) = dictionary.get("ZZ") else {
return;
};
for residue in 0..12 {
let range = residue * 4..residue * 4 + 4;
assert_eq!(
find_named(&chunks, range.clone(), ca),
Some(residue * 4 + 1),
"residue {residue} must report its own CA"
);
assert_eq!(
find_named(&chunks, range, absent),
None,
"an absent name selects nothing"
);
}
}
#[test]
fn the_lookup_reports_the_first_match_when_a_name_repeats() {
let (chunks, dictionary) = chain(4, 3);
let Some(oxygen) = dictionary.get("O") else {
panic!("the fixture interns O")
};
assert_eq!(find_named(&chunks, 0..4, oxygen), Some(3));
}
#[test]
fn the_lookup_declines_a_range_that_reaches_past_every_chunk() {
let (chunks, dictionary) = chain(2, 3);
let Some(nitrogen) = dictionary.get("N") else {
panic!("the fixture interns N")
};
let total = chunks.last().map_or(0, |chunk| chunk.atoms().end);
assert_eq!(find_named(&chunks, total..total + 4, nitrogen), None);
}
#[test]
fn every_residue_stays_inside_one_chunk_which_is_what_the_cursor_relies_on() {
let (chunks, _) = chain(12, 7);
for (position, chunk) in chunks.iter().enumerate() {
assert_eq!(
chunk.len() % 4,
0,
"chunk {position} holds a partial residue: {} atoms",
chunk.len()
);
}
}
#[test]
fn a_lookup_still_finds_a_single_residue_chunk() {
let (chunks, dictionary) = chain(3, 1);
let Some(nitrogen) = dictionary.get("N") else {
panic!("the fixture interns N")
};
assert_eq!(find_named(&chunks, 4..8, nitrogen), Some(4));
}
#[test]
fn a_residue_built_by_the_hierarchy_fixture_reports_its_named_atom() {
let structure = crate::structure::fixture::sample();
let data = structure.data();
let Some(model) = data.model(crate::index::ModelIndex::new(0)) else {
panic!("the fixture has a model")
};
let Some(chain) = model.chain("A") else {
panic!("the fixture has chain A")
};
let Some(residue) = chain.residue(100) else {
panic!("the fixture has residue 100")
};
let atom = residue.atom("CA");
assert_eq!(atom.and_then(AtomRef::name), Some("CA"));
assert!(residue.atom("ZZ").is_none());
}
#[test]
fn the_chunks_tile_the_atom_order_without_a_gap() {
let (chunks, _) = chain(6, 5);
let total: u32 = chunks.iter().map(AtomChunk::len).sum();
assert_eq!(total, 24, "six residues of four atoms");
let mut seen = 0_u32;
for chunk in &chunks {
let atoms = chunk.atoms();
assert_eq!(atoms.start, seen, "chunks tile the order without a gap");
seen = atoms.end;
}
assert_eq!(seen, total);
}