Files
vu/tests/calendar/ember-days.test.ts
T
will 4b6d55d56c Recombine Ember September Friday's split Matins homily into one reading
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
2026-09-04 09:31:51 -04:00

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);
}
});
});