module L = Colitur_naming.Lang let ok = function Ok x -> x | Error e -> Alcotest.failf "parse: %s" e let sample = "[meta]\n\ lang = xx\n\ fallback = la\n\ [celebration]\n\ ef-lent-3-monday = Feria II hebdomadae III Quadragesimae\n\ francis-de-sales = S. Francisci Salesii\n\ [weekday]\n\ sunday = Dominica\n\ monday = Feria II\n\ [month]\n\ 1 = Ianuarius\n\ [month_abbr]\n\ 1 = Ian\n\ [season]\n\ lent = Quadragesima\n\ [rank]\n\ class-1 = I classis\n\ [colour]\n\ white = albus\n\ [term]\n\ epistle = Epistola\n" let test_meta () = let t = ok (L.of_string sample) in Alcotest.(check string) "code" "xx" (L.code t); Alcotest.(check (option string)) "fallback" (Some "la") (L.fallback_code t) let test_lookups () = let t = ok (L.of_string sample) in Alcotest.(check string) "celebration" "Feria II hebdomadae III Quadragesimae" (L.celebration t "ef-lent-3-monday"); Alcotest.(check string) "weekday 0 is Sunday" "Dominica" (L.weekday t 0); Alcotest.(check string) "month 1" "Ianuarius" (L.month t 1); Alcotest.(check string) "month_abbr 1" "Ian" (L.month_abbr t 1); Alcotest.(check string) "season" "Quadragesima" (L.season t "lent"); Alcotest.(check string) "rank" "I classis" (L.rank t "class-1"); Alcotest.(check string) "colour" "albus" (L.colour t "white"); Alcotest.(check string) "term" "Epistola" (L.term t "epistle") (* THE load-bearing property: a missing key degrades to the key itself, never to empty. A partial translation must be usable from its first line, and an untranslated day must still say something a reader can act on. *) let test_missing_degrades_to_key () = let t = ok (L.of_string sample) in Alcotest.(check string) "unknown celebration" "ef-advent-1-monday" (L.celebration t "ef-advent-1-monday"); Alcotest.(check string) "unknown colour" "rose" (L.colour t "rose"); Alcotest.(check string) "unknown term" "gospel" (L.term t "gospel") let test_fallback_chain () = let base = ok (L.of_string "[meta]\nlang = la\n[celebration]\na = ALPHA\nb = BETA\n") in let over = ok (L.of_string "[meta]\nlang = xx\nfallback = la\n[celebration]\nb = BETA-XX\n") in let t = L.with_fallback over base in Alcotest.(check string) "own key wins" "BETA-XX" (L.celebration t "b"); Alcotest.(check string) "falls back" "ALPHA" (L.celebration t "a"); Alcotest.(check string) "neither: the key" "c" (L.celebration t "c") (* --raw must be a real identity table, not a special case threaded through every call site: one table the whole program can pass around. *) let test_raw_is_identity () = Alcotest.(check string) "celebration" "ef-epiphany" (L.celebration L.raw "ef-epiphany"); Alcotest.(check string) "colour" "white" (L.colour L.raw "white"); Alcotest.(check string) "weekday" "0" (L.weekday L.raw 0) let test_malformed_is_error_not_crash () = match L.of_string "[celebration\nbroken" with | Error _ -> () | Ok _ -> Alcotest.fail "a malformed language file must be an Error, never accepted" let test_missing_meta_lang_is_error () = match L.of_string "[celebration]\na = B\n" with | Error _ -> () | Ok _ -> Alcotest.fail "a language file with no [meta] lang must be an Error" (* F1 regression: a hand-edited language file WILL grow duplicate [section] headers as contributors append entries over time (Tasks 3/4's 595-entry la.ini). Both blocks' keys must resolve -- silently dropping the second block is a data-loss footgun that surfaces as a false "missing name" report far from its real cause. *) let test_duplicate_sections_all_merge () = let t = ok (L.of_string "[meta]\nlang = la\n[celebration]\na = ALPHA\n[weekday]\nsunday = Dominica\n\ [celebration]\nb = BETA\n") in Alcotest.(check string) "first block's key" "ALPHA" (L.celebration t "a"); Alcotest.(check string) "second block's key" "BETA" (L.celebration t "b") let test_duplicate_key_across_sections_last_wins () = let t = ok (L.of_string "[meta]\nlang = la\n[celebration]\na = FIRST\n[celebration]\na = SECOND\n") in Alcotest.(check string) "later block's value wins" "SECOND" (L.celebration t "a") let test_duplicate_key_within_section_last_wins () = let t = ok (L.of_string "[meta]\nlang = la\n[celebration]\na = FIRST\na = SECOND\n") in Alcotest.(check string) "later line's value wins" "SECOND" (L.celebration t "a") (* Task 6: [bible] and [sigla] used to be two more section names [of_string] never looked for -- a tenth (or eleventh) section a file author writes was silently ignored, not rejected. Verified directly against a checked-out scratch executable before this change: [Lang.keys] on a table built from text containing a [bible] section came back with zero entries, no error either. *) let test_bible_section_is_read () = let text = "[meta]\nlang = xx\n[bible]\nluke.abbr = Lc\nluke.full = Ewangelia\n" in match L.of_string text with | Error e -> Alcotest.failf "parse: %s" e | Ok t -> Alcotest.(check string) "abbr" "Lc" (L.bible t "luke.abbr"); Alcotest.(check string) "full" "Ewangelia" (L.bible t "luke.full"); (* the TOTAL contract: a miss returns the key *) Alcotest.(check string) "miss" "mark.abbr" (L.bible t "mark.abbr") let test_sigla_section_is_read () = let text = "[meta]\nlang = xx\n[sigla]\nbook = full\npart_sep = \"; \"\n" in match L.of_string text with | Error e -> Alcotest.failf "parse: %s" e | Ok t -> let f = L.sigla_fields t in Alcotest.(check (option string)) "book" (Some "full") (List.assoc_opt "book" f); (* the quotes survive Lang; Render.style_of_fields strips them *) Alcotest.(check (option string)) "sep" (Some "\"; \"") (List.assoc_opt "part_sep" f) (* The deliberate asymmetry: [bible] joins [keys] (lang --check's reference set, so a translation missing every book name is reported as incomplete); [sigla] does not (it is five settings with working defaults, not names a translator owes -- putting it in [keys] would make --check demand five settings from every language file). Pinned so a later change here is a deliberate one, not a drive-by. *) let test_check_demands_bible_not_sigla () = let text = "[meta]\nlang = xx\n[bible]\nluke.abbr = Lc\n[sigla]\nbook = full\n" in match L.of_string text with | Error e -> Alcotest.failf "parse: %s" e | Ok t -> let ks = L.keys t in Alcotest.(check bool) "bible in keys" true (List.mem_assoc "bible.luke.abbr" ks); Alcotest.(check bool) "sigla not in keys" false (List.exists (fun (k, _) -> String.length k > 6 && String.sub k 0 6 = "sigla.") ks) let suite = ( "Lang", [ Alcotest.test_case "meta" `Quick test_meta; Alcotest.test_case "lookups" `Quick test_lookups; Alcotest.test_case "missing degrades to key" `Quick test_missing_degrades_to_key; Alcotest.test_case "fallback chain" `Quick test_fallback_chain; Alcotest.test_case "raw is identity" `Quick test_raw_is_identity; Alcotest.test_case "malformed is error" `Quick test_malformed_is_error_not_crash; Alcotest.test_case "missing meta lang is error" `Quick test_missing_meta_lang_is_error; Alcotest.test_case "duplicate sections all merge" `Quick test_duplicate_sections_all_merge; Alcotest.test_case "duplicate key across sections: last wins" `Quick test_duplicate_key_across_sections_last_wins; Alcotest.test_case "duplicate key within section: last wins" `Quick test_duplicate_key_within_section_last_wins; Alcotest.test_case "bible section is read" `Quick test_bible_section_is_read; Alcotest.test_case "sigla section is read" `Quick test_sigla_section_is_read; Alcotest.test_case "check demands bible not sigla" `Quick test_check_demands_bible_not_sigla ] )