1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
module N = Colitur_kernel.Names
module L = Colitur_kernel.Lang
module C = Colitur_kernel.Citation
module DS = Colitur_kernel.Date_spec
module D = Colitur_kernel.Date
let lang s = L.of_string_exn s
let test_names_basics () =
let n = N.of_list [ (lang "pl", "Wielkanoc"); (lang "la", "Pascha") ] in
Alcotest.(check (option string)) "find la" (Some "Pascha") (N.find n (lang "la"));
Alcotest.(check (option string)) "find en" None (N.find n (lang "en"));
Alcotest.(check (option string)) "fallback la then en"
(Some "Pascha") (N.find_first n [ lang "en"; lang "la" ]);
(* canonical order is by language code, so sexp output is byte-stable *)
Alcotest.(check (list string)) "canonical order" [ "la"; "pl" ]
(List.map (fun (l, _) -> L.to_string l) (N.to_list n))
let test_names_set_remove () =
let n = N.of_list [ (lang "la", "Pascha") ] in
let n = N.set n (lang "en") "Easter" in
Alcotest.(check (option string)) "set en" (Some "Easter") (N.find n (lang "en"));
let n = N.set n (lang "en") "Easter Sunday" in
Alcotest.(check (option string)) "last writer wins" (Some "Easter Sunday") (N.find n (lang "en"));
let n = N.remove n (lang "en") in
Alcotest.(check (option string)) "removed" None (N.find n (lang "en"))
let test_names_of_list_duplicates () =
(* of_list with duplicate language: last entry wins *)
let n = N.of_list [ (lang "en", "Easter"); (lang "en", "Easter Sunday") ] in
Alcotest.(check (option string)) "of_list last wins" (Some "Easter Sunday") (N.find n (lang "en"))
let test_names_sexp_canonical () =
(* Two Names.t with different input order must serialize identically *)
let n1 = N.of_list [ (lang "pl", "Wielkanoc"); (lang "la", "Pascha"); (lang "en", "Easter") ] in
let n2 = N.of_list [ (lang "en", "Easter"); (lang "pl", "Wielkanoc"); (lang "la", "Pascha") ] in
Alcotest.(check bool) "same sexp despite different input order"
true (N.sexp_of_t n1 = N.sexp_of_t n2)
let test_names_sexp_roundtrip () =
let n = N.of_list [ (lang "la", "Pascha"); (lang "en", "Easter") ] in
let sexp = N.sexp_of_t n in
let n' = N.t_of_sexp sexp in
Alcotest.(check bool) "sexp roundtrip" true (n = n')
let test_citation () =
let c = { C.part = C.Gospel; reference = "Jn 3:16" } in
Alcotest.(check bool) "sexp roundtrip" true (C.t_of_sexp (C.sexp_of_t c) = c);
Alcotest.(check string) "part string" "gospel" (C.part_to_string C.Gospel);
Alcotest.(check bool) "part of_string" true (C.part_of_string "first" = Some C.First)
let test_date_spec () =
(match DS.fixed ~month:3 ~day:25 with
| Ok ds -> (
match DS.resolve ds ~year:2026 with
| Some d -> Alcotest.(check string) "resolve" "2026-03-25" (D.to_iso8601 d)
| None -> Alcotest.fail "resolve returned None")
| Error e -> Alcotest.failf "fixed: %s" e);
(* Feb 29 is a legitimate fixed date that simply does not occur every year. *)
(match DS.fixed ~month:2 ~day:29 with
| Ok ds ->
Alcotest.(check bool) "Feb 29 resolves in 2024" true (DS.resolve ds ~year:2024 <> None);
Alcotest.(check bool) "Feb 29 absent in 2026" true (DS.resolve ds ~year:2026 = None)
| Error e -> Alcotest.failf "Feb 29 must be constructible: %s" e);
Alcotest.(check bool) "reject Feb 30" true (Result.is_error (DS.fixed ~month:2 ~day:30));
Alcotest.(check bool) "reject Apr 31" true (Result.is_error (DS.fixed ~month:4 ~day:31));
Alcotest.(check bool) "reject month 13" true (Result.is_error (DS.fixed ~month:13 ~day:1))
let test_date_spec_sexp_roundtrip () =
(match DS.fixed ~month:3 ~day:25 with
| Ok ds ->
let sexp = DS.sexp_of_t ds in
let ds' = DS.t_of_sexp sexp in
Alcotest.(check bool) "sexp roundtrip" true (ds = ds')
| Error e -> Alcotest.failf "fixed: %s" e)
module Cel = Colitur_kernel.Celebration
module S = Colitur_kernel.Slug
module Col = Colitur_kernel.Colour
module Sub = Colitur_kernel.Subject
(* A throwaway rank vocabulary, to exercise the parametric type. *)
type demo_rank = High | Low [@@deriving sexp]
let test_celebration () =
let c =
Cel.make ~slug:(S.of_string_exn "ef-easter-sunday")
~names:(N.of_list [ (lang "la", "Dominica Resurrectionis") ])
~rank:High ~colour:Col.White ~subject:Sub.Lord ~layer:"temporal" ()
in
Alcotest.(check string) "slug" "ef-easter-sunday" (S.to_string c.Cel.slug);
Alcotest.(check bool) "default citations empty" true (c.Cel.citations = []);
let sexp = Cel.sexp_of_t sexp_of_demo_rank c in
Alcotest.(check bool) "sexp roundtrip" true (Cel.t_of_sexp demo_rank_of_sexp sexp = c)
let suite =
( "Names/Citation/DateSpec",
[ Alcotest.test_case "names basics" `Quick test_names_basics;
Alcotest.test_case "names set/remove" `Quick test_names_set_remove;
Alcotest.test_case "names of_list duplicates" `Quick test_names_of_list_duplicates;
Alcotest.test_case "names sexp canonical" `Quick test_names_sexp_canonical;
Alcotest.test_case "names sexp roundtrip" `Quick test_names_sexp_roundtrip;
Alcotest.test_case "citation" `Quick test_citation;
Alcotest.test_case "date_spec" `Quick test_date_spec;
Alcotest.test_case "date_spec sexp roundtrip" `Quick test_date_spec_sexp_roundtrip;
Alcotest.test_case "celebration" `Quick test_celebration ] )
|