Files
vu/tests/calendar/august-sanctoral.test.ts
T
will da3b6e893e
Deploy / deploy (push) Successful in 46s
calendar: model octaves as a generic, data-driven mechanism
Adds calendar/octaves.ts: a lookback over the past week collecting every
octave (sanctoral or temporal) still active on a date, stacking multiple
at once (Christmas + St. Stephen + St. John + Holy Innocents all
commemorated together within the Christmas Octave). Declared via an
optional `octave` field on a saint's own record or a new small
TemporalFeastRecord (calendar/temporal-feasts.ts) for temporal ids like
Christmas/Pentecost that didn't have a metadata record before -- data-
driven per user design discussion, with `{ enabled: true }` alone using
sensible defaults (8 days, semiduplex threshold) so a minimal declaration
works without authored content.

Wired into resolveDay as a post-processing layer: doesn't change how a
single day's own precedence contest is decided, just adds commemorations
for active octaves and occasionally overrides the winner when the
occurring saint doesn't clear the strictest active octave's threshold.

Populated so far: St. Lawrence's own octave (the one that repeatedly cost
real saints their spot in August), the three Comites Christi octaves
(Stephen/John/Innocents -- Thomas of Canterbury deliberately excluded,
per discussion), and Pentecost's (duplex threshold, user-specified).
Pentecost's octave offsets are also removed from temporal-categories.yml's
privileged-feria-major classification, letting a real candidate reach the
new octave layer instead of being transferred away first -- with the
side effect that Pentecost's own Ember Saturday no longer forces a
transfer (a sub-threshold saint is now commemorated in place instead, see
tests/calendar/transfer.test.ts's updated case). Assumption, Nativity
BVM, Immaculate Conception, and All Saints' own octaves are not yet
populated with octave data -- deliberately deferred to a follow-up pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 07:23:43 -04:00

102 lines
4.6 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { resolveDay } from '../../src/calendar';
import { getDayCollect } from '../../src/hours/resolve-common';
// Clean per-annum years for these dates (no Sunday collision) — see
// data/calendar/saints/*.yml for per-saint source detail.
describe('August sanctoral pull (first pass)', () => {
it('St. Peter\'s Chains wins outright at Duplex majus with a real proper collect and antiphon, over a genuinely unwinnable stub', () => {
const day = resolveDay('2026-08-01');
expect(day.winner).toEqual({
kind: 'sanctoral',
id: 'st-peter-ad-vincula',
name: "St. Peter's Chains",
rank: 'duplex-majus',
});
expect(day.commemorations).toEqual([
{ kind: 'sanctoral', id: 'ss-machabees', name: 'Ss. Machabees, Martyrs', rank: 'simplex' },
]);
expect(getDayCollect(day).status.en).toBe('verified');
});
it('a saint normally blocked by the generic Saturday-of-Mary votive wins independently once the date falls on another weekday', () => {
const day = resolveDay('2027-08-02'); // a Monday
expect(day.winner).toEqual({
kind: 'sanctoral',
id: 'st-stephen-i',
name: 'St. Stephen I, Pope and Martyr',
rank: 'simplex',
});
expect(day.commemorations).toEqual([]);
});
it('St. Donatus wins outright every year in this app, unlike the real reference engine where he is occasionally displaced by a multi-day forward transfer this app does not model', () => {
// In the real Monastic 1617 engine, when Aug 3 (the Finding of St.
// Stephen) falls on a Sunday, it transfers forward past several
// higher-rank fixed feasts (Dominic, Our Lady of the Snows, the
// Transfiguration) and lands on Aug 7, displacing Donatus for that
// year (confirmed live for 2025 and 2031, both Aug-3-Sunday years).
// This app's transfer machinery doesn't chase a multi-day skip search
// like that -- see st-donatus.yml -- so Donatus wins here in every
// year, including ones where the real engine would displace him; a
// known, documented simplification rather than a guess.
const ordinary = resolveDay('2026-08-07');
const wouldHaveTransferredInReality = resolveDay('2031-08-07');
for (const day of [ordinary, wouldHaveTransferredInReality]) {
expect(day.winner).toEqual({
kind: 'sanctoral',
id: 'st-donatus',
name: 'St. Donatus, Bishop and Martyr',
rank: 'simplex',
});
}
});
it("the Vigil of St. Lawrence's own vigil rank forces primacy over a real, otherwise-unranked secondary saint", () => {
const day = resolveDay('2025-08-09');
expect(day.winner).toEqual({
kind: 'sanctoral',
id: 'vigil-of-st-lawrence',
name: 'Vigil of St. Lawrence',
rank: 'vigil',
});
expect(day.commemorations).toEqual([
{ kind: 'sanctoral', id: 'st-romanus', name: 'St. Romanus, Martyr', rank: 'simplex' },
]);
});
it('St. Lawrence wins outright at Duplex II. classis, distinct from his own Vigil the day before', () => {
const day = resolveDay('2026-08-10');
expect(day.winner).toEqual({
kind: 'sanctoral',
id: 'st-lawrence',
name: 'St. Lawrence, Martyr',
rank: 'duplex-2-classis',
});
expect(getDayCollect(day).status.en).toBe('verified');
});
it('dates within St. Lawrence and the Assumption\'s own octaves still exclude the real secondary saint the source names (St. Clare, Aug 12) -- Clare herself isn\'t modeled yet -- but St. Lawrence\'s own octave (now modeled, see calendar/octaves.ts) correctly keeps commemorating him through it regardless. The Assumption\'s own octave isn\'t populated with octave data yet, so Aug 22 stays plain.', () => {
const clareDay = resolveDay('2025-08-12');
expect(clareDay.winner.kind).toBe('temporal');
expect(clareDay.commemorations).toEqual([{ kind: 'octave', id: 'st-lawrence', name: 'St. Lawrence, Martyr' }]);
const assumptionOctaveDay = resolveDay('2025-08-22');
expect(assumptionOctaveDay.winner.kind).toBe('temporal');
expect(assumptionOctaveDay.commemorations).toEqual([]);
});
it('the Beheading of St. John the Baptist wins outright at Duplex with a real proper collect and antiphon, over a genuinely unwinnable stub', () => {
const day = resolveDay('2025-08-29');
expect(day.winner).toEqual({
kind: 'sanctoral',
id: 'decollation-of-st-john-baptist',
name: 'The Beheading of St. John the Baptist',
rank: 'duplex',
});
expect(day.commemorations).toEqual([
{ kind: 'sanctoral', id: 'st-sabina', name: 'St. Sabina, Martyr', rank: 'simplex' },
]);
expect(getDayCollect(day).status.en).toBe('verified');
});
});