summaryrefslogtreecommitdiff
path: root/internal/calendar/coverage_test.go
blob: c464e560e634776c03e02047a537f242d646fdcc (plain) (blame)
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
107
package calendar_test

import (
	"strings"
	"testing"
	"time"

	"github.com/lukaszkasprzak/lectio/internal/caldata"
	"github.com/lukaszkasprzak/lectio/internal/calendar"
)

func day(y int, m time.Month, d int) time.Time {
	return time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
}

// TestEFCoverage checks the EF special days added for full coverage: the movable
// vigils, the September/Advent Ember days, and the transfer of an impeded feast.
// (2027: Easter is March 28.)
func TestEFCoverage(t *testing.T) {
	sel := calendar.DefaultSelection()
	sel.Form = "old"
	layers := []calendar.Layer{caldata.Tridentine()}

	cases := []struct {
		date time.Time
		slug string
		rank calendar.Rank
	}{
		{day(2027, time.September, 22), "ef-september-ember-wed", calendar.RankClass2},
		{day(2027, time.December, 15), "ef-advent-ember-wed", calendar.RankClass2},
		{day(2027, time.May, 5), "ef-ascension-vigil", calendar.RankClass2},
		{day(2027, time.May, 15), "ef-pentecost-vigil", calendar.RankClass1},
		{day(2027, time.June, 23), "vigil-of-the-nativity-of-st-john-the-baptist", calendar.RankClass2},
		// Annunciation (Mar 25) is in Holy Week in 2027, so it is transferred to
		// the Monday after Low Sunday (Apr 5); Mar 25 itself keeps the Triduum.
		{day(2027, time.April, 5), "annunciation-of-the-blessed-virgin-mary", calendar.RankClass1},
	}
	for _, c := range cases {
		got := calendar.Compute(c.date, sel, layers)
		if got.Observed.Slug != c.slug || got.Observed.Rank != c.rank {
			t.Errorf("%s: observed %q/%v, want %q/%v", c.date.Format("2006-01-02"),
				got.Observed.Slug, got.Observed.Rank, c.slug, c.rank)
		}
	}
	// The Annunciation must NOT also appear on its impeded fixed date.
	if got := calendar.Compute(day(2027, time.March, 25), sel, layers); strings.Contains(got.Observed.Slug, "annunciation") {
		t.Errorf("2027-03-25 (Holy Thursday) wrongly observes %q", got.Observed.Slug)
	}
	// All Souls: Nov 2 2025 is a Sunday, so it is transferred to Nov 3.
	if got := calendar.Compute(day(2025, time.November, 3), sel, layers); got.Observed.Slug != "commemoration-of-all-souls" {
		t.Errorf("2025-11-03: observed %q, want the transferred All Souls", got.Observed.Slug)
	}
}

// TestOFOrdinaryTimeFerialWeek guards the post-Pentecost Ordinary-Time ferial
// week numbering, which was one too high (a weekday took Christ-the-King's week
// instead of its preceding Sunday's). calapi confirms these canonical weeks.
func TestOFOrdinaryTimeFerialWeek(t *testing.T) {
	sel := calendar.DefaultSelection()
	layers := []calendar.Layer{caldata.Universal()}
	for _, c := range []struct {
		date time.Time
		slug string
	}{
		{day(2026, time.June, 8), "ordinary-10-mon"},  // calapi: 10th week
		{day(2026, time.June, 15), "ordinary-11-mon"}, // calapi: 11th week
	} {
		if got := calendar.Compute(c.date, sel, layers); got.Observed.Slug != c.slug {
			t.Errorf("%s: OT ferial slug %q, want %q", c.date.Format("2006-01-02"), got.Observed.Slug, c.slug)
		}
	}
}

// TestOFMovableMemorials checks Mary, Mother of the Church, the Immaculate Heart,
// and the two-obligatory-memorials rule.
func TestOFMovableMemorials(t *testing.T) {
	sel := calendar.DefaultSelection() // Ordinary Form
	layers := []calendar.Layer{caldata.Universal()}

	// Mary, Mother of the Church: Monday after Pentecost (2027: May 17), with its
	// proper readings (not the ferial).
	got := calendar.Compute(day(2027, time.May, 17), sel, layers)
	if got.Observed.Slug != "mary-mother-of-the-church" {
		t.Errorf("2027-05-17: observed %q, want mary-mother-of-the-church", got.Observed.Slug)
	}
	var first string
	for _, m := range got.Observed.Masses {
		for _, r := range m.Readings {
			if r.Part == "first" {
				first = r.Citation
			}
		}
	}
	if first != "Genesis 3:9-15,20" {
		t.Errorf("Mary MotC first reading = %q, want Genesis 3:9-15,20", first)
	}
	// Two obligatory memorials coincide (St Anthony of Padua + the Immaculate
	// Heart, 2026-06-13): both become optional and the ferial is observed.
	if got := calendar.Compute(day(2026, time.June, 13), sel, layers); got.Observed.Rank != calendar.RankFerial {
		t.Errorf("2026-06-13: observed %q/%v, want the ferial", got.Observed.Slug, got.Observed.Rank)
	}
	// But Mary, Mother of the Church keeps precedence over a coinciding memorial
	// (2028-06-05, with St Boniface).
	if got := calendar.Compute(day(2028, time.June, 5), sel, layers); got.Observed.Slug != "mary-mother-of-the-church" {
		t.Errorf("2028-06-05: observed %q, want mary-mother-of-the-church", got.Observed.Slug)
	}
}