use core::str::FromStr;
use std::{collections::BTreeSet, string::ToString, vec::Vec};
use super::{ParseScriptSubtagError, ScriptSubtag};
use crate::lang::registry;
fn door(sent: &str) -> Result<ScriptSubtag, ParseScriptSubtagError> {
ScriptSubtag::new(sent)
}
fn admitted(sent: &str) -> ScriptSubtag {
door(sent).unwrap_or_else(|refused| panic!("`{sent}` is a script subtag: {refused}"))
}
#[test]
fn the_canonical_form_is_titlecase_and_the_door_folds_every_case() {
for (sent, canonical) in [
("Latn", "Latn"),
("latn", "Latn"),
("LATN", "Latn"),
("lAtN", "Latn"),
("hans", "Hans"),
("HANT", "Hant"),
("cyrl", "Cyrl"),
("zxxx", "Zxxx"),
("ZZZZ", "Zzzz"),
] {
assert_eq!(admitted(sent).as_str(), canonical, "`{sent}`");
}
}
#[test]
fn simplified_and_traditional_han_are_two_scripts() {
let simplified = admitted("Hans");
let traditional = admitted("Hant");
assert_ne!(simplified, traditional);
assert_ne!(simplified.as_str(), traditional.as_str());
assert!(simplified.is_registered() && traditional.is_registered());
let mut distinct = BTreeSet::new();
distinct.insert(simplified);
distinct.insert(traditional);
assert_eq!(distinct.len(), 2, "two scripts, two hash buckets");
assert_eq!(simplified.name(), Some("Han (Simplified variant)"));
assert_eq!(traditional.name(), Some("Han (Traditional variant)"));
let unified = admitted("Hani");
assert_ne!(unified, simplified);
assert_ne!(unified, traditional);
}
#[test]
fn a_structural_violation_is_refused_by_name() {
for (sent, expected) in [
("", ParseScriptSubtagError::Empty),
("Lat", ParseScriptSubtagError::WrongWidth),
("Latin", ParseScriptSubtagError::WrongWidth),
("L", ParseScriptSubtagError::WrongWidth),
("Lat_", ParseScriptSubtagError::NotAlphabetic('_')),
("Lat1", ParseScriptSubtagError::NotAlphabetic('1')),
("Latn-", ParseScriptSubtagError::NotAlphabetic('-')),
("漢字", ParseScriptSubtagError::NotAlphabetic('漢')),
] {
assert_eq!(door(sent), Err(expected), "`{sent}`");
}
}
#[test]
fn a_non_ascii_input_is_refused_for_its_characters() {
assert_eq!("漢字".len(), 6, "and not four");
assert_eq!(
door("漢字"),
Err(ParseScriptSubtagError::NotAlphabetic('漢'))
);
}
#[test]
fn the_two_sentinels_are_values_and_not_an_absence() {
let unwritten = ScriptSubtag::ZXXX;
let uncoded = ScriptSubtag::ZZZZ;
assert_eq!(unwritten.as_str(), "Zxxx");
assert_eq!(uncoded.as_str(), "Zzzz");
assert!(unwritten.is_unwritten() && !unwritten.is_uncoded());
assert!(uncoded.is_uncoded() && !uncoded.is_unwritten());
assert!(unwritten.is_registered() && uncoded.is_registered());
assert_ne!(unwritten, uncoded, "unwritten is not uncoded");
assert_eq!(
admitted("zxxx"),
unwritten,
"the door folds them like any other"
);
assert_eq!(admitted("ZZZZ"), uncoded);
let absent: Option<ScriptSubtag> = None;
assert_ne!(absent, Some(unwritten), "no script is not `Zxxx`");
}
#[test]
fn an_unregistered_subtag_is_admitted_and_the_private_range_is_named() {
let unknown = admitted("Abcd");
assert_eq!(unknown.as_str(), "Abcd");
assert!(!unknown.is_registered());
assert!(!unknown.is_private_use());
assert_eq!(unknown.name(), None);
for private in ["Qaaa", "qaaa", "QABX", "Qaam"] {
let held = admitted(private);
assert!(held.is_private_use(), "`{private}`");
assert!(!held.is_registered(), "`{private}`");
}
assert!(!admitted("Qaby").is_private_use());
}
#[test]
fn every_registered_script_is_a_fixpoint_and_the_fold_collides_nothing() {
let mut reached = BTreeSet::new();
for (subtag, _) in registry::table::SCRIPTS {
let subtag = subtag.as_str();
let held = admitted(subtag);
assert_eq!(held.as_str(), subtag, "`{subtag}` is not a fixpoint");
assert!(held.is_registered(), "`{subtag}`");
assert_eq!(
admitted(&subtag.to_ascii_uppercase()),
held,
"`{subtag}` upper-cased"
);
assert_eq!(
admitted(&subtag.to_ascii_lowercase()),
held,
"`{subtag}` lower-cased"
);
assert!(reached.insert(held), "two scripts folded onto `{subtag}`");
}
assert_eq!(reached.len(), registry::SCRIPT_COUNT);
}
#[test]
fn the_rendering_and_the_parse_are_inverse() {
for sent in ["Latn", "hans", "ZZZZ", "Qaaa", "Abcd"] {
let held = admitted(sent);
let rendered = held.to_string();
assert_eq!(
ScriptSubtag::from_str(&rendered).expect("canonical text"),
held
);
assert_eq!(rendered, held.as_str());
}
assert_eq!(
std::format!("{:?}", admitted("latn")),
r#"ScriptSubtag("Latn")"#
);
}
#[test]
fn ordering_is_alphabetical_over_the_canonical_spelling() {
let mut sorted = [
admitted("Zzzz"),
admitted("hans"),
admitted("LATN"),
admitted("Hant"),
];
sorted.sort();
let spellings: Vec<&str> = sorted.iter().map(ScriptSubtag::as_str).collect();
assert_eq!(spellings, ["Hans", "Hant", "Latn", "Zzzz"]);
}
#[test]
fn a_script_does_not_know_which_languages_imply_it() {
let latin = admitted("Latn");
assert!(latin.is_registered());
let implying = registry::table::LANGUAGE_SUPPRESS_SCRIPT
.iter()
.filter(|(_, script)| *script == latin.as_str())
.count();
assert!(implying > 1, "`Latn` is implied by {implying} languages");
assert_eq!(
registry::language_suppress_script("Latn"),
None,
"not a language"
);
}