4b6d55d56c
Lectio1-3 in ember-september-friday.yml were one continuous, unbroken Gregory homily on Luke 7:36-50, split only to fill the fixed 3-lesson Nocturn slot count -- the "source doesn't genuinely split" case this store's own established default (post-pentecost-15.yml) already recombines elsewhere. User caught this live on /2026-09-18/matins showing 3 identically-labeled lessons in a row. Keeps Lectio1's own responsory per the "first one, when several collapse" convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
137 lines
7.5 KiB
TypeScript
137 lines
7.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { resolveDay } from '../../src/calendar';
|
|
import { getDayLabel } from '../../src/calendar/day-label';
|
|
import { resolveOrdo } from '../../src/hours';
|
|
import { getDayCollect } from '../../src/hours/resolve-common';
|
|
|
|
// September Ember days (Wed/Fri/Sat nearest Sept 14) and Advent Ember days
|
|
// (Wed/Fri/Sat after the 3rd Sunday of Advent) are privileged ferias, not
|
|
// named feasts -- calendar/ember-days.ts relabels the day's own temporal
|
|
// id (or adds a commemoration) so their real proper content is found,
|
|
// while calendar/temporal.ts's septemberEmberDayOffset feeds
|
|
// resolveTemporalCategory's privileged-feria-minor threshold for the
|
|
// September precedence contest (Advent's already covered by its own
|
|
// season default). All dates below verified against the live reference
|
|
// engine (Monastic Tridentinum 1617) during authoring, 2026-08-22.
|
|
describe('Ember days', () => {
|
|
it('September Ember Wednesday 2026 (Sept 16): Ss. Cornelius & Cyprian (Semiduplex) win outright, Ember commemorated', () => {
|
|
const day = resolveDay('2026-09-16');
|
|
expect(day.temporalCategory).toBe('privileged-feria-minor');
|
|
expect(day.winner).toEqual({
|
|
kind: 'sanctoral',
|
|
id: 'ss-cornelius-and-cyprian',
|
|
name: 'Ss. Cornelius, Pope, and Cyprian, Bishop, Martyrs',
|
|
nameLa: 'Sanctus Cornelius, Papa, et Sanctus Cyprianus, Episcopus, Martyres',
|
|
rank: 'semiduplex',
|
|
});
|
|
expect(day.commemorations).toContainEqual({ kind: 'temporal', id: 'ember-september-wednesday' });
|
|
expect(getDayCollect(day).status.en).toBe('verified');
|
|
});
|
|
|
|
it('September Ember Friday 2026 (Sept 18): St. Joseph of Cupertino (Duplex) wins outright, Ember commemorated', () => {
|
|
const day = resolveDay('2026-09-18');
|
|
expect(day.temporalCategory).toBe('privileged-feria-minor');
|
|
expect(day.winner.kind).toBe('sanctoral');
|
|
expect(day.commemorations).toContainEqual({ kind: 'temporal', id: 'ember-september-friday' });
|
|
});
|
|
|
|
it('September Ember Saturday 2026 (Sept 19): St. Januarius (Semiduplex) wins outright, Ember commemorated', () => {
|
|
const day = resolveDay('2026-09-19');
|
|
expect(day.temporalCategory).toBe('privileged-feria-minor');
|
|
expect(day.winner.kind).toBe('sanctoral');
|
|
expect(day.commemorations).toContainEqual({ kind: 'temporal', id: 'ember-september-saturday' });
|
|
});
|
|
|
|
it('Advent Ember Wednesday/Friday/Saturday 2026 (Dec 16/18/19): no competing saint, the Ember day itself wins outright with real content', () => {
|
|
for (const [date, id] of [
|
|
['2026-12-16', 'ember-advent-wednesday'],
|
|
['2026-12-18', 'ember-advent-friday'],
|
|
['2026-12-19', 'ember-advent-saturday'],
|
|
] as const) {
|
|
const day = resolveDay(date);
|
|
expect(day.winner).toEqual({ kind: 'temporal', id });
|
|
expect(getDayCollect(day).status.en).toBe('verified');
|
|
}
|
|
});
|
|
|
|
it("Lauds' Benedictus antiphon is the Ember day's own proper text on all 6 winning-outright days (2026-08-22 added the mechanism/collect/Matins, but not this -- fixed here)", () => {
|
|
// Wed/Fri/Sat winning outright for the September quarter, one per
|
|
// weekday (2026's own dates are all saint-outranked, see above) --
|
|
// found by scanning forward from 2023, all live-verified against the
|
|
// reference engine.
|
|
const cases: [string, string][] = [
|
|
['2023-09-20', 'Hoc genus'],
|
|
['2024-09-20', 'Múlier'],
|
|
['2025-09-20', 'Illumináre, Dómine'],
|
|
['2026-12-16', 'Missus est Gábriel'],
|
|
['2026-12-18', 'Ex quo facta est'],
|
|
['2026-12-19', 'Quómodo fiet istud'],
|
|
];
|
|
for (const [date, expectedStart] of cases) {
|
|
const ordo = resolveOrdo('lauds', date);
|
|
const idx = ordo.parts.findIndex((p) => p.kind === 'canticle' && p.canticleId === 'benedictus');
|
|
const antiphon = ordo.parts[idx + 1] as { text: { text: { la?: string }; status: { la?: string; en?: string } } };
|
|
expect(antiphon.text.status.la).toBe('verified');
|
|
expect(antiphon.text.status.en).toBe('verified');
|
|
expect(antiphon.text.text.la).toContain(expectedStart);
|
|
}
|
|
});
|
|
|
|
it("Vespers' Magnificat antiphon is proper on Ember Wednesday (Sept and Advent) where the reference engine actually renders it, not overridden by a Sunday/O-Antiphon", () => {
|
|
// September Ember Friday/Saturday and Advent Ember Friday/Saturday
|
|
// are excluded here: Saturday's Second Vespers is always ceded to the
|
|
// following Sunday (live-verified), and Advent Ember Friday/Saturday
|
|
// (always Dec 18/19) are always inside the O Antiphons' own
|
|
// unconditional Dec 17-23 window -- both real, not gaps.
|
|
const ordo = resolveOrdo('vespers', '2026-12-16');
|
|
const idx = ordo.parts.findIndex((p) => p.kind === 'canticle' && p.canticleId === 'magnificat');
|
|
const antiphon = ordo.parts[idx + 1] as { text: { status: { la?: string; en?: string } } };
|
|
expect(antiphon.text.status.la).toBe('verified');
|
|
expect(antiphon.text.status.en).toBe('verified');
|
|
});
|
|
|
|
it("the day label names the Ember day itself, both when it's demoted to a commemoration and when it wins outright -- previously never shown anywhere in the UI (2026-09-03 fix)", () => {
|
|
// Demoted cases (2026): a saint wins outright, Ember rides along as a
|
|
// commemoration -- collectCommemorations' temporal branch previously
|
|
// only matched resolveTemporalId(day.date), never an Ember-relabeled id.
|
|
expect(getDayLabel(resolveDay('2026-09-16')).en).toContain('Ember Wednesday in September');
|
|
expect(getDayLabel(resolveDay('2026-09-18')).en).toContain('Ember Friday in September');
|
|
expect(getDayLabel(resolveDay('2026-09-19')).en).toContain('Ember Saturday in September');
|
|
// Winning-outright cases: previously fell through to the plain
|
|
// ordinal weekday label with no Ember mention at all.
|
|
expect(getDayLabel(resolveDay('2023-09-20')).en).toBe('Ember Wednesday in September (Feria) — Vigil of St. Matthew');
|
|
expect(getDayLabel(resolveDay('2026-12-16')).en).toBe('Ember Wednesday in Advent (Feria)');
|
|
expect(getDayLabel(resolveDay('2026-12-18')).en).toBe('Ember Friday in Advent (Feria)');
|
|
expect(getDayLabel(resolveDay('2026-12-19')).en).toContain('Ember Saturday in Advent (Feria)');
|
|
});
|
|
|
|
it('Matins renders real, distinct, verified lessons+responsories for each of the 6 Ember days', () => {
|
|
// September Ember Friday (2026-09-18) is 1, not >=3, since 2026-09-04:
|
|
// ember-september-friday.yml's own Gregory homily (Lectio1-3) was
|
|
// recombined into a single reading -- it's one continuous, unbroken
|
|
// homily on the same pericope, split only to fill the fixed 3-lesson
|
|
// slot count, the same "source doesn't genuinely split" default
|
|
// post-pentecost-15.yml's own doc comment already establishes. The
|
|
// other 5 Ember days' own nocturn-readings files still keep their
|
|
// historical 3-way split (each with its own genuinely distinct
|
|
// responsory) -- not revisited here, out of scope for this fix.
|
|
const dates: [string, number][] = [
|
|
['2026-09-16', 3],
|
|
['2026-09-18', 1],
|
|
['2026-09-19', 3],
|
|
['2026-12-16', 3],
|
|
['2026-12-18', 3],
|
|
['2026-12-19', 3],
|
|
];
|
|
for (const [date, minLessons] of dates) {
|
|
const ordo = resolveOrdo('matins', date);
|
|
const lessons = ordo.parts.filter((p) => p.kind === 'lesson') as {
|
|
text: { status: { en?: string } };
|
|
responsory?: { text: { status: { en?: string } } };
|
|
}[];
|
|
const emberLessons = lessons.filter((l) => l.text.status.en === 'verified' && l.responsory !== undefined);
|
|
expect(emberLessons.length).toBeGreaterThanOrEqual(minLessons);
|
|
}
|
|
});
|
|
});
|