use super::{
ApiError, CatalogCoverageOptions, CatalogMessageKey, CatalogMessageStatus, ParseCatalogOptions,
PluralEncoding, measure_catalog_coverage, normalized_catalog, normalized_fcl_catalog,
parse_catalog,
};
fn invalid_arguments_message(error: ApiError) -> String {
match error {
ApiError::InvalidArguments(message) => message,
other => panic!("expected invalid arguments error, got {other:?}"),
}
}
#[test]
fn coverage_report_counts_expected_message_statuses() {
let source = normalized_catalog(
concat!(
"msgid \"Hello\"\nmsgstr \"Hello\"\n\n",
"msgid \"Empty\"\nmsgstr \"Empty\"\n\n",
"msgid \"Fuzzy\"\nmsgstr \"Fuzzy\"\n\n",
"msgid \"Gone\"\nmsgstr \"Gone\"\n\n",
"msgid \"Missing\"\nmsgstr \"Missing\"\n",
),
Some("en"),
PluralEncoding::Icu,
);
let target = normalized_catalog(
concat!(
"msgid \"Hello\"\nmsgstr \"Hallo\"\n\n",
"msgid \"Empty\"\nmsgstr \"\"\n\n",
"#, fuzzy\nmsgid \"Fuzzy\"\nmsgstr \"Unscharf\"\n\n",
"#~ msgid \"Gone\"\n#~ msgstr \"Weg\"\n\n",
"msgid \"Extra\"\nmsgstr \"Extra\"\n",
),
Some("de"),
PluralEncoding::Icu,
);
let report = measure_catalog_coverage(
&[&source, &target],
&CatalogCoverageOptions::new("en").with_details(true),
)
.expect("coverage");
let locale = &report.locales[0];
assert_eq!(report.source_messages, 5);
assert_eq!(report.target_locales, 1);
assert_eq!(locale.total, 5);
assert_eq!(locale.translated, 1);
assert_eq!(locale.empty, 1);
assert_eq!(locale.fuzzy(), 1);
assert_eq!(locale.obsolete, 1);
assert_eq!(locale.missing, 1);
assert_eq!(locale.extra, 1);
assert_eq!(locale.incomplete(), 4);
assert_eq!(locale.completion_percent(), 20.0);
assert!(locale.details.iter().any(|detail| {
detail.source_key == CatalogMessageKey::new("Fuzzy", None)
&& detail.status == CatalogMessageStatus::Fuzzy
}));
}
#[test]
fn coverage_report_classifies_fcl_fuzzy_like_po_fuzzy() {
let source = normalized_fcl_catalog("%FCL1\tsource=en\nFuzzy\t\tFuzzy\n", Some("en"));
let target =
normalized_fcl_catalog("%FCL1\tsource=en\nFuzzy\t\tUnscharf\tf=fuzzy\n", Some("de"));
let report = measure_catalog_coverage(
&[&source, &target],
&CatalogCoverageOptions::new("en").with_details(true),
)
.expect("coverage");
let locale = &report.locales[0];
assert_eq!(locale.translated, 0);
assert_eq!(locale.fuzzy(), 1);
assert_eq!(locale.details[0].status, CatalogMessageStatus::Fuzzy);
}
#[test]
fn coverage_report_includes_optional_details() {
let source = normalized_catalog(
"msgid \"Hello\"\nmsgstr \"Hello\"\n",
Some("en"),
PluralEncoding::Icu,
);
let target = normalized_catalog(
"msgid \"Hello\"\nmsgstr \"Hallo\"\n\nmsgid \"Extra\"\nmsgstr \"Extra\"\n",
Some("de"),
PluralEncoding::Icu,
);
let report = measure_catalog_coverage(
&[&source, &target],
&CatalogCoverageOptions::new("en").with_details(true),
)
.expect("coverage");
let details = &report.locales[0].details;
assert!(details.iter().any(|detail| {
detail.source_key == CatalogMessageKey::new("Hello", None)
&& detail.status == CatalogMessageStatus::Translated
}));
assert!(details.iter().any(|detail| {
detail.source_key == CatalogMessageKey::new("Extra", None)
&& detail.status == CatalogMessageStatus::Extra
}));
}
#[test]
fn coverage_report_rejects_the_metadata_discarding_parse_projection() {
let source = normalized_catalog(
"msgid \"Hello\"\nmsgstr \"Hello\"\n",
Some("en"),
PluralEncoding::Icu,
);
let target = parse_catalog(
ParseCatalogOptions::new(
"#, fuzzy, x-custom\nmsgid \"Hello\"\nmsgstr \"Hallo\"\n",
"en",
)
.with_locale("de"),
)
.expect("parse catalog")
.into_normalized_view()
.expect("normalize catalog");
let error = measure_catalog_coverage(&[&source, &target], &CatalogCoverageOptions::new("en"))
.expect_err("discarded review state must not silently count fuzzy as translated");
assert!(
invalid_arguments_message(error).contains("parse_catalog_for_review"),
"error should explain the review-aware parse path"
);
assert!(!target.has_review_state());
}
#[test]
fn coverage_report_filters_requested_locales() {
let source = normalized_catalog(
"msgid \"Hello\"\nmsgstr \"Hello\"\n",
Some("en"),
PluralEncoding::Icu,
);
let de = normalized_catalog(
"msgid \"Hello\"\nmsgstr \"Hallo\"\n",
Some("de"),
PluralEncoding::Icu,
);
let fr = normalized_catalog(
"msgid \"Hello\"\nmsgstr \"Bonjour\"\n",
Some("fr"),
PluralEncoding::Icu,
);
let requested = ["fr"];
let report = measure_catalog_coverage(
&[&source, &de, &fr],
&CatalogCoverageOptions {
locales: &requested,
..CatalogCoverageOptions::new("en")
},
)
.expect("coverage");
assert_eq!(report.target_locales, 1);
assert_eq!(report.locales[0].locale, "fr");
}
#[test]
fn coverage_report_skips_source_and_duplicate_requested_locales() {
let source = normalized_catalog(
"msgid \"Hello\"\nmsgstr \"Hello\"\n",
Some("en"),
PluralEncoding::Icu,
);
let de = normalized_catalog(
"msgid \"Hello\"\nmsgstr \"Hallo\"\n",
Some("de"),
PluralEncoding::Icu,
);
let fr = normalized_catalog(
"msgid \"Hello\"\nmsgstr \"Bonjour\"\n",
Some("fr"),
PluralEncoding::Icu,
);
let requested = ["en", "de", "de", "fr"];
let report = measure_catalog_coverage(
&[&source, &de, &fr],
&CatalogCoverageOptions {
locales: &requested,
..CatalogCoverageOptions::new("en")
},
)
.expect("coverage");
assert_eq!(report.target_locales, 2);
assert_eq!(report.locales[0].locale, "de");
assert_eq!(report.locales[1].locale, "fr");
}
#[test]
fn coverage_report_treats_empty_source_set_as_complete() {
let source = normalized_catalog("", Some("en"), PluralEncoding::Icu);
let target = normalized_catalog("", Some("de"), PluralEncoding::Icu);
let report = measure_catalog_coverage(&[&source, &target], &CatalogCoverageOptions::new("en"))
.expect("coverage");
let locale = &report.locales[0];
assert_eq!(report.source_messages, 0);
assert_eq!(locale.total, 0);
assert_eq!(locale.incomplete(), 0);
assert_eq!(locale.completion_ratio(), 1.0);
assert_eq!(locale.completion_percent(), 100.0);
assert!(locale.details.is_empty());
}
#[test]
fn coverage_report_classifies_plural_messages_with_empty_slots() {
let source = normalized_catalog(
concat!(
"msgid \"book\"\n",
"msgid_plural \"books\"\n",
"msgstr[0] \"book\"\n",
"msgstr[1] \"books\"\n",
),
Some("en"),
PluralEncoding::Gettext,
);
let target = normalized_catalog(
concat!(
"msgid \"book\"\n",
"msgid_plural \"books\"\n",
"msgstr[0] \"Buch\"\n",
"msgstr[1] \"\"\n",
),
Some("de"),
PluralEncoding::Gettext,
);
let report = measure_catalog_coverage(
&[&source, &target],
&CatalogCoverageOptions::new("en").with_details(true),
)
.expect("coverage");
let locale = &report.locales[0];
assert_eq!(locale.empty, 1);
assert!(locale.details.iter().any(|detail| {
detail.source_key == CatalogMessageKey::new("book", None)
&& detail.status == CatalogMessageStatus::Empty
}));
}
#[test]
fn coverage_statuses_are_mutually_exclusive_when_fuzzy_overlaps_other_state() {
let source = normalized_catalog(
concat!(
"msgid \"partial\"\n",
"msgid_plural \"partials\"\n",
"msgstr[0] \"partial\"\n",
"msgstr[1] \"partials\"\n\n",
"msgid \"obsolete\"\nmsgstr \"obsolete\"\n",
),
Some("en"),
PluralEncoding::Gettext,
);
let target = normalized_catalog(
concat!(
"#, fuzzy\n",
"msgid \"partial\"\n",
"msgid_plural \"partials\"\n",
"msgstr[0] \"Teil\"\n",
"msgstr[1] \"\"\n\n",
"#~ #, fuzzy\n",
"#~ msgid \"obsolete\"\n",
"#~ msgstr \"veraltet\"\n\n",
"#, fuzzy\n",
"msgid \"extra\"\n",
"msgstr \"zusätzlich\"\n",
),
Some("de"),
PluralEncoding::Gettext,
);
let report = measure_catalog_coverage(
&[&source, &target],
&CatalogCoverageOptions::new("en").with_details(true),
)
.expect("coverage");
let locale = &report.locales[0];
assert_eq!(locale.translated, 0);
assert_eq!(locale.fuzzy(), 0);
assert_eq!(locale.empty, 1);
assert_eq!(locale.obsolete, 1);
assert_eq!(locale.missing, 0);
assert_eq!(locale.extra, 1);
assert_eq!(
locale.total,
locale.translated + locale.fuzzy() + locale.empty + locale.obsolete + locale.missing
);
}
#[test]
fn coverage_report_rejects_invalid_catalog_locale_inputs() {
let source = normalized_catalog(
"msgid \"Hello\"\nmsgstr \"Hello\"\n",
Some("en"),
PluralEncoding::Icu,
);
let duplicate_source = normalized_catalog(
"msgid \"Bye\"\nmsgstr \"Bye\"\n",
Some("en"),
PluralEncoding::Icu,
);
let missing_locale = normalized_catalog(
"msgid \"Hello\"\nmsgstr \"Hallo\"\n",
None,
PluralEncoding::Icu,
);
let de = normalized_catalog(
"msgid \"Hello\"\nmsgstr \"Hallo\"\n",
Some("de"),
PluralEncoding::Icu,
);
let requested = ["fr"];
let missing_source = measure_catalog_coverage(&[&de], &CatalogCoverageOptions::new("en"))
.expect_err("missing source locale should fail");
assert!(invalid_arguments_message(missing_source).contains("source locale"));
let duplicate_locale = measure_catalog_coverage(
&[&source, &duplicate_source],
&CatalogCoverageOptions::new("en"),
)
.expect_err("duplicate locale should fail");
assert!(invalid_arguments_message(duplicate_locale).contains("duplicate catalog locale"));
let undeclared_locale =
measure_catalog_coverage(&[&missing_locale], &CatalogCoverageOptions::new("en"))
.expect_err("missing declared locale should fail");
assert!(invalid_arguments_message(undeclared_locale).contains("declare a locale"));
let missing_requested = measure_catalog_coverage(
&[&source, &de],
&CatalogCoverageOptions {
locales: &requested,
..CatalogCoverageOptions::new("en")
},
)
.expect_err("missing requested locale should fail");
assert!(invalid_arguments_message(missing_requested).contains("requested locale"));
}