use std::rc::Rc;
use readsight::hyphenation::{
HyphenationExceptionsCollection, HyphenationOverride, LiangHyphenator, PatternsCollection,
};
use readsight::syllable::{
CompositeSyllableCounter, HeuristicSyllableCounter, SyllableCounter, TexSyllableCounter,
};
use serde_json::{json, Value};
fn counter(cfg: &Value) -> HeuristicSyllableCounter {
HeuristicSyllableCounter::new(Some(cfg))
}
fn full_config() -> Value {
json!({
"problemWords": {"banana": 3, "beautiful": 3, "extraordinary": 6},
"subtractPatterns": ["cial", "tia", "cious", "eous$"],
"addPatterns": ["ia", "io", "iu"],
"prefixes": {"un": 1, "pre": 1, "dis": 1},
"suffixes": {"ly": 1, "tion": 1, "ment": 1},
"vowelPattern": "[aeiouy]"
})
}
#[test]
fn has_rules_variants() {
assert!(counter(&full_config()).has_rules());
assert!(counter(&json!({
"problemWords": {}, "subtractPatterns": ["cial"], "addPatterns": [],
"prefixes": {}, "suffixes": {}, "vowelPattern": "[aeiouy]"
}))
.has_rules());
assert!(!HeuristicSyllableCounter::new(None).has_rules());
assert!(!counter(&json!({
"problemWords": {}, "subtractPatterns": [], "addPatterns": [],
"prefixes": {}, "suffixes": {}, "vowelPattern": "[aeiouy]"
}))
.has_rules());
}
#[test]
fn has_word_cases() {
let c = counter(&full_config());
assert!(c.has_word("banana"));
assert!(c.has_word("BANANA"));
assert!(c.has_word("Beautiful"));
assert!(!c.has_word("computer"));
assert!(!c.has_word(""));
assert!(!c.has_word(" "));
}
#[test]
fn problem_words_exact_count() {
let c = counter(&full_config());
assert_eq!(c.count_syllables("banana"), 3);
assert_eq!(c.count_syllables("extraordinary"), 6);
assert_eq!(c.count_syllables("BaNaNa"), 3);
}
#[test]
fn vowel_counting_basic() {
let c = counter(&json!({"vowelPattern": "[aeiouy]"}));
assert_eq!(c.count_syllables("cat"), 1);
assert_eq!(c.count_syllables("table"), 2);
assert_eq!(c.count_syllables("computer"), 3);
}
#[test]
fn never_returns_zero_but_empty_is_zero() {
let c = counter(&full_config());
assert_eq!(c.count_syllables("bcd"), 1);
assert_eq!(c.count_syllables(""), 0);
assert_eq!(c.count_syllables(" "), 0);
}
#[test]
fn cluster_vs_individual_mode() {
let vowels = "[аеёиоуыэюяій]";
let cluster = counter(&json!({"vowelPattern": vowels, "vowelMode": "cluster"}));
let individual = counter(&json!({"vowelPattern": vowels, "vowelMode": "individual"}));
let default = counter(&json!({"vowelPattern": vowels}));
assert_eq!(cluster.count_syllables("дыхание"), 3);
assert_eq!(individual.count_syllables("дыхание"), 4);
assert_eq!(default.count_syllables("дыхание"), 3); assert_eq!(individual.count_syllables("молоко"), 3);
}
#[test]
fn affixes_add_syllables() {
let pre = counter(&json!({"prefixes": {"un": 1}, "vowelPattern": "[aeiouy]"}));
assert_eq!(pre.count_syllables("undo"), 2);
let suf = counter(&json!({"suffixes": {"ly": 1}, "vowelPattern": "[aeiouy]"}));
assert_eq!(suf.count_syllables("slowly"), 2);
}
#[test]
fn subtract_and_add_patterns() {
let sub = counter(&json!({"subtractPatterns": ["eous$"], "vowelPattern": "[aeiouy]"}));
assert!(sub.count_syllables("courageous") >= 2);
let add = counter(&json!({"addPatterns": ["ia", "io"], "vowelPattern": "[aeiouy]"}));
assert!(add.count_syllables("radio") >= 3);
}
#[test]
fn backreference_add_pattern_compiles() {
let c = counter(&json!({"addPatterns": ["([^aeiouy])\\1l$"], "vowelPattern": "[aeiouy]"}));
let with = c.count_syllables("little");
assert!(with >= 1);
}
#[test]
fn split_syllables_behaviour() {
let c = counter(&full_config());
assert_eq!(c.split_syllables("cat"), vec!["cat"]);
assert_eq!(c.split_syllables(""), Vec::<String>::new());
let word = "communication";
assert_eq!(
c.split_syllables(word).len() as i64,
c.count_syllables(word)
);
}
#[test]
fn default_vowel_pattern_when_absent() {
let c = counter(&json!({"problemWords": {}}));
assert_eq!(c.count_syllables("cat"), 1);
assert_eq!(c.count_syllables("table"), 2);
}
#[test]
fn split_syllables_more_syllables_than_chars() {
let c = counter(&json!({"vowelPattern": "[aeiou]", "vowelMode": "individual"}));
assert_eq!(c.split_syllables("aei"), vec!["a", "e", "i"]);
}
#[test]
fn non_letter_word_returns_one() {
let c = counter(&full_config());
assert_eq!(c.count_syllables("123"), 1);
assert_eq!(c.count_syllables("!!!"), 1);
}
#[test]
fn invalid_vowel_pattern_falls_back() {
let c = counter(&json!({"vowelPattern": "[z-a]"}));
assert_eq!(c.count_syllables("cat"), 1);
assert_eq!(c.count_syllables("table"), 2);
}
fn tex_counter() -> TexSyllableCounter {
let mut exceptions = HyphenationExceptionsCollection::new();
exceptions.add(HyphenationOverride::new("associate", "as-so-ci-ate"));
let h = Rc::new(LiangHyphenator::new(
PatternsCollection::new(),
exceptions,
2,
2,
));
TexSyllableCounter::new(h)
}
#[test]
fn tex_counter_delegates() {
let tex = tex_counter();
assert_eq!(tex.count_syllables("associate"), 4);
assert_eq!(
tex.split_syllables("associate"),
vec!["as", "so", "ci", "ate"]
);
}
#[test]
fn composite_uses_heuristic_when_rules_present() {
let composite = CompositeSyllableCounter::new(counter(&full_config()), tex_counter());
assert_eq!(composite.count_syllables("banana"), 3);
assert_eq!(composite.split_syllables("banana").len(), 3);
}
#[test]
fn composite_falls_back_to_tex_without_rules() {
let empty = HeuristicSyllableCounter::new(None);
let composite = CompositeSyllableCounter::new(empty, tex_counter());
assert_eq!(composite.count_syllables("associate"), 4);
assert_eq!(
composite.split_syllables("associate"),
vec!["as", "so", "ci", "ate"]
);
}