use core::str::FromStr;
use std::{
collections::BTreeMap,
string::{String, ToString},
vec::Vec,
};
use super::{ParseRegionError, Region};
use crate::lang::registry;
fn door(sent: &str) -> Result<Region, ParseRegionError> {
Region::new(sent)
}
fn admitted(sent: &str) -> Region {
door(sent).unwrap_or_else(|refused| panic!("`{sent}` is a region subtag: {refused}"))
}
#[test]
fn both_grammars_reach_their_own_canonical_form() {
for (sent, canonical) in [
("DE", "DE"),
("de", "DE"),
("dE", "DE"),
("tw", "TW"),
("419", "419"),
("001", "001"),
("150", "150"),
("zz", "ZZ"),
] {
assert_eq!(admitted(sent).as_str(), canonical, "`{sent}`");
}
}
#[test]
fn an_area_codes_leading_zeros_are_part_of_it() {
assert_eq!(admitted("001").as_str(), "001");
assert_eq!(admitted("001").name(), Some("World"));
assert_eq!(door("1"), Err(ParseRegionError::WrongDigitWidth));
assert_eq!(door("01"), Err(ParseRegionError::WrongDigitWidth));
assert_eq!(door("0001"), Err(ParseRegionError::WrongDigitWidth));
}
#[test]
fn a_deprecated_region_folds_onto_the_one_that_replaced_it() {
for (sent, canonical) in [
("BU", "MM"),
("bu", "MM"),
("ZR", "CD"),
("TP", "TL"),
("DD", "DE"),
("FX", "FR"),
("YD", "YE"),
] {
let held = admitted(sent);
assert_eq!(held.as_str(), canonical, "`{sent}`");
assert!(!held.is_deprecated(), "`{sent}` became a current region");
}
assert_eq!(admitted("BU"), admitted("MM"), "one value, one stored row");
assert_eq!(admitted("MM").name(), Some("Myanmar"));
}
#[test]
fn a_deprecated_region_without_a_successor_stays_itself() {
for orphaned in ["AN", "CS", "NT", "SU", "YU"] {
let held = admitted(orphaned);
assert_eq!(held.as_str(), orphaned, "`{orphaned}`");
assert!(held.is_deprecated(), "`{orphaned}`");
assert!(held.is_registered(), "`{orphaned}`");
assert_eq!(registry::region_preferred(orphaned), None, "`{orphaned}`");
}
}
#[test]
fn a_structural_violation_is_refused_by_the_arm_it_was_nearest() {
for (sent, expected) in [
("", ParseRegionError::Empty),
("D", ParseRegionError::WrongLetterWidth),
("DEU", ParseRegionError::WrongLetterWidth),
("GERM", ParseRegionError::WrongLetterWidth),
("1", ParseRegionError::WrongDigitWidth),
("41", ParseRegionError::WrongDigitWidth),
("4190", ParseRegionError::WrongDigitWidth),
("D1", ParseRegionError::Mixed),
("4A", ParseRegionError::Mixed),
("419A", ParseRegionError::Mixed),
("D_", ParseRegionError::NotAlphanumeric('_')),
("de-DE", ParseRegionError::NotAlphanumeric('-')),
("日本", ParseRegionError::NotAlphanumeric('日')),
(" DE", ParseRegionError::NotAlphanumeric(' ')),
] {
assert_eq!(door(sent), Err(expected), "`{sent}`");
}
}
#[test]
fn the_two_individually_registered_private_regions_are_both_at_once() {
for private in ["AA", "ZZ"] {
let held = admitted(private);
assert!(held.is_private_use(), "`{private}`");
assert!(held.is_registered(), "`{private}` has a record of its own");
assert_eq!(held.name(), Some("Private use"), "`{private}`");
}
assert_eq!(registry::REGION_PRIVATE_USE_SUBTAGS, ["AA", "ZZ"]);
for ranged in ["QM", "QZ", "XA", "XZ", "XK"] {
let held = admitted(ranged);
assert!(held.is_private_use(), "`{ranged}`");
assert!(!held.is_registered(), "`{ranged}`");
}
}
#[test]
fn zz_is_a_value_and_not_an_absence() {
let unknown = Region::ZZ;
assert_eq!(unknown.as_str(), "ZZ");
assert!(unknown.is_zz());
assert_eq!(admitted("zz"), unknown);
assert!(!admitted("DE").is_zz());
let absent: Option<Region> = None;
assert_ne!(absent, Some(unknown), "no region is not `ZZ`");
}
#[test]
fn an_area_code_says_that_it_is_one() {
assert!(admitted("419").is_area());
assert!(admitted("001").is_area());
assert!(!admitted("DE").is_area());
assert!(!admitted("ZZ").is_area());
assert_eq!(admitted("150").name(), Some("Europe"));
assert_eq!(
admitted("419").name(),
Some("Latin America and the Caribbean")
);
}
#[test]
fn an_unregistered_subtag_is_admitted_on_either_grammar() {
let letters = admitted("ZY");
assert_eq!(letters.as_str(), "ZY");
assert!(!letters.is_registered());
assert!(!letters.is_private_use());
let digits = admitted("999");
assert_eq!(digits.as_str(), "999");
assert!(!digits.is_registered());
assert!(digits.is_area());
}
#[test]
fn a_canonical_region_is_a_fixpoint_and_the_fold_collides_nothing() {
let mut reached: BTreeMap<String, &str> = BTreeMap::new();
for (subtag, _) in registry::table::REGIONS {
let subtag = subtag.as_str();
let held = admitted(subtag);
match registry::region_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 regions folded onto `{}`",
held.as_str()
);
}
}
assert_eq!(
admitted(held.as_str()),
held,
"`{subtag}` is not idempotent"
);
}
assert_eq!(reached.len(), registry::REGION_COUNT - 6);
}
#[test]
fn the_rendering_and_the_parse_are_inverse() {
for sent in ["DE", "bu", "419", "001", "ZZ", "ZY"] {
let held = admitted(sent);
let rendered = held.to_string();
assert_eq!(Region::from_str(&rendered).expect("canonical text"), held);
assert_eq!(rendered, held.as_str());
}
assert_eq!(std::format!("{:?}", admitted("bu")), r#"Region("MM")"#);
}
#[test]
fn ordering_is_bytewise_over_the_canonical_spelling() {
let mut sorted = [
admitted("ZZ"),
admitted("de"),
admitted("419"),
admitted("001"),
];
sorted.sort();
let spellings: Vec<&str> = sorted.iter().map(Region::as_str).collect();
assert_eq!(spellings, ["001", "419", "DE", "ZZ"], "digits sort first");
}