From 0404642614fb1358a6e487a94dd4297a0f38d2b5 Mon Sep 17 00:00:00 2001 From: Will Estes Date: Tue, 11 Aug 2026 14:13:22 -0400 Subject: [PATCH] calendar: add Christ the King, last Sunday of October A 1925 addition, so nowhere in the Monastic Tridentinum 1617 base this project otherwise targets -- content sourced from "Monastic Divino 1930" instead, the Monastic-lineage version of the era that actually carries it. Always wins outright (Duplex I. classis, confirmed live), commemorating whichever Sunday after Pentecost it displaces -- same additive-layering shape as applyOctaves/applyMarianSaturday, run last so nested commemorations (e.g. a Simplex saint already commemorated under that Sunday) survive underneath it. Extended the Lauds psalmody-override mechanism (both the psalm groups and, newly, the chapter/responsory/hymn/versicle bundle) to recognize named temporal winners as unconditionally override-eligible, not just duplex-majus+ sanctoral ones -- generalizing what had been a Marian- Saturday-only special case. Suffrages needed their own explicit check too: isDoubleOrHigher only ever looks at a sanctoral rank, and a named temporal winner has no FeastClass to check at all. Co-Authored-By: Claude Sonnet 5 --- src/calendar/index.ts | 33 +++++++- .../temporal-feasts/christ-the-king.yml | 8 ++ .../christ-the-king.yml | 27 +++++++ .../lauds-capitulum-christ-the-king.yml | 12 +++ .../common/lauds-hymn-christ-the-king.yml | 80 +++++++++++++++++++ .../lauds-responsory-christ-the-king.yml | 21 +++++ .../common/lauds-versicle-christ-the-king.yml | 9 +++ .../christ-the-king-benedictus-antiphon.yml | 9 +++ .../temporal/christ-the-king-collect.yml | 17 ++++ src/hours/lauds-psalmody-overrides.ts | 4 +- src/hours/lauds.ts | 68 +++++++++++----- tests/calendar/christ-the-king.test.ts | 39 +++++++++ tests/hours/lauds-psalmody-overrides.test.ts | 31 +++++++ 13 files changed, 338 insertions(+), 20 deletions(-) create mode 100644 src/data/calendar/temporal-feasts/christ-the-king.yml create mode 100644 src/data/hours/lauds-psalmody-overrides/christ-the-king.yml create mode 100644 src/data/propers/common/lauds-capitulum-christ-the-king.yml create mode 100644 src/data/propers/common/lauds-hymn-christ-the-king.yml create mode 100644 src/data/propers/common/lauds-responsory-christ-the-king.yml create mode 100644 src/data/propers/common/lauds-versicle-christ-the-king.yml create mode 100644 src/data/propers/temporal/christ-the-king-benedictus-antiphon.yml create mode 100644 src/data/propers/temporal/christ-the-king-collect.yml create mode 100644 tests/calendar/christ-the-king.test.ts diff --git a/src/calendar/index.ts b/src/calendar/index.ts index 1391b75..11a81b0 100644 --- a/src/calendar/index.ts +++ b/src/calendar/index.ts @@ -1,6 +1,6 @@ import type { Commemoration, DayWinner, LiturgicalDay, SanctoralIdentity, TemporalCategory } from './types'; import { weekdayOf } from './weekday'; -import { resolveSeason, resolveTemporalCategory } from './temporal'; +import { resolveSeason, resolveTemporalCategory, sundayOnOrBefore } from './temporal'; import { resolveTemporalId } from './temporal-id'; import { getSanctoralCandidatesFor } from './feasts'; import { decideOccurrence, isAtLeast, type OccurrenceResult } from './commemorations'; @@ -102,10 +102,41 @@ export function resolveDay(isoDate: string): LiturgicalDay { winner = applyOctaves(isoDate, winner, commemorations); winner = applyMarianSaturday(isoDate, weekday, temporalCategory, winner, commemorations); + winner = applyChristTheKing(isoDate, winner, commemorations); return { date: isoDate, weekday, season, temporalCategory, winner, commemorations }; } +/** The Sunday on or before Oct 31 — always lands in October since Oct 31 + * is at most 6 days after the month's last Sunday. */ +function lastSundayOfOctober(year: number): string { + return sundayOnOrBefore(`${year}-10-31`); +} + +/** + * Per direct instruction: the last Sunday of October is always Christ the + * King, full stop — unlike every other layer in this file, nothing here + * loses gracefully or gets a rank check; whatever was winning (a plain + * numbered Sunday after Pentecost in every ordinary year, but modeled + * generally in case a high-ranked sanctoral candidate is ever assigned to + * that date too) is demoted straight to a commemoration. Confirmed live + * (see data/calendar/temporal-feasts/christ-the-king.yml): Duplex I. + * classis, well above anything that could contest it under this + * project's own calendar. + */ +function applyChristTheKing(isoDate: string, winner: DayWinner, commemorations: Commemoration[]): DayWinner { + const year = Number(isoDate.slice(0, 4)); + if (isoDate !== lastSundayOfOctober(year)) { + return winner; + } + if (winner.kind === 'temporal') { + commemorations.push({ kind: 'temporal', id: winner.id }); + } else { + commemorations.push({ kind: 'sanctoral', id: winner.id, name: winner.name, rank: winner.rank }); + } + return { kind: 'temporal', id: 'christ-the-king' }; +} + /** * Layered on top of everything above, same spirit as applyOctaves: never * changes decideOccurrence's own rules, just relabels the result on a free diff --git a/src/data/calendar/temporal-feasts/christ-the-king.yml b/src/data/calendar/temporal-feasts/christ-the-king.yml new file mode 100644 index 0000000..f5fff5b --- /dev/null +++ b/src/data/calendar/temporal-feasts/christ-the-king.yml @@ -0,0 +1,8 @@ +# A 1925 addition, so nowhere in the Monastic Tridentinum 1617 base this +# project otherwise targets -- content sourced from "Monastic Divino 1930" +# instead (the Monastic-lineage version of the era that actually carries +# it), confirmed live: last Sunday of October, Duplex I. classis, +# commemorating whichever Sunday after Pentecost it would otherwise be. +# See calendar/index.ts's applyChristTheKing for the occurrence rule. +id: christ-the-king +name: "Christ the King" diff --git a/src/data/hours/lauds-psalmody-overrides/christ-the-king.yml b/src/data/hours/lauds-psalmody-overrides/christ-the-king.yml new file mode 100644 index 0000000..b868ffe --- /dev/null +++ b/src/data/hours/lauds-psalmody-overrides/christ-the-king.yml @@ -0,0 +1,27 @@ +# Verified against Divinum Officium ("Monastic Divino 1930" -- see +# data/calendar/temporal-feasts/christ-the-king.yml on why not Monastic +# Tridentinum 1617), Lauds live query, the last Sunday of October +# (2026-10-25). +id: christ-the-king +groups: + - psalms: [92] + antiphon: + la: "Suscitábit * Deus cæli regnum quod commínuet et consúmet univérsa regna, et ipsum stabit in ætérnum." + en: "The God of heaven * will set up a kingdom that shall break in pieces and shall consume all these kingdoms, and itself shall stand for ever." + - psalms: [99] + antiphon: + la: "Dedit ei Dóminus * potestátem et honórem et regnum; et omnes pópuli, tribus et linguæ ipsi sérvient." + en: "The Lord gave him * power, and glory, and a kingdom: and all peoples, tribes, and tongues shall serve him." + - psalms: [62] + antiphon: + la: "Exíbunt aquæ vivæ * de Jerúsalem; et erit Dóminus Rex super omnem terram." + en: "Living waters shall go out * from Jerusalem; and the Lord shall be king over all the earth." +canticle: + id: canticum-trium-puerorum + antiphon: + la: "Magnificábitur * usque ad términos terræ, et erit iste pax." + en: "He shall be magnified * even to the ends of the earth, and he shall be our peace." +laudate: + antiphon: + la: "Gens et regnum * quod non servíerit tibi, períbit: et gentes solitúdine vastabúntur." + en: "The nation and the kingdom * that will not serve thee, shall perish: and the Gentiles shall be wasted with desolation." diff --git a/src/data/propers/common/lauds-capitulum-christ-the-king.yml b/src/data/propers/common/lauds-capitulum-christ-the-king.yml new file mode 100644 index 0000000..b0f019c --- /dev/null +++ b/src/data/propers/common/lauds-capitulum-christ-the-king.yml @@ -0,0 +1,12 @@ +# Verified against Divinum Officium ("Monastic Divino 1930"), Lauds live +# query, the last Sunday of October (2026-10-25). +id: lauds-capitulum-christ-the-king +text: + la: "Fratres: Grátias ágimus Deo Patri, qui dignos nos fecit in partem sortis sanctórum in lúmine, qui erípuit nos de potestáte tenebrárum, et tránstulit in regnum Fílii dilectiónis suæ." + en: "Brethren: We give thanks to God the Father, who hath made us worthy to be partakers of the lot of the saints in light, who hath delivered us from the power of darkness, and hath translated us into the kingdom of the Son of his love." +citation: + la: "Col 1:12-13" + en: "Col 1:12-13" +status: + la: verified + en: verified diff --git a/src/data/propers/common/lauds-hymn-christ-the-king.yml b/src/data/propers/common/lauds-hymn-christ-the-king.yml new file mode 100644 index 0000000..d565f4c --- /dev/null +++ b/src/data/propers/common/lauds-hymn-christ-the-king.yml @@ -0,0 +1,80 @@ +# Verified against Divinum Officium ("Monastic Divino 1930"), Lauds live +# query, the last Sunday of October (2026-10-25). "Vexilla Christus +# inclita" -- self-contained, including its own final doxology stanza. +id: lauds-hymn-christ-the-king +text: + la: | + Vexílla Christus ínclita + Late triúmphans éxplicat: + Gentes adéste súpplices, + Regíque regum pláudite. + + Non Ille regna cládibus: + Non vi metúque súbdidit + Alto levátus stípite, + Amóre traxit ómnia. + + O ter beáta cívitas + Cui rite Christus ímperat, + Quæ jussa pergit éxsequi + Edícta mundo cǽlitus! + + Non arma flagrant ímpia, + Pax usque firmat fœ́dera, + Arrídet et concórdia, + Tutus stat ordo cívicus. + + Servat fides connúbia, + Juvénta pubet íntegra, + Pudíca florent límina + Domésticis virtútibus. + + Optáta nobis spléndeat + Lux ista, Rex dulcíssime: + Te, pace adépta cándida, + Adóret orbis súbditus. + + Glória tibi, Dómine, + Qui sceptra mundi témperas, + Cum Patre, et Sancto Spíritu, + In sempitérna sǽcula. + Amen. + en: | + Christ's flag unfurled in glory raised, + Triumphing o'er the serpent's stings; + Come humbly forward all ye lands, + Applauding the great King of kings. + + No force or threat doth he need use, + Not by coersion leadeth he; + Raised on the cross he draweth all, + With love unto his fruitful tree. + + O! Thrice blest city of the Lord, + Wherein Christ ruleth without dearth; + Fervently she doth execute + His heavenly edicts to the earth, + + No fiery weapon can e'er harm, + The peace 'stablished by Jesus' hand; + In happiness and unity, + The ranks of Christ secure do stand. + + A marriage blessed by faith in thee, + Yields virtuous offspring from the womb; + Like seed in fertile soil doth grow, + In virtuous homes do children bloom. + + We yearn and long to shine on us, + Thy light, O sweet and winsome King; + All thus submitted to thy reign, + To us, thy gift of peace do bring. + + All glory be, Jesu, to thee, + Thy scepter over all that be; + All glory, as is ever meet, + To Father and to Paraclete. + Amen. +status: + la: verified + en: verified diff --git a/src/data/propers/common/lauds-responsory-christ-the-king.yml b/src/data/propers/common/lauds-responsory-christ-the-king.yml new file mode 100644 index 0000000..e636728 --- /dev/null +++ b/src/data/propers/common/lauds-responsory-christ-the-king.yml @@ -0,0 +1,21 @@ +# Verified against Divinum Officium ("Monastic Divino 1930"), Lauds live +# query, the last Sunday of October (2026-10-25). +id: lauds-responsory-christ-the-king +text: + la: | + R.br. Data est mihi * Omnis potéstas. + R. Data est mihi * Omnis potéstas. + V. In cælo et in terra. + R. Omnis potéstas. + V. Glória Patri, et Fílio, * et Spirítui Sancto. + R. Data est mihi * Omnis potéstas. + en: | + R.br. Given to me is * All power. + R. Given to me is * All power. + V. In heaven and in earth. + R. All power. + V. Glory be to the Father, and to the Son, * and to the Holy Ghost. + R. Given to me is * All power. +status: + la: verified + en: verified diff --git a/src/data/propers/common/lauds-versicle-christ-the-king.yml b/src/data/propers/common/lauds-versicle-christ-the-king.yml new file mode 100644 index 0000000..1abcbfa --- /dev/null +++ b/src/data/propers/common/lauds-versicle-christ-the-king.yml @@ -0,0 +1,9 @@ +# Verified against Divinum Officium ("Monastic Divino 1930"), Lauds live +# query, the last Sunday of October (2026-10-25). +id: lauds-versicle-christ-the-king +text: + la: "V. Multiplicábitur ejus impérium.\nR. Et pacis non erit finis." + en: "V. His empire shall be multiplied.\nR. And there shall be no end of peace." +status: + la: verified + en: verified diff --git a/src/data/propers/temporal/christ-the-king-benedictus-antiphon.yml b/src/data/propers/temporal/christ-the-king-benedictus-antiphon.yml new file mode 100644 index 0000000..1adf6fb --- /dev/null +++ b/src/data/propers/temporal/christ-the-king-benedictus-antiphon.yml @@ -0,0 +1,9 @@ +# Verified against Divinum Officium ("Monastic Divino 1930"), Lauds live +# query, the last Sunday of October (2026-10-25). +id: christ-the-king-benedictus-antiphon +text: + la: "Fecit nos Deo * et Patri suo regnum, primogénitus mortuórum, et Princeps regum terræ, allelúja." + en: "He hath made us * a kingdom to God and his Father: the first begotten of the dead, and the Prince of the kings of the earth, alleluia." +status: + la: verified + en: verified diff --git a/src/data/propers/temporal/christ-the-king-collect.yml b/src/data/propers/temporal/christ-the-king-collect.yml new file mode 100644 index 0000000..9f661f5 --- /dev/null +++ b/src/data/propers/temporal/christ-the-king-collect.yml @@ -0,0 +1,17 @@ +# Verified against Divinum Officium ("Monastic Divino 1930"), Lauds live +# query, the last Sunday of October (2026-10-25). +id: christ-the-king-collect +text: + la: | + Orémus. + Omnípotens sempitérne Deus, qui in dilécto Fílio tuo, universórum Rege, ómnia instauráre voluísti: concéde propítius; ut cunctæ famíliæ géntium, peccáti vúlnere disgregátæ, ejus suavíssimo subdántur império: + Qui tecum vivit et regnat in unitáte Spíritus Sancti, Deus, per ómnia sǽcula sæculórum. + R. Amen. + en: | + Let us pray. + Almighty and everlasting God, who in thy beloved Son, the King of the whole world, hast willed to restore all things: mercifully grant that all the families of nations, now kept apart by the wound of sin, may be brought under the sweet yoke of his rule. + Who with thee liveth and reigneth, in the unity of the Holy Spirit, God, world without end. + R. Amen. +status: + la: verified + en: verified diff --git a/src/hours/lauds-psalmody-overrides.ts b/src/hours/lauds-psalmody-overrides.ts index 21d614b..dd58ac9 100644 --- a/src/hours/lauds-psalmody-overrides.ts +++ b/src/hours/lauds-psalmody-overrides.ts @@ -11,7 +11,9 @@ export interface LaudsPsalmodyOverride { id: string; groups: { psalms: number[]; antiphon: Partial> }[]; - canticle: { id: string; antiphon: Partial> }; + // `split`: see hours/lauds.ts's PsalmodyBlock — no current override + // needs it, but the shape stays aligned with the plain weekday default. + canticle: { id: string; antiphon: Partial>; split?: number }; laudate: { antiphon: Partial> }; } diff --git a/src/hours/lauds.ts b/src/hours/lauds.ts index 4d2ce92..e7627ae 100644 --- a/src/hours/lauds.ts +++ b/src/hours/lauds.ts @@ -4,7 +4,7 @@ import { resolveDay, isAtLeast } from '../calendar'; import { getDayLabel } from '../calendar/day-label'; import { getPsalmVerses } from '../psalter'; import { getCanticle } from './lauds-canticles'; -import { getLaudsPsalmodyOverride } from './lauds-psalmody-overrides'; +import { getLaudsPsalmodyOverride, type LaudsPsalmodyOverride } from './lauds-psalmody-overrides'; import { getOpeningVersicleId } from './opening-versicle'; import { getMarianAntiphonId, getMarianAntiphonLabel } from './marian-antiphon'; import { isDoubleOrHigher } from './antiphon'; @@ -34,19 +34,25 @@ interface PsalmodyBlock { } const laudsAntiphons = laudsAntiphonsData as Record; +// Named temporal winners with real standing of their own, unconditionally +// override-eligible (no rank check — they aren't "a saint" competing with +// the weekday default the way one is, and neither carries a FeastClass at +// all since DayWinner's temporal variant never does). Grows one entry at +// a time as each is built, same as everywhere else in this codebase. +const ALWAYS_OVERRIDE_TEMPORAL_IDS = new Set(['marian-saturday', 'christ-the-king']); + /** * A duplex-majus+ saint's own proper psalmody (per-feast, not per-Common — - * explicit choice), or Marian Saturday's (unconditional whenever it wins, - * no rank threshold — it isn't "a saint" competing with the weekday - * default the way one is). Falls back to `undefined` — the plain weekday - * default — for a duplex-majus+ saint with no override authored yet, same - * honest incremental-content convention as everywhere else in this - * codebase; it's not gated behind whether content exists, just behind - * whether it's *eligible* to override at all. + * explicit choice), or one of the named temporal feasts above. Falls back + * to `undefined` — the plain weekday default — for a duplex-majus+ saint + * with no override authored yet, same honest incremental-content + * convention as everywhere else in this codebase; it's not gated behind + * whether content exists, just behind whether it's *eligible* to override + * at all. */ -function getPsalmodyOverrideFor(day: LiturgicalDay): PsalmodyBlock | undefined { - if (day.winner.kind === 'temporal' && day.winner.id === 'marian-saturday') { - return getLaudsPsalmodyOverride('marian-saturday'); +function getPsalmodyOverrideFor(day: LiturgicalDay): LaudsPsalmodyOverride | undefined { + if (day.winner.kind === 'temporal' && ALWAYS_OVERRIDE_TEMPORAL_IDS.has(day.winner.id)) { + return getLaudsPsalmodyOverride(day.winner.id); } if (day.winner.kind === 'sanctoral' && isAtLeast(day.winner.rank, 'duplex-majus')) { return getLaudsPsalmodyOverride(day.winner.id); @@ -166,12 +172,31 @@ function resolvePsalmody(day: LiturgicalDay): ResolvedPart[] { return parts; } -function resolveOffice(weekday: Weekday): ResolvedPart[] { +/** A duplex-majus+/Marian-Saturday override's own capitulum/responsory/ + * hymn/versicle (data/propers/common/lauds-{part}-${id}.yml), when + * authored — most overrides only bother with the psalmody (see + * getPsalmodyOverrideFor) and leave this bundle at its plain weekday + * default, which is exactly what falls back here when the id-keyed + * capitulum doesn't exist (same dual-missing-status "not authored" + * signal used throughout this codebase, not a special case). */ +function resolveOffice(day: LiturgicalDay): ResolvedPart[] { + const overrideId = getPsalmodyOverrideFor(day)?.id; + if (overrideId) { + const chapter = resolveCommon(`lauds-capitulum-${overrideId}`); + if (chapter.status.la !== 'missing' || chapter.status.en !== 'missing') { + return [ + { kind: 'chapter', text: chapter }, + { kind: 'responsory', text: resolveCommon(`lauds-responsory-${overrideId}`) }, + { kind: 'hymn', text: resolveCommon(`lauds-hymn-${overrideId}`) }, + { kind: 'versicle', text: resolveCommon(`lauds-versicle-${overrideId}`) }, + ]; + } + } return [ - { kind: 'chapter', text: resolveCommon(capitulumId(weekday)) }, - { kind: 'responsory', text: resolveCommon(`lauds-responsory-${weekday}`) }, - { kind: 'hymn', text: resolveCommon(`lauds-hymn-${weekday}`) }, - { kind: 'versicle', text: resolveCommon(`lauds-versicle-${weekday}`) }, + { kind: 'chapter', text: resolveCommon(capitulumId(day.weekday)) }, + { kind: 'responsory', text: resolveCommon(`lauds-responsory-${day.weekday}`) }, + { kind: 'hymn', text: resolveCommon(`lauds-hymn-${day.weekday}`) }, + { kind: 'versicle', text: resolveCommon(`lauds-versicle-${day.weekday}`) }, ]; } @@ -190,7 +215,7 @@ function resolvePart(part: HourPart, day: LiturgicalDay): ResolvedPart[] { case 'lauds-psalmody': return resolvePsalmody(day); case 'lauds-office': - return resolveOffice(day.weekday); + return resolveOffice(day); case 'benedictus': { const { incipit, full } = splitNamedAntiphon(getBenedictusAntiphon(day).text); const opening = isDoubleOrHigher(day.winner) ? full : incipit; @@ -204,7 +229,14 @@ function resolvePart(part: HourPart, day: LiturgicalDay): ResolvedPart[] { case 'day-collects': return getDayCollects(day).map((text) => ({ kind: 'prayer' as const, text })); case 'suffrages': { - if (part.omitOnDouble && isDoubleOrHigher(day.winner)) { + // isDoubleOrHigher only ever looks at a sanctoral rank — Christ the + // King is modeled as a named temporal winner (see + // ALWAYS_OVERRIDE_TEMPORAL_IDS above) with no FeastClass to check at + // all, so it needs its own explicit inclusion here. Confirmed live: + // "Suffragium{omittitur}" — the whole set drops, same as any other + // Double-or-higher day. + const isChristTheKing = day.winner.kind === 'temporal' && day.winner.id === 'christ-the-king'; + if (part.omitOnDouble && (isDoubleOrHigher(day.winner) || isChristTheKing)) { return []; } // Confirmed live: Our Lady's Saturday drops both "Of the Holy Cross" diff --git a/tests/calendar/christ-the-king.test.ts b/tests/calendar/christ-the-king.test.ts new file mode 100644 index 0000000..7b60782 --- /dev/null +++ b/tests/calendar/christ-the-king.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from 'vitest'; +import { resolveDay } from '../../src/calendar'; +import { getDayLabel } from '../../src/calendar/day-label'; + +// A 1925 addition, not in the Monastic Tridentinum 1617 base this project +// otherwise targets -- content sourced from "Monastic Divino 1930" +// instead. Always wins the last Sunday of October, commemorating +// whichever Sunday after Pentecost it displaces. +describe('Christ the King (applyChristTheKing)', () => { + it('wins outright on the last Sunday of October, commemorating the Sunday it displaces', () => { + const day = resolveDay('2026-10-25'); // last Sunday of October 2026 + expect(day.winner).toEqual({ kind: 'temporal', id: 'christ-the-king' }); + expect(day.commemorations).toContainEqual({ kind: 'temporal', id: 'post-pentecost-22' }); + expect(getDayLabel(day)).toBe('Christ the King'); + }); + + it('preserves a nested commemoration: a Simplex saint already commemorated under the ordinary Sunday stays commemorated', () => { + // Ss. Chrysanthus and Daria, Simplex, land on 2026-10-25 -- already + // commemorated under the Sunday per the ordinary-sunday rule before + // Christ the King further displaces that Sunday itself. + const day = resolveDay('2026-10-25'); + expect(day.commemorations).toContainEqual({ + kind: 'sanctoral', + id: 'ss-chrysanthus-and-daria', + name: 'Ss. Chrysanthus and Daria, Martyrs', + rank: 'simplex', + }); + }); + + it('does not apply on any other Sunday in October', () => { + const day = resolveDay('2026-10-18'); + expect(day.winner).not.toEqual({ kind: 'temporal', id: 'christ-the-king' }); + }); + + it('correctly finds a different last Sunday of October in a year where Oct 31 itself is a Sunday', () => { + const day = resolveDay('2027-10-31'); // 2027-10-31 is a Sunday + expect(day.winner).toEqual({ kind: 'temporal', id: 'christ-the-king' }); + }); +}); diff --git a/tests/hours/lauds-psalmody-overrides.test.ts b/tests/hours/lauds-psalmody-overrides.test.ts index 4598ff8..b0234e7 100644 --- a/tests/hours/lauds-psalmody-overrides.test.ts +++ b/tests/hours/lauds-psalmody-overrides.test.ts @@ -51,4 +51,35 @@ describe('Lauds psalmody override (duplex-majus+ sanctoral, and Marian Saturday) 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']); }); + + it("substitutes Christ the King's own proper psalmody and office bundle, and omits the suffrages entirely (Duplex I. classis)", () => { + const ordo = resolveOrdo('lauds', '2026-10-25'); + 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('The God of heaven'); + + const chapter = ordo.parts.find((p) => p.kind === 'chapter'); + expect(chapter?.kind === 'chapter' ? chapter.text.citation?.en : undefined).toBe('Col 1:12-13'); + + const hymn = ordo.parts.find((p) => p.kind === 'hymn'); + expect(hymn?.kind === 'hymn' ? hymn.text.text.la : undefined).toContain('Vexílla Christus ínclita'); + + const benedictus = ordo.parts.find((p) => p.kind === 'canticle' && p.canticleId === 'benedictus'); + expect(benedictus?.kind === 'canticle' ? benedictus.antiphon?.text.en : undefined).toContain( + 'He hath made us', + ); + + // Two extra collects: the commemorated Sunday's own, and the Simplex + // saint already commemorated under that Sunday before Christ the King + // displaced it too -- see tests/calendar/christ-the-king.test.ts. + const collects = ordo.parts.filter((p) => p.kind === 'prayer'); + expect(collects.length).toBe(3); + + const suffrages = ordo.parts.filter((p) => p.kind === 'preces' && p.label && p.label !== 'Salve Regina'); + expect(suffrages).toEqual([]); + }); });