readsight 1.0.1

Multilingual readability library — 86 languages, 17 formulas, TeX-based syllable counting via the Frank M. Liang algorithm.
Documentation
//! Unit tests for the hyphenation layer (ported from the PHP suite).

use readsight::hyphenation::{
    parse_pattern_token, parse_tex, HyphenationExceptionsCollection, HyphenationOverride,
    Hyphenator, LiangHyphenator, Pattern, PatternsCollection,
};

fn empty_hyphenator(left: i32, right: i32) -> LiangHyphenator {
    LiangHyphenator::new(
        PatternsCollection::new(),
        HyphenationExceptionsCollection::new(),
        left,
        right,
    )
}

#[test]
fn empty_word_returns_empty() {
    assert_eq!(empty_hyphenator(2, 2).hyphenate(""), Vec::<String>::new());
}

#[test]
fn short_word_returned_as_is() {
    assert_eq!(empty_hyphenator(2, 2).hyphenate("it"), vec!["it"]);
}

#[test]
fn exception_word_respected() {
    let mut exceptions = HyphenationExceptionsCollection::new();
    exceptions.add(HyphenationOverride::new("associate", "as-so-ci-ate"));
    let h = LiangHyphenator::new(PatternsCollection::new(), exceptions, 2, 2);
    assert_eq!(h.hyphenate("associate"), vec!["as", "so", "ci", "ate"]);
    assert_eq!(h.count_syllables("associate"), 4);
}

#[test]
fn exception_case_insensitive() {
    let mut exceptions = HyphenationExceptionsCollection::new();
    exceptions.add(HyphenationOverride::new("table", "ta-ble"));
    let h = LiangHyphenator::new(PatternsCollection::new(), exceptions, 2, 2);
    assert_eq!(h.hyphenate("TABLE"), vec!["TA", "BLE"]);
}

#[test]
fn user_hyphenation_overrides_patterns() {
    let h = empty_hyphenator(2, 2);
    h.add_hyphenations([("custom", "cus-tom")]);
    assert_eq!(h.hyphenate("custom"), vec!["cus", "tom"]);
}

#[test]
fn count_syllables_edge_cases() {
    let h = empty_hyphenator(2, 2);
    assert_eq!(h.count_syllables(""), 0);
    assert_eq!(h.count_syllables("it"), 1);
}

#[test]
fn pattern_based_splitting() {
    let mut patterns = PatternsCollection::new();
    patterns.add(Pattern::new(vec!["a".into(), "n".into()], vec![0, 3, 0]));
    let h = LiangHyphenator::new(patterns, HyphenationExceptionsCollection::new(), 1, 1);
    assert_eq!(h.hyphenate("banana"), vec!["ba", "na", "na"]);
}

#[test]
fn pattern_fields() {
    let p = Pattern::new(vec!["a".into(), "c".into(), "h".into()], vec![0, 2, 0, 0]);
    assert_eq!(p.length, 3);
    assert_eq!(p.chars, vec!["a", "c", "h"]);
    assert_eq!(p.weights, vec![0, 2, 0, 0]);
}

#[test]
fn patterns_collection_semantics() {
    let mut c = PatternsCollection::new();
    assert!(c.is_empty());
    c.add(Pattern::new(
        vec!["a".into(), "c".into(), "h".into()],
        vec![0, 2, 0, 0],
    ));
    c.add(Pattern::new(vec![".".into(), "a".into()], vec![0, 0, 4]));
    assert_eq!(c.count(), 2);
    assert_eq!(c.max_length(), 3);
    assert_eq!(c.get_weights("ach"), Some(&[0, 2, 0, 0][..]));
    assert_eq!(c.get_weights("zz"), None);
    assert!(!c.is_empty());
}

#[test]
fn exceptions_collection_semantics() {
    let mut c = HyphenationExceptionsCollection::new();
    assert!(c.is_empty());
    c.add(HyphenationOverride::new("hyphenated", "hy-phen-ated"));
    assert!(c.has("hyphenated"));
    assert_eq!(c.get("hyphenated"), Some("hy-phen-ated"));
    assert_eq!(c.get("missing"), None);
    assert_eq!(c.count(), 1);
    assert!(!c.is_empty());
}

#[test]
fn parse_pattern_token_examples() {
    let a2ch = parse_pattern_token("a2ch").unwrap();
    assert_eq!(a2ch.chars, vec!["a", "c", "h"]);
    assert_eq!(a2ch.weights, vec![0, 2, 0, 0]);

    let dot = parse_pattern_token(".ach4").unwrap();
    assert_eq!(dot.chars, vec![".", "a", "c", "h"]);
    assert_eq!(dot.weights, vec![0, 0, 0, 0, 4]);

    assert!(parse_pattern_token("abc").is_none()); // no digit
    assert!(parse_pattern_token("123").is_none()); // no chars
    assert!(parse_pattern_token("").is_none());
}

#[test]
fn parse_tex_patterns_and_hyphenation() {
    let tex = "\\patterns{\na1b\n.ach4\n}\n\\hyphenation{\nas-so-ciate\n}\n% a comment\n";
    let loaded = parse_tex(tex);
    assert!(loaded.patterns.count() >= 2);
    assert_eq!(loaded.patterns.get_weights("ab"), Some(&[0, 1, 0][..]));
    assert_eq!(loaded.max_pattern_length, loaded.patterns.max_length());
    assert!(loaded.exceptions.has("associate"));
    assert_eq!(loaded.exceptions.get("associate"), Some("as-so-ciate"));
}

#[test]
fn parse_tex_ignores_comments_and_other_commands() {
    let tex = "% comment line\n\\message{hello}\n\\patterns{\na1b\n}\n";
    let loaded = parse_tex(tex);
    assert_eq!(loaded.patterns.get_weights("ab"), Some(&[0, 1, 0][..]));
}

#[test]
fn parse_tex_stray_backslash_and_braces() {
    // A backslash not followed by a letter is skipped; stray braces are ignored.
    let tex = "\\5 not a command } { \n\\patterns{\na1b\n}\n";
    let loaded = parse_tex(tex);
    assert_eq!(loaded.patterns.get_weights("ab"), Some(&[0, 1, 0][..]));
}

#[test]
fn split_by_hyphenation_trailing_hyphen_and_overflow() {
    let h = empty_hyphenator(1, 1);
    // trailing hyphen -> final empty part is dropped
    h.add_hyphenations([("abc", "a-bc-")]);
    assert_eq!(h.hyphenate("abc"), vec!["a", "bc"]);
    // more hyphenated letters than the original word -> extras ignored
    h.add_hyphenations([("abcd", "a-b-c-d-e")]);
    assert_eq!(h.hyphenate("abcd"), vec!["a", "b", "c", "d"]);
}