use ferrocat_icu::{
MessageArgumentKind, MessageArgumentMetadataInput, MessageMetadataInput, MessageSelectorKind,
MessageSelectorMetadata,
};
use crate::{CatalogAuditIcuOptions, audit_catalogs_with_icu_options};
use super::{
CatalogAuditOptions, CatalogMode, DiagnosticSeverity, IcuSyntaxPolicy, ParseCatalogOptions,
audit_catalogs, parse_catalog,
};
fn catalog(content: &str, locale: &str) -> super::super::NormalizedParsedCatalog {
parse_catalog(ParseCatalogOptions {
locale: Some(locale),
mode: CatalogMode::IcuPo,
..ParseCatalogOptions::new(content, "en")
})
.expect("parse catalog")
.into_normalized_view()
.expect("normalize catalog")
}
fn diagnostic_codes(report: &super::super::CatalogAuditReport) -> Vec<&str> {
report
.diagnostics
.iter()
.map(|diagnostic| diagnostic.code.as_str())
.collect()
}
#[test]
fn audit_reports_missing_translation_for_active_source_key() {
let source = catalog("msgid \"Hello\"\nmsgstr \"Hello\"\n", "en");
let target = catalog("", "de");
let report =
audit_catalogs(&[&source, &target], &CatalogAuditOptions::new("en")).expect("audit");
assert!(diagnostic_codes(&report).contains(&"catalog.missing_translation"));
assert!(report.has_errors());
}
#[test]
fn audit_reports_empty_target_translation() {
let source = catalog("msgid \"Hello\"\nmsgstr \"Hello\"\n", "en");
let target = catalog("msgid \"Hello\"\nmsgstr \"\"\n", "de");
let report =
audit_catalogs(&[&source, &target], &CatalogAuditOptions::new("en")).expect("audit");
assert!(diagnostic_codes(&report).contains(&"catalog.empty_translation"));
}
#[test]
fn audit_reports_target_only_active_message_as_extra() {
let source = catalog("msgid \"Hello\"\nmsgstr \"Hello\"\n", "en");
let target = catalog(
"msgid \"Hello\"\nmsgstr \"Hallo\"\n\nmsgid \"Only target\"\nmsgstr \"Nur Ziel\"\n",
"de",
);
let report =
audit_catalogs(&[&source, &target], &CatalogAuditOptions::new("en")).expect("audit");
let diagnostic = report
.diagnostics
.iter()
.find(|diagnostic| diagnostic.code == "catalog.extra_translation")
.expect("extra diagnostic");
assert_eq!(diagnostic.severity, DiagnosticSeverity::Warning);
}
#[test]
fn audit_does_not_let_obsolete_target_satisfy_completeness() {
let source = catalog("msgid \"Hello\"\nmsgstr \"Hello\"\n", "en");
let target = catalog("#~ msgid \"Hello\"\n#~ msgstr \"Hallo\"\n", "de");
let report =
audit_catalogs(&[&source, &target], &CatalogAuditOptions::new("en")).expect("audit");
let codes = diagnostic_codes(&report);
assert!(codes.contains(&"catalog.missing_translation"));
assert!(codes.contains(&"catalog.obsolete_entry"));
}
#[test]
fn audit_reports_existing_fuzzy_flags_as_info() {
let source = catalog("msgid \"Hello\"\nmsgstr \"Hello\"\n", "en");
let target = catalog("#, fuzzy\nmsgid \"Hello\"\nmsgstr \"Hallo\"\n", "de");
let report =
audit_catalogs(&[&source, &target], &CatalogAuditOptions::new("en")).expect("audit");
let diagnostic = report
.diagnostics
.iter()
.find(|diagnostic| diagnostic.code == "catalog.fuzzy_flag")
.expect("fuzzy diagnostic");
assert_eq!(diagnostic.severity, DiagnosticSeverity::Info);
}
#[test]
fn audit_reports_missing_source_locale() {
let target = catalog("msgid \"Hello\"\nmsgstr \"Hallo\"\n", "de");
let report = audit_catalogs(&[&target], &CatalogAuditOptions::new("en")).expect("audit");
assert!(diagnostic_codes(&report).contains(&"catalog.missing_source_locale"));
assert_eq!(report.summary.errors, 1);
}
#[test]
fn audit_locale_filter_limits_target_checks_and_reports_missing_locale() {
let source = catalog("msgid \"Hello\"\nmsgstr \"Hello\"\n", "en");
let de = catalog("msgid \"Hello\"\nmsgstr \"Hallo\"\n", "de");
let fr = catalog("msgid \"Hello\"\nmsgstr \"\"\n", "fr");
let requested = ["de", "it"];
let report = audit_catalogs(
&[&source, &de, &fr],
&CatalogAuditOptions {
locales: &requested,
..CatalogAuditOptions::new("en")
},
)
.expect("audit");
let codes = diagnostic_codes(&report);
assert!(codes.contains(&"catalog.missing_locale"));
assert!(!codes.contains(&"catalog.empty_translation"));
assert_eq!(report.summary.target_locales, 1);
}
#[test]
fn audit_surfaces_icu_syntax_errors() {
let source = catalog("msgid \"Hello {name\"\nmsgstr \"Hello {name\"\n", "en");
let target = catalog("msgid \"Hello {name\"\nmsgstr \"Hallo {name\"\n", "de");
let report =
audit_catalogs(&[&source, &target], &CatalogAuditOptions::new("en")).expect("audit");
assert!(diagnostic_codes(&report).contains(&"icu.invalid_syntax"));
}
#[test]
fn audit_strict_policy_reports_literal_apostrophes() {
let source = catalog(
"msgid \"Set your hours when you're available.\"\nmsgstr \"Set your hours when you're available.\"\n",
"en",
);
let report = audit_catalogs(&[&source], &CatalogAuditOptions::new("en")).expect("audit");
assert!(diagnostic_codes(&report).contains(&"icu.invalid_syntax"));
}
#[test]
fn audit_runtime_literal_apostrophes_policy_accepts_runtime_valid_messages() {
let source = catalog(
concat!(
"msgid \"Set your hours when you're available.\"\n",
"msgstr \"Set your hours when you're available.\"\n\n",
"msgid \"We've got {count, plural, one {one opening} other {# openings}}.\"\n",
"msgstr \"We've got {count, plural, one {one opening} other {# openings}}.\"\n",
),
"en",
);
let report = audit_catalogs_with_icu_options(
&[&source],
&CatalogAuditOptions::new("en"),
&CatalogAuditIcuOptions::new()
.with_syntax_policy(IcuSyntaxPolicy::RuntimeLiteralApostrophes),
)
.expect("audit");
assert!(!diagnostic_codes(&report).contains(&"icu.invalid_syntax"));
}
#[test]
fn audit_runtime_literal_apostrophes_policy_keeps_real_invalid_icu() {
let source = catalog("msgid \"Hello\"\nmsgstr \"Hello {{name}}\"\n", "en");
let report = audit_catalogs_with_icu_options(
&[&source],
&CatalogAuditOptions::new("en"),
&CatalogAuditIcuOptions::new()
.with_syntax_policy(IcuSyntaxPolicy::RuntimeLiteralApostrophes),
)
.expect("audit");
assert!(diagnostic_codes(&report).contains(&"icu.invalid_syntax"));
}
#[test]
fn audit_runtime_literal_apostrophes_policy_still_reports_compatibility_changes() {
let source = catalog(
"msgid \"We've got {count, plural, one {one opening} other {# openings}}.\"\nmsgstr \"We've got {count, plural, one {one opening} other {# openings}}.\"\n",
"en",
);
let target = catalog(
"msgid \"We've got {count, plural, one {one opening} other {# openings}}.\"\nmsgstr \"Wir haben freie Termine.\"\n",
"de",
);
let report = audit_catalogs_with_icu_options(
&[&source, &target],
&CatalogAuditOptions::new("en"),
&CatalogAuditIcuOptions::new()
.with_syntax_policy(IcuSyntaxPolicy::RuntimeLiteralApostrophes),
)
.expect("audit");
assert!(diagnostic_codes(&report).contains(&"icu.missing_argument"));
}
#[test]
fn audit_reuses_icu_compatibility_codes() {
let source = catalog("msgid \"Hello {name}\"\nmsgstr \"Hello {name}\"\n", "en");
let target = catalog("msgid \"Hello {name}\"\nmsgstr \"Hallo\"\n", "de");
let report =
audit_catalogs(&[&source, &target], &CatalogAuditOptions::new("en")).expect("audit");
assert!(diagnostic_codes(&report).contains(&"icu.missing_argument"));
}
#[test]
fn audit_reports_duplicate_unknown_and_conflicting_metadata() {
let source = catalog(
"msgid \"{count, plural, one {One item} other {# items}}\"\nmsgstr \"{count, plural, one {One item} other {# items}}\"\n",
"en",
);
let mut args = std::collections::BTreeMap::new();
args.insert(
"count".to_owned(),
MessageArgumentMetadataInput::Kind(MessageArgumentKind::String),
);
let mut selectors = std::collections::BTreeMap::new();
selectors.insert(
"count".to_owned(),
MessageSelectorMetadata {
kind: MessageSelectorKind::Plural,
cases: vec!["one".to_owned()],
offset: None,
},
);
let mut first = MessageMetadataInput::new("{count, plural, one {One item} other {# items}}");
first.args = Some(args);
first.selectors = Some(selectors);
let duplicate = MessageMetadataInput::new("{count, plural, one {One item} other {# items}}");
let unknown = MessageMetadataInput::new("Unknown message");
let metadata = [first, duplicate, unknown];
let report = audit_catalogs(
&[&source],
&CatalogAuditOptions {
metadata: &metadata,
..CatalogAuditOptions::new("en")
},
)
.expect("audit");
let codes = diagnostic_codes(&report);
assert!(codes.contains(&"catalog.duplicate_metadata"));
assert!(codes.contains(&"catalog.metadata_unknown_message"));
assert!(codes.contains(&"metadata.argument_kind_mismatch"));
assert!(codes.contains(&"metadata.missing_selector_case"));
}