use core::str::FromStr;
use std::{
collections::{BTreeMap, BTreeSet},
string::{String, ToString},
vec::Vec,
};
use smol_bytes::Utf8Bytes;
use super::{Language, ParseLanguageError};
use crate::lang::registry;
fn door(sent: &str) -> Result<Language, ParseLanguageError> {
Language::new(sent)
}
fn admitted(sent: &str) -> Language {
door(sent).unwrap_or_else(|refused| panic!("`{sent}` is a language subtag: {refused}"))
}
#[test]
fn the_canonical_form_is_the_shortest_bcp47_spelling() {
for (sent, canonical) in [
("de", "de"),
("zh", "zh"),
("yue", "yue"),
("fil", "fil"),
("und", "und"),
] {
assert_eq!(admitted(sent).as_str(), canonical, "`{sent}`");
}
}
#[test]
fn the_door_folds_every_spelling_the_ruling_names() {
for (sent, canonical, why) in [
("DE", "de", "upper case"),
("De", "de", "mixed case"),
("YUE", "yue", "upper case, three letters"),
("UND", "und", "the sentinel is folded like anything else"),
("ger", "de", "bibliographic"),
("GER", "de", "bibliographic, upper case"),
("fre", "fr", "bibliographic"),
("chi", "zh", "bibliographic"),
("deu", "de", "terminological"),
("fra", "fr", "terminological"),
("zho", "zh", "terminological"),
("iw", "he", "deprecated"),
("IW", "he", "deprecated, upper case"),
("in", "id", "deprecated"),
("ji", "yi", "deprecated"),
("mo", "ro", "deprecated"),
] {
assert_eq!(admitted(sent).as_str(), canonical, "`{sent}` — {why}");
}
}
#[test]
fn a_subtag_that_needs_no_fold_is_held_as_it_arrived() {
for already in ["de", "en", "fr", "zh", "yue", "haw", "und"] {
assert_eq!(admitted(already).as_str(), already);
assert!(admitted(already).is_registered(), "`{already}`");
}
}
#[test]
fn two_containers_spellings_of_one_language_are_equal() {
let mkv = admitted("ger");
let mp4 = admitted("deu");
let bcp47 = admitted("de");
assert_eq!(mkv, mp4);
assert_eq!(mp4, bcp47);
assert_eq!(mkv, bcp47);
let mut distinct = BTreeSet::new();
distinct.insert(mkv);
distinct.insert(mp4);
distinct.insert(bcp47);
assert_eq!(distinct.len(), 1, "one language, one hash bucket");
}
#[test]
fn undetermined_is_a_value_and_not_an_absence() {
let undetermined = Language::UND;
assert_eq!(undetermined.as_str(), "und");
assert!(undetermined.is_undetermined());
assert!(undetermined.is_registered(), "the registry registers `und`");
assert_eq!(undetermined.name(), Some("Undetermined"));
assert_eq!(admitted("und"), undetermined);
assert!(!admitted("de").is_undetermined());
let absent: Option<Language> = None;
let determined: Option<Language> = Some(undetermined);
assert_ne!(absent, determined, "no language is not `und`");
}
#[test]
fn an_unregistered_subtag_is_admitted_and_named_as_such() {
for unknown in ["xyz", "qqq", "abcdefgh"] {
let held = admitted(unknown);
assert_eq!(held.as_str(), unknown, "held as it arrived");
assert!(!held.is_registered(), "`{unknown}`");
assert_eq!(held.name(), None, "`{unknown}`");
}
let private = admitted("qaa");
assert!(private.is_private_use());
assert!(!private.is_registered());
let typo = admitted("xyz");
assert!(!typo.is_private_use());
assert!(!typo.is_registered());
}
#[test]
fn a_structural_violation_is_refused_by_name() {
for (sent, expected) in [
("", ParseLanguageError::Empty),
("e", ParseLanguageError::TooShort),
("abcdefghi", ParseLanguageError::TooLong),
("zh_CN", ParseLanguageError::NotAlphabetic('_')),
("zh-CN", ParseLanguageError::NotAlphabetic('-')),
("d3", ParseLanguageError::NotAlphabetic('3')),
("419", ParseLanguageError::NotAlphabetic('4')),
("dé", ParseLanguageError::NotAlphabetic('é')),
("日本語", ParseLanguageError::NotAlphabetic('日')),
(" de", ParseLanguageError::NotAlphabetic(' ')),
] {
assert_eq!(door(sent), Err(expected), "`{sent}`");
}
}
#[test]
fn a_non_ascii_input_is_refused_for_its_characters_and_not_its_width() {
assert_eq!("日本語".len(), 9, "wider than the eight-byte ceiling");
assert_eq!(door("日本語"), Err(ParseLanguageError::NotAlphabetic('日')));
assert_eq!(door("abcdefghi"), Err(ParseLanguageError::TooLong));
assert!(door("abcdefgh").is_ok(), "eight is the ceiling, inclusive");
}
#[test]
fn a_canonical_subtag_is_a_fixpoint_and_the_fold_collides_nothing() {
let mut reached: BTreeMap<String, &str> = BTreeMap::new();
for (subtag, _) in registry::table::LANGUAGES {
let subtag = subtag.as_str();
let held = admitted(subtag);
match registry::language_preferred(subtag) {
Some(preferred) => assert_eq!(held.as_str(), preferred, "`{subtag}`"),
None => {
assert_eq!(held.as_str(), subtag, "`{subtag}` is not a fixpoint");
assert_eq!(
reached.insert(String::from(held.as_str()), subtag),
None,
"two languages folded onto `{}`",
held.as_str()
);
}
}
assert_eq!(
admitted(held.as_str()),
held,
"`{subtag}` is not idempotent"
);
}
assert_eq!(reached.len(), registry::LANGUAGE_COUNT - 112);
}
#[test]
fn every_iso_639_2_code_reaches_a_registered_language() {
for (code, shortest) in registry::table::ALPHA3 {
let code = code.as_str();
let held = admitted(code);
assert_eq!(held.as_str(), *shortest, "`{code}`");
assert!(held.is_registered(), "`{code}` reached `{shortest}`");
assert_eq!(
admitted(&code.to_ascii_uppercase()),
held,
"`{code}` upper-cased"
);
}
}
#[test]
fn the_rendering_and_the_parse_are_inverse() {
for sent in ["de", "GER", "iw", "yue", "und", "qaa", "xyz"] {
let held = admitted(sent);
let rendered = held.to_string();
assert_eq!(Language::from_str(&rendered).expect("canonical text"), held);
assert_eq!(rendered, held.as_str());
}
}
#[test]
fn the_debug_face_prints_the_subtag() {
assert_eq!(std::format!("{:?}", admitted("ger")), r#"Language("de")"#);
}
#[test]
fn a_canonical_value_can_still_be_deprecated() {
let orphaned = registry::table::LANGUAGE_DEPRECATED
.iter()
.copied()
.find(|subtag| registry::language_preferred(subtag).is_none())
.expect("120 of them");
let held = admitted(orphaned);
assert_eq!(held.as_str(), orphaned);
assert!(held.is_deprecated());
assert!(held.is_registered());
let replaced = admitted("iw");
assert_eq!(replaced.as_str(), "he");
assert!(!replaced.is_deprecated(), "the value it became is current");
}
#[test]
fn a_language_publishes_the_script_it_implies() {
assert_eq!(admitted("en").suppressed_script(), Some("Latn"));
assert_eq!(admitted("he").suppressed_script(), Some("Hebr"));
assert_eq!(
admitted("iw").suppressed_script(),
Some("Hebr"),
"after the fold"
);
assert_eq!(admitted("zh").suppressed_script(), None);
assert_eq!(admitted("xyz").suppressed_script(), None, "unregistered");
}
#[test]
fn the_text_seat_converts_both_ways() {
let held = admitted("ger");
assert_eq!(held.as_ref() as &str, "de");
assert_eq!(String::from(held), "de");
assert_eq!(Utf8Bytes::from(held), Utf8Bytes::from("de"));
}
#[test]
fn ordering_is_alphabetical_over_the_canonical_spelling() {
let mut sorted = [
admitted("zh"),
admitted("ger"),
admitted("en"),
admitted("fr"),
];
sorted.sort();
let spellings: Vec<&str> = sorted.iter().map(Language::as_str).collect();
assert_eq!(spellings, ["de", "en", "fr", "zh"]);
}