Three separate resolver files (terce.ts/sext.ts/none.ts), not a shared little-hour module — they're structurally identical but kept independent by choice. Ordo verified against Divinum Officium (Monastic Tridentinum 1617): opening versicle, self-contained hymn, one antiphon framing three psalms (no closing repeat, unlike Prime), a capitulum with its own embedded short versicle/response, the day's own collect, and a closing identical across all three hours. Also fills in psalter-distribution.yml's previously-missing Sunday/ Monday/Tuesday psalm entries (Ps 118 sections for Sunday/Monday; Tuesday turns out to share Wednesday/Thursday's native gradual-psalm set, not a distinct one). Friday/Saturday's antiphons are derived from each day's first psalm rather than reusing the native gradual antiphons, since those two days already use a redistributed, non-native psalm set. hours/types.ts gains a 'day-collect' part kind — the first ordo to use the day's own Proper collect rather than a fixed text. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { resolveOrdo } from '../../src/hours';
|
||||
|
||||
// Clean per-annum dates, verified against Divinum Officium (Monastic
|
||||
// Tridentinum 1617) to have no sanctoral override — see
|
||||
// data/psalter-distribution.yml and data/hours/*-antiphons.yml for how
|
||||
// each was chosen.
|
||||
const SUNDAY = '2026-08-09';
|
||||
const MONDAY = '2026-08-31';
|
||||
const TUESDAY = '2026-10-13';
|
||||
|
||||
const LITTLE_HOURS = ['terce', 'sext', 'none'] as const;
|
||||
const PSALMS_BY_HOUR: Record<(typeof LITTLE_HOURS)[number], [number, number, number]> = {
|
||||
terce: [119, 120, 121],
|
||||
sext: [122, 123, 124],
|
||||
none: [125, 126, 127],
|
||||
};
|
||||
|
||||
describe.each(LITTLE_HOURS)('resolveOrdo(%s, ...)', (hourId) => {
|
||||
it('is implemented', () => {
|
||||
const ordo = resolveOrdo(hourId, TUESDAY);
|
||||
expect(ordo.notImplemented).toBeUndefined();
|
||||
expect(ordo.hourId).toBe(hourId);
|
||||
});
|
||||
|
||||
it('resolves three psalms from the gradual set on a plain weekday, framed by one antiphon', () => {
|
||||
const ordo = resolveOrdo(hourId, TUESDAY);
|
||||
const psalmParts = ordo.parts.filter((p) => p.kind === 'psalm');
|
||||
expect(psalmParts.map((p) => (p.kind === 'psalm' ? p.psalmNumber : undefined))).toEqual(
|
||||
PSALMS_BY_HOUR[hourId],
|
||||
);
|
||||
expect(psalmParts[0]?.kind === 'psalm' ? psalmParts[0].antiphon?.status.en : undefined).toBe('verified');
|
||||
// No closing repeat of the antiphon — confirmed against the real engine.
|
||||
expect(psalmParts[1]?.kind === 'psalm' ? psalmParts[1].antiphon : undefined).toBeUndefined();
|
||||
expect(psalmParts[2]?.kind === 'psalm' ? psalmParts[2].antiphon : undefined).toBeUndefined();
|
||||
expect(ordo.parts.some((p) => p.kind === 'antiphon')).toBe(false);
|
||||
});
|
||||
|
||||
it('slices Psalm 118 by section on Sunday and Monday instead of the gradual set', () => {
|
||||
const sunday = resolveOrdo(hourId, SUNDAY);
|
||||
const sundayPsalms = sunday.parts.filter((p) => p.kind === 'psalm');
|
||||
expect(sundayPsalms.every((p) => (p.kind === 'psalm' ? p.psalmNumber === 118 : false))).toBe(true);
|
||||
|
||||
const monday = resolveOrdo(hourId, MONDAY);
|
||||
const mondayPsalms = monday.parts.filter((p) => p.kind === 'psalm');
|
||||
expect(mondayPsalms.every((p) => (p.kind === 'psalm' ? p.psalmNumber === 118 : false))).toBe(true);
|
||||
|
||||
// Different sections, not a repeat of Sunday's.
|
||||
const sundayVerseNs = sundayPsalms[0]?.kind === 'psalm' ? sundayPsalms[0].verses.map((v) => v.n) : [];
|
||||
const mondayVerseNs = mondayPsalms[0]?.kind === 'psalm' ? mondayPsalms[0].verses.map((v) => v.n) : [];
|
||||
expect(sundayVerseNs).not.toEqual(mondayVerseNs);
|
||||
});
|
||||
|
||||
it('resolves the hymn as fully self-contained, verified text (no seasonal doxology append)', () => {
|
||||
const ordo = resolveOrdo(hourId, TUESDAY);
|
||||
const hymn = ordo.parts.find((p) => p.kind === 'hymn');
|
||||
expect(hymn && hymn.kind === 'hymn' ? hymn.text.status.en : undefined).toBe('verified');
|
||||
expect(hymn && hymn.kind === 'hymn' ? hymn.text.text.la : undefined).toContain('Amen.');
|
||||
});
|
||||
|
||||
it('resolves the per-annum capitulum with its citation', () => {
|
||||
const ordo = resolveOrdo(hourId, TUESDAY);
|
||||
const chapter = ordo.parts.find((p) => p.kind === 'chapter');
|
||||
expect(chapter && chapter.kind === 'chapter' ? chapter.text.status.en : undefined).toBe('verified');
|
||||
expect(chapter && chapter.kind === 'chapter' ? chapter.text.citation?.en : undefined).toBeDefined();
|
||||
});
|
||||
|
||||
it("resolves the day's own collect as the closing prayer", () => {
|
||||
const ordo = resolveOrdo(hourId, SUNDAY); // post-pentecost-11 — real, verified collect
|
||||
const prayer = ordo.parts.find((p) => p.kind === 'prayer');
|
||||
expect(prayer && prayer.kind === 'prayer' ? prayer.text.status.en : undefined).toBe('verified');
|
||||
expect(prayer && prayer.kind === 'prayer' ? prayer.text.text.en : undefined).toContain('Let us pray.');
|
||||
});
|
||||
|
||||
it('ends with the shared Little Hour conclusion, identical across all three hours', () => {
|
||||
const ordo = resolveOrdo(hourId, TUESDAY);
|
||||
const conclusion = ordo.parts.find((p) => p.kind === 'preces' && p.label === 'Conclusion');
|
||||
expect(conclusion && conclusion.kind === 'preces' ? conclusion.text.text.en : undefined).toContain(
|
||||
'Let us bless the Lord.',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -188,8 +188,8 @@ describe('resolveOrdo("prime", ...)', () => {
|
||||
});
|
||||
|
||||
describe('resolveOrdo for not-yet-built hours', () => {
|
||||
it('flags terce as not implemented rather than throwing', () => {
|
||||
const ordo = resolveOrdo('terce', MONDAY);
|
||||
it('flags lauds as not implemented rather than throwing', () => {
|
||||
const ordo = resolveOrdo('lauds', MONDAY);
|
||||
expect(ordo.notImplemented).toBe(true);
|
||||
expect(ordo.parts).toEqual([]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user