aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/test_emit.ml46
-rw-r--r--test/test_ics.ml16
-rw-r--r--test/test_view.ml52
3 files changed, 113 insertions, 1 deletions
diff --git a/test/test_emit.ml b/test/test_emit.ml
index 0a40054..bd9cadc 100644
--- a/test/test_emit.ml
+++ b/test/test_emit.ml
@@ -91,6 +91,20 @@ let test_csv_quotes_commas () =
Alcotest.(check bool) "Joseph's comma-bearing name is quoted whole, not split" true
(contains ~needle:"\"St. Joseph, Spouse of the Bl. Virgin Mary\"" joseph)
+(* W5: a day with [[First; Second; Gospel]] MUST NOT widen the CSV's own
+ fixed 16-column header -- see [Emit_csv]'s own comment for why a variable
+ column count is not RFC 4180-legal. Guards the deliberate-drop claim made
+ there with an actual assertion, not only a comment: still exactly 16
+ fields (the [Second] reading genuinely absent from the row, not merely
+ untested), and the header is unchanged. *)
+let test_csv_does_not_widen_for_a_third_citation () =
+ let out = Csv.year (Test_view.three_citation_view ()) in
+ let lines = String.split_on_char '\n' out |> List.filter (fun l -> l <> "") in
+ Alcotest.(check string) "header unchanged"
+ "date,rite,season,season_name,week,slug,name,weekday,rank,rank_name,colour,colour_name,subject,first,gospel,comms"
+ (List.hd lines);
+ Alcotest.(check int) "row still 16 fields" 16 (List.length (parse_csv_row (List.nth lines 1)))
+
let test_json_parses_back () =
let out = Json.year (view_2027 ()) in
Alcotest.(check bool) "starts as an object" true (out.[0] = '{');
@@ -133,6 +147,8 @@ let suite =
( "Emit/csv+json",
[ Alcotest.test_case "csv header and rows" `Quick test_csv_header_and_rows;
Alcotest.test_case "csv quotes commas" `Quick test_csv_quotes_commas;
+ Alcotest.test_case "csv does not widen for a third citation" `Quick
+ test_csv_does_not_widen_for_a_third_citation;
Alcotest.test_case "json parses back" `Quick test_json_parses_back;
Alcotest.test_case "json escapes" `Quick test_json_escapes;
Alcotest.test_case "json passes utf8 through" `Quick test_utf8_passes_through_json ] )
@@ -194,10 +210,38 @@ let test_xml_escapes_live_data () =
String.iteri (fun i c -> if c = '&' && not (is_entity_at out i) then incr bare) out;
Alcotest.(check int) "no bare, unescaped '&' anywhere in the document" 0 !bare
+(* First index [needle] starts at in [hay], or fails the test -- a small,
+ dependency-free substring search (no [Str], per the project's frozen
+ deps), sufficient for the fixed literal tags searched for below. *)
+let index_of ~needle hay =
+ let n = String.length needle and h = String.length hay in
+ let rec go i =
+ if i + n > h then Alcotest.failf "%S not found" needle
+ else if String.sub hay i n = needle then i
+ else go (i + 1)
+ in
+ go 0
+
+(* W5: unlike CSV, XML has no fixed column count -- [<citation>] is a
+ repeated element, so a [Second] reading is a third element, not a
+ breaking schema change. Checks BOTH the presence and the ORDER
+ (first, second, gospel -- reading order, not declaration order in
+ [Emit_xml.day]'s own three [cite] calls). *)
+let test_xml_three_citations () =
+ let out = Xml.year (Test_view.three_citation_view ()) in
+ Alcotest.(check bool) "second citation present" true
+ (contains ~needle:"<citation part=\"second\">1 Cor 1:3-9</citation>" out);
+ let first_at = index_of ~needle:"<citation part=\"first\">" out in
+ let second_at = index_of ~needle:"<citation part=\"second\">" out in
+ let gospel_at = index_of ~needle:"<citation part=\"gospel\">" out in
+ Alcotest.(check bool) "reading order: first < second < gospel" true
+ (first_at < second_at && second_at < gospel_at)
+
let xml_suite =
( "Emit/xml",
[ Alcotest.test_case "shape" `Quick test_xml_shape;
Alcotest.test_case "tags balance" `Quick test_xml_tags_balance;
Alcotest.test_case "escapes data" `Quick test_xml_escapes_data;
Alcotest.test_case "escapes live data (2035, Fabian & Sebastian)" `Quick
- test_xml_escapes_live_data ] )
+ test_xml_escapes_live_data;
+ Alcotest.test_case "three citations, in reading order" `Quick test_xml_three_citations ] )
diff --git a/test/test_ics.ml b/test/test_ics.ml
index 7d1ca37..fef20c7 100644
--- a/test/test_ics.ml
+++ b/test/test_ics.ml
@@ -78,6 +78,20 @@ let test_text_escaped () =
let rec f i = i + n <= String.length joseph && (String.sub joseph i n = re || f (i + 1)) in
f 0)
+(* W5: like [Emit_xml] and unlike [Emit_csv], DESCRIPTION is free text, not
+ a fixed column -- a [Second] reading is a third joined line, not a
+ breaking schema change. [second] is spliced BETWEEN [first]/[gospel]
+ (reading order, OLM 1981 Praenotanda n. 69.1), matching
+ [Emit_xml.day]'s own ordering. Asserts the exact line rather than a
+ substring: short enough (69 octets incl. the "DESCRIPTION:" property
+ name) to stay under RFC 5545's 75-octet fold threshold, so it is one
+ physical line, not merely one logical property split across several. *)
+let test_description_carries_second_reading_in_order () =
+ let ls = lines (Ics.year (Test_view.three_citation_view ())) in
+ let desc = List.find (fun l -> count_prefix "DESCRIPTION:" [ l ] = 1) ls in
+ Alcotest.(check string) "Epistle, Second, Gospel, in that order"
+ "DESCRIPTION:Epistle Rom 1:1-7\\nSecond 1 Cor 1:3-9\\nGospel John 1:1-14" desc
+
let test_dtstamp_is_a_parameter () =
let a = Ics.year ~dtstamp:"20200101T000000Z" (Test_view.view_of 2027) in
let b = Ics.year ~dtstamp:"20210101T000000Z" (Test_view.view_of 2027) in
@@ -138,6 +152,8 @@ let suite =
Alcotest.test_case "folded and CRLF" `Quick test_every_line_folded_and_crlf;
Alcotest.test_case "no RRULE" `Quick test_no_rrule;
Alcotest.test_case "text escaped" `Quick test_text_escaped;
+ Alcotest.test_case "description carries second reading in order" `Quick
+ test_description_carries_second_reading_in_order;
Alcotest.test_case "DTSTAMP is a parameter" `Quick test_dtstamp_is_a_parameter;
Alcotest.test_case "DTEND omitted at domain end" `Quick test_dtend_omitted_at_domain_end;
Alcotest.test_case "all 9999 DTENDs are eight digits" `Quick test_all_9999_dtends_are_eight_digits ] )
diff --git a/test/test_view.ml b/test/test_view.ml
index a542419..bbed8ee 100644
--- a/test/test_view.ml
+++ b/test/test_view.ml
@@ -140,6 +140,41 @@ let test_day_fields () =
Alcotest.(check bool) "first citation present" true (as_str (get [ "first" ] d) <> "");
Alcotest.(check bool) "gospel citation present" true (as_str (get [ "gospel" ] d) <> "")
+(* W5's own reason for existing: OF cannot reach [View.of_days] through the
+ CLI today ([bin/main.ml]'s [reject_rite_for] refuses [--rite] on every
+ command that would build one), so the only honest way to exercise a
+ THREE-citation day through this path is to drive [View.of_days] directly
+ with a hand-built [Liturgical_day.t] -- which rite actually produced the
+ citations is irrelevant to the render layer; only [citations] itself is.
+ Reuses a real EF day's [temporal]/[observed]/etc. (record update syntax)
+ purely as scaffolding, with its own two real citations discarded and
+ replaced -- nothing here asserts anything EF-specific. Exposed (not
+ [let () = ...] inline) so [test_emit.ml]/[test_ics.ml] can drive the same
+ synthetic day through [Emit_xml]/[Emit_ics] without duplicating it. *)
+let three_citation_view () =
+ let module K = Colitur_kernel in
+ let real = List.hd (days_of_year 2027) in
+ let synthetic =
+ { real with
+ K.Liturgical_day.citations =
+ [ { K.Citation.part = K.Citation.First; reference = "Rom 1:1-7" };
+ { K.Citation.part = K.Citation.Second; reference = "1 Cor 1:3-9" };
+ { K.Citation.part = K.Citation.Gospel; reference = "Ioann 1:1-14" } ] }
+ in
+ let lang = Lazy.force default_lang in
+ V.of_days ~lang ~sigla:(Test_support.default_sigla lang) ~vocab:Rite_ef.Vocab_ef.vocab ~rite:"ef" ~year:2027
+ [ synthetic ]
+
+let test_three_citation_day_exposes_all_three () =
+ let v = three_citation_view () in
+ let d = List.hd (as_list (get [ "days" ] v)) in
+ Alcotest.(check string) "first" "Rom 1:1-7" (as_str (get [ "first" ] d));
+ Alcotest.(check string) "second" "1 Cor 1:3-9" (as_str (get [ "second" ] d));
+ (* "Ioann" -> "John": the stored (Latin) book abbreviation renders through
+ [sigla] the same as [first]/[gospel] always have -- proving [second]
+ shares the identical [Sigla.format] path, not a second, divergent one. *)
+ Alcotest.(check string) "gospel" "John 1:1-14" (as_str (get [ "gospel" ] d))
+
(* Exactly one of the six colour booleans is true on every day of a whole year:
a template that keys a cell colour off them can never get no colour or two. *)
let test_exactly_one_colour_flag () =
@@ -164,6 +199,21 @@ let keys_of = function
| T.Obj kvs -> List.sort compare (List.map fst kvs)
| _ -> Alcotest.fail "expected an object"
+(* W5: EF's own citations are always exactly [First; Gospel]
+ ({!Rite_ef}'s own [citation_shapes]) -- so no EF day's key set should
+ EVER include "second" (or any of the other still-unbuilt parts:
+ "psalm"/"tract"/"alleluia"/"sequence"). Confirms the "absent key, not a
+ present key holding the empty string" half of [View.citation_fields]'s
+ own design, over a whole year rather than one sampled day. *)
+let test_ef_days_never_carry_a_second_key () =
+ let v = view_of 2027 in
+ List.iter
+ (fun d ->
+ List.iter
+ (fun k -> if List.mem k (keys_of d) then Alcotest.failf "%s carries an unexpected key %s" (as_str (get [ "iso" ] d)) k)
+ [ "second"; "psalm"; "tract"; "alleluia"; "sequence" ])
+ (as_list (get [ "days" ] v))
+
let test_padding_and_real_share_key_set () =
let v = view_of 2027 in
let jan = List.hd (as_list (get [ "months" ] v)) in
@@ -318,6 +368,8 @@ let suite =
Alcotest.test_case "padding cells flagged" `Quick test_padding_cells_are_flagged;
Alcotest.test_case "first week flagged" `Quick test_first_week_flag;
Alcotest.test_case "padding and real days share key set" `Quick test_padding_and_real_share_key_set;
+ Alcotest.test_case "EF days never carry a second key" `Quick test_ef_days_never_carry_a_second_key;
+ Alcotest.test_case "three-citation day exposes all three" `Quick test_three_citation_day_exposes_all_three;
Alcotest.test_case "week num_roman and month fields" `Quick test_week_num_roman_and_month_fields;
Alcotest.test_case "week date span" `Quick test_week_date_span;
Alcotest.test_case "single_day agrees with span and cells" `Quick test_single_day_agrees_with_span_and_cells;