calendar: model Our Lady's Saturday as a real occurrence outcome
Deploy / deploy (push) Successful in 49s
Deploy / deploy (push) Successful in 49s
On a free Saturday (nothing privileged already claims it, no active octave), the day's own identity is now "Our Lady's Saturday" rather than an anonymous ordinary-feria -- mirrors privileged-feria-minor exactly, per direct instruction: Semiduplex-or-higher still wins outright (Marian Saturday doesn't apply at all); below that (Simplex, and Vigil, both under semiduplex on the FeastClass scale) loses and is commemorated instead. Layered additively after decideOccurrence, same shape as applyOctaves -- never touches decideOccurrence's own rules. Needed a real winner identity (not just the plain temporal feria id) so day-label/antiphon/collect sourcing can recognize it -- added a temporal-feasts record purely for that, and taught getDayLabel to check it for any named temporal winner (a small, free improvement for Christmas/Pentecost's own labels too, previously unhandled). hours: build the duplex-majus+ Lauds psalmody override (per-feast) lauds-psalmody no longer unconditionally uses the plain weekday default: a duplex-majus-or-higher sanctoral winner, or Our Lady's Saturday (unconditional, no rank threshold -- it isn't competing with the weekday default the way a saint is), now substitutes its own proper psalm groups/antiphons/canticle. Per-feast, not per-Common, per direct instruction, even though most duplex-majus+ saints across the year don't have one authored yet and fall back to the plain weekday default until they do. Content for the worked example (St. Lawrence's own day) surfaced a real bug while sourcing it: st-lawrence-antiphon.yml had been read from the Roman/secular rite's own [Ant 1], not Monastic 1617's actual Benedictus antiphon for that day -- corrected from a fresh live query. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { resolveOrdo } from '../../src/hours';
|
||||
|
||||
describe('Lauds psalmody override (duplex-majus+ sanctoral, and Marian Saturday)', () => {
|
||||
it("substitutes St. Lawrence's own proper psalmody on his own day (Duplex II. classis, above the threshold)", () => {
|
||||
const ordo = resolveOrdo('lauds', '2026-08-10');
|
||||
const psalmNumbers = ordo.parts
|
||||
.filter((p) => p.kind === 'psalm')
|
||||
.map((p) => (p.kind === 'psalm' ? p.psalmNumber : undefined));
|
||||
expect(psalmNumbers).toEqual([66, 92, 99, 62, 148, 149, 150]);
|
||||
|
||||
const ps92 = ordo.parts.find((p) => p.kind === 'psalm' && p.psalmNumber === 92);
|
||||
expect(ps92?.kind === 'psalm' ? ps92.antiphon?.text.en : undefined).toContain('Lawrence went in to be a martyr');
|
||||
|
||||
const canticle = ordo.parts.find((p) => p.kind === 'canticle' && p.canticleId !== 'benedictus');
|
||||
expect(canticle?.kind === 'canticle' ? canticle.canticleId : undefined).toBe('canticum-trium-puerorum');
|
||||
});
|
||||
|
||||
it("doesn't override a saint below duplex-majus (e.g. Semiduplex) -- plain weekday psalmody still used", () => {
|
||||
// 2026-09-19: St. Januarius, Semiduplex (below duplex-majus) -- see
|
||||
// tests/calendar/marian-saturday.test.ts for confirming he wins
|
||||
// outright as the day's winner despite it being a Saturday.
|
||||
const ordo = resolveOrdo('lauds', '2026-09-19');
|
||||
const psalmNumbers = ordo.parts
|
||||
.filter((p) => p.kind === 'psalm')
|
||||
.map((p) => (p.kind === 'psalm' ? p.psalmNumber : undefined));
|
||||
// Saturday's own plain default (Ps 50 + 142), not the Common-shaped
|
||||
// override -- no lauds-psalmody-overrides/st-januarius.yml exists.
|
||||
expect(psalmNumbers).toEqual([66, 50, 142, 148, 149, 150]);
|
||||
});
|
||||
|
||||
it("substitutes Our Lady's Saturday own proper psalmody, unconditionally, whenever it wins", () => {
|
||||
const ordo = resolveOrdo('lauds', '2026-10-24'); // a clean, otherwise-empty Marian Saturday
|
||||
const psalmNumbers = ordo.parts
|
||||
.filter((p) => p.kind === 'psalm')
|
||||
.map((p) => (p.kind === 'psalm' ? p.psalmNumber : undefined));
|
||||
expect(psalmNumbers).toEqual([66, 92, 99, 62, 148, 149, 150]);
|
||||
|
||||
const ps92 = ordo.parts.find((p) => p.kind === 'psalm' && p.psalmNumber === 92);
|
||||
expect(ps92?.kind === 'psalm' ? ps92.antiphon?.text.la : undefined).toContain('Dum esset Rex');
|
||||
|
||||
const benedictus = ordo.parts.find((p) => p.kind === 'canticle' && p.canticleId === 'benedictus');
|
||||
expect(benedictus?.kind === 'canticle' ? benedictus.antiphon?.text.la : undefined).toContain('Beáta Dei Génitrix');
|
||||
|
||||
const collect = ordo.parts.find((p) => p.kind === 'prayer');
|
||||
expect(collect?.kind === 'prayer' ? collect.text.text.en : undefined).toContain('glorious intercession of the Blessed Mary');
|
||||
});
|
||||
|
||||
it("drops the Holy Cross and Blessed Virgin Mary suffrages on Our Lady's Saturday, keeping Apostles and Peace", () => {
|
||||
const ordo = resolveOrdo('lauds', '2026-10-24');
|
||||
const labels = ordo.parts.filter((p) => p.kind === 'preces' && p.label).map((p) => (p.kind === 'preces' ? p.label : undefined));
|
||||
expect(labels).toEqual(['Of the Holy Apostles Peter and Paul', 'For Peace', 'Salve Regina']);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user