use super::super::decode;
use super::super::state::WordInfo;
use super::super::tokenizer::Tokenizer;
use super::super::windows::CHUNK_OVERLAP_SAMPLES;
use super::super::{ENCODER_SUBSAMPLING, HOP_LENGTH};
use super::*;
#[test]
fn test_token_formatter_groups_words() {
let tok = Tokenizer::from_tokens(vec![
"\u{2581}hel".into(), "lo".into(), "\u{2581}wor".into(), "ld".into(), ]);
let tokens = vec![
decode::TokenInfo {
token_id: 0,
frame_index: 0,
confidence: 0.9,
},
decode::TokenInfo {
token_id: 1,
frame_index: 1,
confidence: 0.8,
},
decode::TokenInfo {
token_id: 2,
frame_index: 2,
confidence: 0.95,
},
decode::TokenInfo {
token_id: 3,
frame_index: 3,
confidence: 0.85,
},
];
let words = TokenFormatter::tokens_to_words(&tok, &tokens, 0);
assert_eq!(words.len(), 2);
assert_eq!(words[0].word, "hello");
assert_eq!(words[1].word, "world");
assert!((words[0].confidence - 0.85).abs() < 1e-6);
assert!((words[1].confidence - 0.90).abs() < 1e-6);
assert!((words[0].start - 0.0).abs() < 1e-9);
assert!((words[0].end - 0.04).abs() < 1e-9);
assert!((words[1].start - 0.08).abs() < 1e-9);
}
#[test]
fn test_token_formatter_empty_tokens() {
let tok = Tokenizer::from_tokens(vec!["\u{2581}a".into()]);
assert!(TokenFormatter::tokens_to_words(&tok, &[], 0).is_empty());
}
#[test]
fn test_token_formatter_frame_offset_shifts_time() {
let tok = Tokenizer::from_tokens(vec!["\u{2581}x".into()]);
let tokens = vec![decode::TokenInfo {
token_id: 0,
frame_index: 0,
confidence: 1.0,
}];
let words = TokenFormatter::tokens_to_words(&tok, &tokens, 10);
assert_eq!(words.len(), 1);
assert!((words[0].start - 0.4).abs() < 1e-9);
}
fn word(text: &str, start: f64, end: f64) -> WordInfo {
WordInfo::new(text, start, end, 1.0, None)
}
#[test]
fn test_stitch_first_chunk_passes_through() {
let next = vec![word("a", 0.0, 0.5), word("b", 0.6, 1.0)];
let out = stitch_chunk_words(Vec::new(), next.clone(), 11.0);
assert_eq!(out.len(), 2);
assert_eq!(out[0].word, "a");
assert_eq!(out[1].word, "b");
}
#[test]
fn test_stitch_dedups_overlap_no_drop_no_dup() {
let chunk_a = vec![
word("first", 1.0, 1.4), word("middle", 21.0, 21.4), word("dup", 22.4, 22.8), ];
let chunk_b = vec![
word("dup", 22.5, 22.9), word("later", 25.0, 25.4), word("end", 40.0, 40.4), ];
let seam_s = 22.0 + CHUNK_OVERLAP_SAMPLES as f64 / 2.0 / 16000.0; assert!((seam_s - 23.0).abs() < 1e-9);
let out = stitch_chunk_words(chunk_a, chunk_b, seam_s);
let texts: Vec<&str> = out.iter().map(|w| w.word.as_str()).collect();
assert_eq!(texts, vec!["first", "middle", "dup", "later", "end"]);
for w in out.windows(2) {
assert!(w[0].start <= w[1].start, "not monotonic: {:?}", out);
}
}
#[test]
fn test_stitch_drops_a_tail_past_seam() {
let chunk_a = vec![word("keep", 22.0, 22.4), word("a_tail", 23.5, 23.9)];
let chunk_b = vec![word("b_seam", 23.2, 23.6), word("b_late", 30.0, 30.4)];
let out = stitch_chunk_words(chunk_a, chunk_b, 23.0);
let texts: Vec<&str> = out.iter().map(|w| w.word.as_str()).collect();
assert_eq!(texts, vec!["keep", "b_seam", "b_late"]);
}
#[test]
fn test_stitch_straddling_word_duplicated_across_seam() {
let chunk_a = vec![word("на", 22.0, 22.32), word("мосту", 22.96, 23.36)];
let chunk_b = vec![word("мосту", 23.04, 23.44), word("стоял", 24.0, 24.4)];
let out = stitch_chunk_words(chunk_a, chunk_b, 23.0);
let texts: Vec<&str> = out.iter().map(|w| w.word.as_str()).collect();
assert_eq!(
texts,
vec!["на", "мосту", "мосту", "стоял"],
"a straddling word whose copies land on opposite sides of the seam is duplicated"
);
}
#[test]
fn test_stitch_straddling_word_deleted_at_seam() {
let chunk_a = vec![word("на", 22.0, 22.32), word("мосту", 23.04, 23.44)];
let chunk_b = vec![word("мосту", 22.96, 23.36), word("стоял", 24.0, 24.4)];
let out = stitch_chunk_words(chunk_a, chunk_b, 23.0);
let texts: Vec<&str> = out.iter().map(|w| w.word.as_str()).collect();
assert_eq!(
texts,
vec!["на", "стоял"],
"a straddling word can vanish entirely: dropped by both sides of the seam"
);
}
#[test]
fn test_stitch_word_exactly_on_seam_kept_from_earlier_chunk() {
let chunk_a = vec![WordInfo::new("шов", 23.0, 23.4, 0.5, None)];
let chunk_b = vec![
WordInfo::new("шов", 23.0, 23.4, 0.9, None),
word("после", 24.0, 24.4),
];
let out = stitch_chunk_words(chunk_a, chunk_b, 23.0);
let texts: Vec<&str> = out.iter().map(|w| w.word.as_str()).collect();
assert_eq!(
texts,
vec!["шов", "после"],
"no duplicate exactly on the seam"
);
assert_eq!(
out[0].confidence, 0.5,
"the surviving copy comes from the earlier chunk"
);
}
#[test]
fn test_stitch_empty_next_chunk_still_trims_tail_past_seam() {
let chunk_a = vec![word("до", 22.0, 22.4), word("хвост", 23.04, 23.44)];
let out = stitch_chunk_words(chunk_a, Vec::new(), 23.0);
let texts: Vec<&str> = out.iter().map(|w| w.word.as_str()).collect();
assert_eq!(
texts,
vec!["до"],
"an empty chunk still drops the earlier chunk's post-seam tail"
);
}
#[test]
fn test_stitch_silence_at_seam_loses_nothing() {
let chunk_a = vec![word("перед", 22.6, 22.9)];
let chunk_b = vec![word("после", 23.1, 23.5), word("конец", 24.0, 24.4)];
let out = stitch_chunk_words(chunk_a, chunk_b, 23.0);
let texts: Vec<&str> = out.iter().map(|w| w.word.as_str()).collect();
assert_eq!(texts, vec!["перед", "после", "конец"]);
for w in out.windows(2) {
assert!(w[0].start <= w[1].start, "not monotonic: {:?}", out);
}
}
#[test]
fn test_stitch_truncate_matches_retain_predicate() {
let merged: Vec<WordInfo> = (0..40)
.map(|i| word(&format!("w{i}"), i as f64 * 0.5, i as f64 * 0.5 + 0.3))
.collect();
for step in 0..=80 {
let seam_s = step as f64 * 0.25;
let mut expected = merged.clone();
expected.retain(|w| w.start <= seam_s);
let got = stitch_chunk_words(merged.clone(), Vec::new(), seam_s);
assert_eq!(
got.iter().map(|w| w.word.as_str()).collect::<Vec<_>>(),
expected.iter().map(|w| w.word.as_str()).collect::<Vec<_>>(),
"diverged at seam {seam_s}"
);
}
}
#[test]
fn test_stitch_timestamp_offset_math() {
let tok = Tokenizer::from_tokens(vec!["\u{2581}w".into()]);
let tokens = vec![decode::TokenInfo {
token_id: 0,
frame_index: 0,
confidence: 1.0,
}];
let start_samples = 16000 * 22; let frame_offset = start_samples / (HOP_LENGTH * ENCODER_SUBSAMPLING);
let words = TokenFormatter::tokens_to_words(&tok, &tokens, frame_offset);
assert_eq!(words.len(), 1);
assert!(
(words[0].start - 22.0).abs() < 1e-9,
"got {}",
words[0].start
);
}
#[test]
fn test_token_formatter_last_word_empty_confidences_defaults_to_one() {
let tok = Tokenizer::from_tokens(vec![
"\u{2581}real".into(), "\u{2581}".into(), ]);
let tokens = vec![
decode::TokenInfo {
token_id: 0,
frame_index: 0,
confidence: 0.7,
},
decode::TokenInfo {
token_id: 1,
frame_index: 1,
confidence: 0.5,
},
];
let words = TokenFormatter::tokens_to_words(&tok, &tokens, 0);
assert_eq!(words.len(), 1);
assert_eq!(words[0].word, "real");
assert!((words[0].confidence - 0.7).abs() < 1e-6);
}