diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/test_temporal_ef.ml | 116 |
1 files changed, 116 insertions, 0 deletions
diff --git a/test/test_temporal_ef.ml b/test/test_temporal_ef.ml index 751abd2..7837fb5 100644 --- a/test/test_temporal_ef.ml +++ b/test/test_temporal_ef.ml @@ -167,13 +167,95 @@ let test_resumed_sundays () = module Cel = Colitur_kernel.Celebration module Sl = Colitur_kernel.Slug module Colr = Colitur_kernel.Colour +module Sub = Colitur_kernel.Subject let office dt = (T.temporal dt).Colitur_kernel.Temporal.office let slug_of dt = Sl.to_string (office dt).Cel.slug let rank_of dt = V.rank_to_string (office dt).Cel.rank let colour_of dt = Colr.to_string (office dt).Cel.colour +let subject_of dt = (office dt).Cel.subject let temporal_week dt = (T.temporal dt).Colitur_kernel.Temporal.week +(* RG 17(b) (Caput III, "De Dominicis"), primary text (scan-verified, + docs/research/rules-register.md §4): "festum S. Familiae Iesu, Mariae, + Ioseph, celebrandum dominica prima post Epiphaniam" -- the Holy Family is + celebrated on the first Sunday after Epiphany, EVERY year (unlike RG + 17(a)'s Holy Name, whose narrower 2-5 January window can be empty and + carries an explicit "vel ea deficiente, die 2 ianuarii" fallback in the + calendarium itself -- 7-13 January can never be empty of a Sunday, so + Holy Family needs, and the calendarium carries, no such fallback). + [T.holy_family_sunday] is [named]'s own [week_origin Time_after_epiphany] + formula, restated for its own citation -- not re-derived a second time. + + Deliberately NOT part of [T.named]: [named] is checked before + [T.sunday_slug]'s own Christmastide branch in [T.temporal]'s dispatch, but + this feast's slug, rank and colour are EXACTLY what that branch already + computes for the 7-13 January Sunday (register's own pre-existing note on + [sunday_slug]: "the key stays lectio's") -- the only field that actually + needed to change is [subject] (silently always [Temporal] before this), + so [T.temporal] applies it as a targeted override inside that branch + rather than duplicating slug/rank/colour a second time through [named]'s + 4-tuple. *) +let test_holy_family () = + (* 2026: Epiphany (6 Jan) is a Tuesday, so Holy Family falls 11 January -- + an ordinary instance, no collision with the Baptism (13 Jan). *) + Alcotest.(check string) "2026: slug is the ordinary I-post-Epiphany Sunday key" + "ef-time-after-epiphany-sunday-1" (slug_of (d 2026 1 11)); + Alcotest.(check string) "2026: II class" "class-2" (rank_of (d 2026 1 11)); + Alcotest.(check string) "2026: white" "white" (colour_of (d 2026 1 11)); + Alcotest.(check bool) "2026: subject is Lord (RG 91 entry 14, \"Festa Domini II \ + classis\" -- the mystery of the Holy Family touches Christ)" + true + (subject_of (d 2026 1 11) = Sub.Lord); + (* 2019: Epiphany itself is a Sunday, so the first Sunday AFTER it is the + LATEST possible date, 13 January -- the one date Holy Family collides + with the fixed Commemoration of the Baptism of the Lord (data/ef/ + sanctoral.sexp: "commemoration-of-the-baptism-of-the-lord", also 13 + Jan, II class, subject Lord). [T.temporal]'s own resolution of WHO wins + that day is {!Rite_ef.Precedence_ef}'s business (band/disposition, see + test_precedence_ef.ml), not this function's -- this only proves + [T.temporal] itself still identifies 13 January 2019 as Holy Family's + own date, unconditionally, regardless of what else might compete for + the day once precedence is applied. *) + Alcotest.(check bool) "2019: 6 January is a Sunday" true (D.weekday (d 2019 1 6) = D.Sun); + Alcotest.(check string) "2019: Holy Family falls on the latest possible date, 13 Jan" + "2019-01-13" (D.to_iso8601 (T.holy_family_sunday 2019)); + Alcotest.(check string) "2019: still the same slug" "ef-time-after-epiphany-sunday-1" + (slug_of (T.holy_family_sunday 2019)); + Alcotest.(check bool) "2019: still subject Lord" true (subject_of (T.holy_family_sunday 2019) = Sub.Lord); + (* Every possible weekday for 6 January exercised, proving the formula + never leaves the 7-13 window empty (register's own citation above) -- + not merely the two years already checked above. *) + List.iter + (fun y -> + let hf = T.holy_family_sunday y in + Alcotest.(check bool) (Printf.sprintf "%d: Holy Family is a Sunday" y) true + (D.weekday hf = D.Sun); + Alcotest.(check bool) (Printf.sprintf "%d: Holy Family falls 7-13 January" y) true + (D.month hf = 1 && D.day hf >= 7 && D.day hf <= 13)) + [ 2019; 2020; 2021; 2022; 2023; 2024; 2025; 2026; 2027 ]; + (* Negative control: an ORDINARY Sunday after Epiphany (the 2nd) stays + [subject Temporal] -- the override is scoped to exactly one Sunday a + year, not every Time-after-Epiphany-shaped Sunday. *) + Alcotest.(check bool) "2026: the following Sunday (18 Jan, II after Epiphany) is NOT Lord" + true + (subject_of (d 2026 1 18) = Sub.Temporal) + +(* Three of [test_holy_family]'s own exhaustive-weekday sample, pinned to + their literal ISO dates and independently cross-checked against + `date -d <iso> +%A` (the same discipline test_golden.ml's own header + describes for its own weekday assertions) -- the earliest possible date + (Epiphany a Saturday), the latest (Epiphany a Sunday), and one ordinary + middle case, rather than trusting the structural "always 7-13" check + above on its own without ever anchoring it to real calendar dates. *) +let test_holy_family_no_gap_year () = + Alcotest.(check string) "2024 (6 Jan a Saturday): earliest possible date, 7 Jan" "2024-01-07" + (D.to_iso8601 (T.holy_family_sunday 2024)); + Alcotest.(check string) "2019 (6 Jan a Sunday): latest possible date, 13 Jan" "2019-01-13" + (D.to_iso8601 (T.holy_family_sunday 2019)); + Alcotest.(check string) "2025 (6 Jan a Monday): 12 Jan" "2025-01-12" + (D.to_iso8601 (T.holy_family_sunday 2025)) + (* Regression for a Task 14 Validate finding, reworked (register finding 4): [named] no longer carries its own week at all -- [temporal] computes it uniformly via [week] for every day, named or not -- so "a named day inside @@ -432,6 +514,36 @@ let test_anchors_erosion_is_caught () = [ "ef-ascension"; "ef-nativity" ] (missing_from_anchors ~named_slugs ~anchors:eroded_anchors) +(* Holy Family's own anchor coverage, kept SEPARATE from the two tests above: + it is genuinely NOT one of [T.named]'s outputs (test_holy_family's own + comment explains why -- [T.temporal] applies it as a targeted override + inside [T.sunday_slug]'s branch instead), so [named_slugs_for_year]'s scan + cannot see it, and folding it into [test_anchors_cover_all_named_days]/ + [test_anchors_erosion_is_caught] would misrepresent what those two are + actually proving ("everything [named] produces"). This proves the same + two properties directly against [T.holy_family_sunday] instead: the entry + is present, AND deleting it is caught -- {!Validate}'s own production + anchor check (validate.ml, register/spec §5.7) calls [temporal], not + [named], so it is unaffected by this distinction and already covers this + entry regardless; this is this file's own unit-level guard against the + same erosion. *) +let test_holy_family_anchor_present_and_erosion_is_caught () = + let y = 2026 in + let expected_slug = "ef-time-after-epiphany-sunday-1" in + let expected_date = T.holy_family_sunday y in + Alcotest.(check bool) "the anchor entry exists, at the right date" true + (List.mem (expected_slug, expected_date) (T.anchors y)); + (* Erosion: with the entry struck out, [T.temporal] itself still puts + [expected_slug] at [expected_date] (unaffected -- deleting an [anchors] + row never touches [temporal]'s own computation, only what is CHECKED + against it), so a reader who only trusted [anchors] would no longer be + told to look there at all. *) + let eroded = List.filter (fun (s, dt) -> not (String.equal s expected_slug && dt = expected_date)) (T.anchors y) in + Alcotest.(check bool) "the erosion is caught: the entry no longer appears" false + (List.mem (expected_slug, expected_date) eroded); + Alcotest.(check string) "sanity: temporal itself is unaffected by the anchors-list erosion" + expected_slug (slug_of expected_date) + let test_totality () = (* Every day of 2026 yields an office without raising. Not a slug re-validation -- Slug.t is a private string validated on construction, @@ -453,6 +565,8 @@ let suite_extra = Alcotest.test_case "nativity octave" `Quick test_nativity_octave; Alcotest.test_case "week numbers" `Quick test_week_numbers; Alcotest.test_case "sunday slugs" `Quick test_sunday_slugs; + Alcotest.test_case "holy family (RG 17(b))" `Quick test_holy_family; + Alcotest.test_case "holy family: no gap year (RG 17(b))" `Quick test_holy_family_no_gap_year; Alcotest.test_case "week/sunday_slug agree" `Quick test_week_sunday_slug_agree; Alcotest.test_case "resumed sundays" `Quick test_resumed_sundays; Alcotest.test_case "ferial slugs and ranks" `Quick test_ferial_slugs_and_ranks; @@ -465,6 +579,8 @@ let suite_extra = Alcotest.test_case "totality" `Quick test_totality; Alcotest.test_case "anchors cover all named days" `Quick test_anchors_cover_all_named_days; + Alcotest.test_case "holy family anchor present, erosion caught" `Quick + test_holy_family_anchor_present_and_erosion_is_caught; Alcotest.test_case "anchors erosion is caught" `Quick test_anchors_erosion_is_caught ] let suite = |
