summaryrefslogtreecommitdiff
path: root/test/test_lang.ml
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-19 23:48:35 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-19 23:48:35 +0200
commitecadd969e1d96918820a1fdac0cf0d520d96ba06 (patch)
treea06ed1160bfd28ae4578d9445242e48ac9ac3ea4 /test/test_lang.ml
parent6762ce46af3cb12bc6ae37cda762c5d95add7903 (diff)
parent329d07b49e397cb65ab52e7ca019b47313027136 (diff)
downloadcolitur-ecadd969e1d96918820a1fdac0cf0d520d96ba06.tar.gz
colitur-ecadd969e1d96918820a1fdac0cf0d520d96ba06.zip
feat: naming, localisation and the rebuilt printed output
colitur computed the calendar correctly and could not say what it had computed. A printed ordo read ef-septuagesima-sunday-2 where a reader expects Dominica in Sexagesima, and the wall calendar showed slugs in every cell. lib/naming a language table and a config file, both pure and total, parsing the INI reader Overlay_ini already had lang/ la.ini and en.ini -- 725 names, every one transcribed from the 1962 Missal and citing the line it came from templates the ordo rebuilt as an A5 booklet: one week per page, a table of contents, framed days, a colour swatch; the wall calendar now fills its sheet instead of a quarter of it tools check_citations.py verifies all 400 citations resolve, with 33 self-tests of its own Names resolve through lang -> declared fallback -> the slug, so a partial translation is usable from its first line and the fully degraded case is the old output rather than a blank page. colitur day and colitur readings are BYTE-IDENTICAL to before, verified against main rather than asserted; --lang, --raw and the lang/config subcommands are still to come, and man/colitur-templates.5 still documents the pre-naming view, so writing a custom template needs the source until that lands.
Diffstat (limited to 'test/test_lang.ml')
-rw-r--r--test/test_lang.ml117
1 files changed, 117 insertions, 0 deletions
diff --git a/test/test_lang.ml b/test/test_lang.ml
new file mode 100644
index 0000000..b54e85b
--- /dev/null
+++ b/test/test_lang.ml
@@ -0,0 +1,117 @@
+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\
+ [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) "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")
+
+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 ] )