summaryrefslogtreecommitdiff
path: root/test/test_precedence.ml
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-22 22:40:07 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-22 22:40:07 +0200
commit761ae859d73660bcfaa59581312989cb942e704d (patch)
treeadcf7aa53f104fd753769445ced043946d6f0927 /test/test_precedence.ml
parent9c96e0afc19925b651e0dfee3bc9182dee72acf0 (diff)
downloadcolitur-761ae859d73660bcfaa59581312989cb942e704d.tar.gz
colitur-761ae859d73660bcfaa59581312989cb942e704d.zip
feat(ef): RG 111(a), the sung-Mass commemoration cap
Item 1 of Phase 3 (celebrant-rubrics-phase1): the "at Low Mass" commemoration-placement rule the design spec recorded as unread. It is not a new rule -- it is the sung/low axis of RG 111, which colitur already implements. RG 111(a) (LT.txt, "Ratio admittendi commemorationes"): a liturgical day of the first class, AND any non-conventual sung Mass regardless of the day's own class, admits at most one commemoration, and only if it is privileged. (b)/(c)/(d), the same rubric's remaining clauses, give the wider caps colitur's admit already computes -- which is exactly the LOW MASS answer. Exposed as Precedence.sung_mass_commemorations, a pure derivation over the existing Low-Mass admitted list (filter to Privileged, keep the first), not a new stored field on Liturgical_day.t: the input list is already validated and privilege-tagged, so a second field would only create a second place for the same fact to drift out of sync with the first, with no new information gained. Liturgical_day.t.commemorations is now documented as the Low Mass set explicitly, removing the ambiguity its .mli previously left unstated. Tested against synthetic Low-Mass sets (none/one/two privileged, already-first, empty) and two real calendar days resolved through the normal Cal.day pipeline: 2026-08-14 (Vigil of the Assumption, an ordinary-only commemoration, dropped at Sung Mass) and 2026-04-25 (the Major Litanies, RG 80/109(f), privileged, kept at both Masses).
Diffstat (limited to 'test/test_precedence.ml')
-rw-r--r--test/test_precedence.ml55
1 files changed, 54 insertions, 1 deletions
diff --git a/test/test_precedence.ml b/test/test_precedence.ml
index acc89fc..54ab3f9 100644
--- a/test/test_precedence.ml
+++ b/test/test_precedence.ml
@@ -98,6 +98,49 @@ let test_temporal_only_day () =
Alcotest.(check int) "nothing deferred" 0 (List.length r.P.deferred);
Alcotest.(check int) "nothing omitted" 0 (List.length r.P.omitted)
+(* RG 111(a): sung_mass_commemorations -- see precedence.mli's own citation.
+ Pure function, no {!resolve} needed: exercised directly against
+ hand-built Low-Mass admitted lists, the same shape {!P.resolve} would
+ produce, rather than through a full resolution. *)
+let sung_test_cand rank slug = cand ~rank slug
+
+let test_sung_mass_no_privileged () =
+ let low_mass = [ (sung_test_cand Lo "a", P.Ordinary); (sung_test_cand Lo "b", P.Ordinary) ] in
+ Alcotest.(check (list string)) "no privileged commemoration -> sung Mass keeps none" []
+ (List.map (fun (c, _) -> slug_of c) (P.sung_mass_commemorations low_mass))
+
+let test_sung_mass_one_privileged () =
+ let low_mass =
+ [ (sung_test_cand Lo "ordinary-one", P.Ordinary); (sung_test_cand Hi "privileged-one", P.Privileged) ]
+ in
+ Alcotest.(check (list string)) "the ordinary commemoration is dropped, the privileged one kept"
+ [ "privileged-one" ]
+ (List.map (fun (c, _) -> slug_of c) (P.sung_mass_commemorations low_mass))
+
+let test_sung_mass_privileged_first_already () =
+ let low_mass =
+ [ (sung_test_cand Hi "privileged-one", P.Privileged); (sung_test_cand Lo "ordinary-one", P.Ordinary) ]
+ in
+ Alcotest.(check (list string)) "a privileged commemoration already first is kept alone" [ "privileged-one" ]
+ (List.map (fun (c, _) -> slug_of c) (P.sung_mass_commemorations low_mass))
+
+let test_sung_mass_empty_low_mass_set () =
+ Alcotest.(check (list string)) "an empty Low-Mass set stays empty at Sung Mass" []
+ (List.map (fun (c, _) -> slug_of c) (P.sung_mass_commemorations []))
+
+(* If two privileged commemorations were ever admitted together (not known
+ to occur on any shipped rite's data -- precedence.mli's own citation),
+ [sung_mass_commemorations] keeps only the FIRST -- input order is
+ already RG 113 precedence order, so this is "the highest-precedence
+ privileged entry survives", not an arbitrary truncation. *)
+let test_sung_mass_two_privileged_keeps_first () =
+ let low_mass =
+ [ (sung_test_cand Hi "first-privileged", P.Privileged); (sung_test_cand Hi "second-privileged", P.Privileged) ]
+ in
+ Alcotest.(check (list string)) "only the first (higher-precedence) privileged commemoration survives"
+ [ "first-privileged" ]
+ (List.map (fun (c, _) -> slug_of c) (P.sung_mass_commemorations low_mass))
+
let suite =
( "Precedence",
[ Alcotest.test_case "highest band wins" `Quick test_highest_band_wins;
@@ -107,4 +150,14 @@ let suite =
test_commemoration_only_never_observed;
Alcotest.test_case "nothing silently lost" `Quick test_nothing_silently_lost;
Alcotest.test_case "order independent" `Quick test_order_independent;
- Alcotest.test_case "temporal-only day" `Quick test_temporal_only_day ] )
+ Alcotest.test_case "temporal-only day" `Quick test_temporal_only_day;
+ Alcotest.test_case "RG 111(a): sung Mass, no privileged commemoration" `Quick
+ test_sung_mass_no_privileged;
+ Alcotest.test_case "RG 111(a): sung Mass keeps the one privileged commemoration" `Quick
+ test_sung_mass_one_privileged;
+ Alcotest.test_case "RG 111(a): sung Mass, privileged already first" `Quick
+ test_sung_mass_privileged_first_already;
+ Alcotest.test_case "RG 111(a): sung Mass, empty Low-Mass set" `Quick
+ test_sung_mass_empty_low_mass_set;
+ Alcotest.test_case "RG 111(a): sung Mass keeps only the first of two privileged" `Quick
+ test_sung_mass_two_privileged_keeps_first ] )