Compare commits

..

2 Commits

Author SHA1 Message Date
will 427ba6756a Show rank in the day label for every winner kind, not just sanctoral
Deploy / deploy (push) Successful in 1m3s
Rank display (added in 1fc4bd7) only fired for a plain sanctoral
winner. Every other case -- an active octave day, a named temporal
feast, a season anchor day (Easter, Trinity Sunday, Epiphany), or the
plain ordinal Sunday/feria fallback -- showed no rank at all, which is
what made today's octave day (Aug 18, day 4 of the Assumption's
octave) show no rank. Octave days use the octave's own already-computed
effective rank; anchor days get a fixed Duplex I Class (Ash Wednesday
excepted -- a privileged feria has no duplex-scale rank); the plain
fallback derives a Semiduplex/Feria label from the day's own
temporalCategory, since Sundays/ferias aren't on the duplex scale at
all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 07:41:57 -04:00
will a68ab716b7 Add rank data to named temporal feast records
TemporalFeastRecord had no rank field at all, so Christmas/Pentecost/
Christ the King/Marian Saturday had nothing for the day label to show.
Christmas, Pentecost, and Christ the King are Duplex I Class (the
latter's own file comment already said so); Marian Saturday is a
Simplex-strength votive commemoration.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 07:41:50 -04:00
10 changed files with 76 additions and 27 deletions
+32 -5
View File
@@ -9,7 +9,7 @@
// become a configurable choice later (the same day->id indirection // become a configurable choice later (the same day->id indirection
// philosophy already used for the sanctoral calendar), not hardcoded here // philosophy already used for the sanctoral calendar), not hardcoded here
// forever — just not built yet. // forever — just not built yet.
import type { Commemoration, FeastClass, LiturgicalDay } from './types'; import type { Commemoration, FeastClass, LiturgicalDay, TemporalCategory } from './types';
import { easterSunday } from './easter'; import { easterSunday } from './easter';
import { adventStart, firstSundayStrictlyAfter, sundayOnOrBefore } from './temporal'; import { adventStart, firstSundayStrictlyAfter, sundayOnOrBefore } from './temporal';
import { addDays, daysBetween, toIsoDate } from './date-math'; import { addDays, daysBetween, toIsoDate } from './date-math';
@@ -50,6 +50,12 @@ interface OrdinalSeason {
/** Used in "the Nth Sunday/week {preposition} {ordinalName}". */ /** Used in "the Nth Sunday/week {preposition} {ordinalName}". */
ordinalName: string; ordinalName: string;
preposition: 'after' | 'of'; preposition: 'after' | 'of';
/** The season's own anchor day's rank, if it sits on the closed
* pre-1955 duplex scale at all — Ash Wednesday (Lent's anchor) doesn't;
* a privileged feria has no duplex-scale rank, so it's left undefined
* and shows no parenthetical, same "omit rather than guess" convention
* `formatRank`/`status` fields use elsewhere. */
anchorRank?: FeastClass;
} }
const ORDINAL_SEASONS: Partial<Record<string, OrdinalSeason>> = { const ORDINAL_SEASONS: Partial<Record<string, OrdinalSeason>> = {
@@ -66,6 +72,7 @@ const ORDINAL_SEASONS: Partial<Record<string, OrdinalSeason>> = {
anchorName: 'Epiphany', anchorName: 'Epiphany',
ordinalName: 'Epiphany', ordinalName: 'Epiphany',
preposition: 'after', preposition: 'after',
anchorRank: 'duplex-1-classis',
}, },
lent: { lent: {
anchorDate: (year) => addDays(toIsoDate(easterSunday(year)), -46), anchorDate: (year) => addDays(toIsoDate(easterSunday(year)), -46),
@@ -80,6 +87,7 @@ const ORDINAL_SEASONS: Partial<Record<string, OrdinalSeason>> = {
anchorName: 'Easter', anchorName: 'Easter',
ordinalName: 'Easter', ordinalName: 'Easter',
preposition: 'after', preposition: 'after',
anchorRank: 'duplex-1-classis',
}, },
trinitytide: { trinitytide: {
anchorDate: (year) => addDays(toIsoDate(easterSunday(year)), 56), anchorDate: (year) => addDays(toIsoDate(easterSunday(year)), 56),
@@ -87,6 +95,7 @@ const ORDINAL_SEASONS: Partial<Record<string, OrdinalSeason>> = {
anchorName: 'Trinity Sunday', anchorName: 'Trinity Sunday',
ordinalName: 'Trinity', ordinalName: 'Trinity',
preposition: 'after', preposition: 'after',
anchorRank: 'duplex-1-classis',
}, },
}; };
@@ -105,7 +114,7 @@ function anchorDayName(day: LiturgicalDay): string | undefined {
} }
const year = Number(day.date.slice(0, 4)); const year = Number(day.date.slice(0, 4));
if (day.date === config.anchorDate(year)) { if (day.date === config.anchorDate(year)) {
return config.anchorName; return config.anchorRank ? `${config.anchorName} (${formatRank(config.anchorRank)})` : config.anchorName;
} }
return undefined; return undefined;
} }
@@ -209,7 +218,7 @@ function temporalLabel(day: LiturgicalDay): string {
* special-cased for that rare edge case (see applyOctaves's own * special-cased for that rare edge case (see applyOctaves's own
* `isOwnStartDay` comment in calendar/index.ts for when it can happen). */ * `isOwnStartDay` comment in calendar/index.ts for when it can happen). */
function octaveLabel(octave: ActiveOctave): string { function octaveLabel(octave: ActiveOctave): string {
return `${ordinal(octave.dayNumber)} Day within the Octave of ${octave.name}`; return `${ordinal(octave.dayNumber)} Day within the Octave of ${octave.name} (${formatRank(octave.wins)})`;
} }
/** Every `kind: 'octave'` commemoration on `day` other than `excludeId` — /** Every `kind: 'octave'` commemoration on `day` other than `excludeId` —
@@ -249,6 +258,24 @@ function formatRank(rank: FeastClass): string {
return RANK_LABELS[rank]; return RANK_LABELS[rank];
} }
/** Sundays/ferias aren't on the `FeastClass` duplex scale at all (that
* scale is for feasts), so this is a separate, plain-string lookup rather
* than another `formatRank` case — used for the final ordinal-temporal-
* label fallback (weekdays, Sundays with no named-feast/anchor-day/octave
* standing of their own). Pre-1955, an ordinary Sunday's own rank is
* Semiduplex regardless of whether it's additionally "privileged"
* (privilege is about resisting supersession, a property of
* `temporalCategory`, not a higher spot on the duplex scale) — so both
* Sunday categories share one label here. */
const TEMPORAL_CATEGORY_RANK_LABELS: Record<TemporalCategory, string> = {
'ordinary-sunday': 'Semiduplex',
'privileged-sunday': 'Semiduplex',
'ordinary-feria': 'Feria',
'privileged-feria-minor': 'Feria',
'privileged-feria': 'Feria',
'privileged-feria-major': 'Feria',
};
/** /**
* The full "day being celebrated" label: a feast name when * The full "day being celebrated" label: a feast name when
* calendar/commemorations.ts says the day has one, combined with (or * calendar/commemorations.ts says the day has one, combined with (or
@@ -280,7 +307,7 @@ export function getDayLabel(day: LiturgicalDay): string {
// temporal-feasts.ts) and fall through to the ordinal label as before. // temporal-feasts.ts) and fall through to the ordinal label as before.
const namedFeast = getTemporalFeastRecord(day.winner.id); const namedFeast = getTemporalFeastRecord(day.winner.id);
if (namedFeast) { if (namedFeast) {
return namedFeast.name; return namedFeast.rank ? `${namedFeast.name} (${formatRank(namedFeast.rank)})` : namedFeast.name;
} }
const commemoratedSaint = day.commemorations.find((c) => c.kind === 'sanctoral'); const commemoratedSaint = day.commemorations.find((c) => c.kind === 'sanctoral');
@@ -323,6 +350,6 @@ export function getDayLabel(day: LiturgicalDay): string {
return [primary, ...rest].join(' — '); return [primary, ...rest].join(' — ');
} }
const temporal = temporalLabel(day); const temporal = `${temporalLabel(day)} (${TEMPORAL_CATEGORY_RANK_LABELS[day.temporalCategory]})`;
return commemoratedSaint ? `${commemoratedSaint.name}${temporal}` : temporal; return commemoratedSaint ? `${commemoratedSaint.name}${temporal}` : temporal;
} }
+2 -1
View File
@@ -5,13 +5,14 @@
// don't need a record at all (they're just collect-text lookups via // don't need a record at all (they're just collect-text lookups via
// propers/index.ts's getTemporalProper); this only exists for the ones // propers/index.ts's getTemporalProper); this only exists for the ones
// that do. // that do.
import type { OctaveConfig } from './types'; import type { FeastClass, OctaveConfig } from './types';
import { easterOffsetOf } from './temporal'; import { easterOffsetOf } from './temporal';
export interface TemporalFeastRecord { export interface TemporalFeastRecord {
id: string; id: string;
name: string; name: string;
octave?: OctaveConfig; octave?: OctaveConfig;
rank?: FeastClass;
} }
const temporalFeastModules = import.meta.glob<{ default: TemporalFeastRecord }>( const temporalFeastModules = import.meta.glob<{ default: TemporalFeastRecord }>(
@@ -6,3 +6,4 @@
# See calendar/index.ts's applyChristTheKing for the occurrence rule. # See calendar/index.ts's applyChristTheKing for the occurrence rule.
id: christ-the-king id: christ-the-king
name: "Christ the King" name: "Christ the King"
rank: duplex-1-classis
@@ -12,5 +12,6 @@
# specifically. # specifically.
id: christmas-day id: christmas-day
name: "Christmas" name: "Christmas"
rank: duplex-1-classis
octave: octave:
enabled: true enabled: true
@@ -5,3 +5,4 @@
# for how a day actually becomes this. # for how a day actually becomes this.
id: marian-saturday id: marian-saturday
name: "Our Lady's Saturday" name: "Our Lady's Saturday"
rank: simplex
@@ -10,6 +10,7 @@
# before it gets the chance. # before it gets the chance.
id: pentecost-sunday id: pentecost-sunday
name: "Pentecost" name: "Pentecost"
rank: duplex-1-classis
octave: octave:
enabled: true enabled: true
wins: duplex wins: duplex
+1 -1
View File
@@ -11,7 +11,7 @@ describe('Christ the King (applyChristTheKing)', () => {
const day = resolveDay('2026-10-25'); // last Sunday of October 2026 const day = resolveDay('2026-10-25'); // last Sunday of October 2026
expect(day.winner).toEqual({ kind: 'temporal', id: 'christ-the-king' }); expect(day.winner).toEqual({ kind: 'temporal', id: 'christ-the-king' });
expect(day.commemorations).toContainEqual({ kind: 'temporal', id: 'post-pentecost-22' }); expect(day.commemorations).toContainEqual({ kind: 'temporal', id: 'post-pentecost-22' });
expect(getDayLabel(day)).toBe('Christ the King'); expect(getDayLabel(day)).toBe('Christ the King (Duplex I Class)');
}); });
it('preserves a nested commemoration: a Simplex saint already commemorated under the ordinary Sunday stays commemorated', () => { it('preserves a nested commemoration: a Simplex saint already commemorated under the ordinary Sunday stays commemorated', () => {
+35 -18
View File
@@ -10,8 +10,8 @@ describe('getDayLabel — ordinal temporal label', () => {
// the full-year sanctoral import), which would win outright and hide // the full-year sanctoral import), which would win outright and hide
// this purely-temporal label case. 2027's Trinity Sunday is May 23, // this purely-temporal label case. 2027's Trinity Sunday is May 23,
// so Trinity Monday is May 24 -- free of any sanctoral entry. // so Trinity Monday is May 24 -- free of any sanctoral entry.
expect(getDayLabel(resolveDay('2027-05-24'))).toBe('Monday after Trinity Sunday'); expect(getDayLabel(resolveDay('2027-05-24'))).toBe('Monday after Trinity Sunday (Feria)');
expect(getDayLabel(resolveDay('2026-01-07'))).toBe('Wednesday after Epiphany'); // Epiphany 2026 is a Tuesday expect(getDayLabel(resolveDay('2026-01-07'))).toBe('Wednesday after Epiphany (Feria)'); // Epiphany 2026 is a Tuesday
}); });
it('names the anchor day itself, not "day after itself"', () => { it('names the anchor day itself, not "day after itself"', () => {
@@ -25,7 +25,7 @@ describe('getDayLabel — ordinal temporal label', () => {
// winner/commemorations breakdown), so the label now reflects her, // winner/commemorations breakdown), so the label now reflects her,
// not Trinity Sunday itself. // not Trinity Sunday itself.
expect(getDayLabel(resolveDay('2026-05-31'))).toBe('The Queenship of the Blessed Virgin Mary (Duplex II Class)'); expect(getDayLabel(resolveDay('2026-05-31'))).toBe('The Queenship of the Blessed Virgin Mary (Duplex II Class)');
expect(getDayLabel(resolveDay('2026-04-05'))).toBe('Easter'); expect(getDayLabel(resolveDay('2026-04-05'))).toBe('Easter (Duplex I Class)');
expect(getDayLabel(resolveDay('2026-02-18'))).toBe('Ash Wednesday'); expect(getDayLabel(resolveDay('2026-02-18'))).toBe('Ash Wednesday');
}); });
@@ -35,7 +35,7 @@ describe('getDayLabel — ordinal temporal label', () => {
// sanctoral import), which wins outright. 2027's Trinity Sunday is // sanctoral import), which wins outright. 2027's Trinity Sunday is
// May 23, so 5 full weeks after the first Sunday-after-Trinity // May 23, so 5 full weeks after the first Sunday-after-Trinity
// (May 30) is Jun 28 -- free of any sanctoral entry. // (May 30) is Jun 28 -- free of any sanctoral entry.
expect(getDayLabel(resolveDay('2027-06-28'))).toBe('Monday in the 5th week after Trinity'); expect(getDayLabel(resolveDay('2027-06-28'))).toBe('Monday in the 5th week after Trinity (Feria)');
}); });
it("Advent's own anchor Sunday is already week 1, not a separate anchor-week case", () => { it("Advent's own anchor Sunday is already week 1, not a separate anchor-week case", () => {
@@ -45,8 +45,8 @@ describe('getDayLabel — ordinal temporal label', () => {
// reference engine, which shows the same pairing), so the label // reference engine, which shows the same pairing), so the label
// reflects both, feast name first, same pattern as Trinity Sunday's // reflects both, feast name first, same pattern as Trinity Sunday's
// own St. Felix I case above. // own St. Felix I case above.
expect(getDayLabel(resolveDay('2025-11-30'))).toBe('St. Andrew, Apostle — The 1st Sunday of Advent'); expect(getDayLabel(resolveDay('2025-11-30'))).toBe('St. Andrew, Apostle — The 1st Sunday of Advent (Semiduplex)');
expect(getDayLabel(resolveDay('2025-12-01'))).toBe('Monday in the 1st week of Advent'); expect(getDayLabel(resolveDay('2025-12-01'))).toBe('Monday in the 1st week of Advent (Feria)');
}); });
it('falls back to weekday + season name for seasons with no ordinal convention modeled', () => { it('falls back to weekday + season name for seasons with no ordinal convention modeled', () => {
@@ -73,7 +73,7 @@ describe('getDayLabel — resumed post-Epiphany Sunday (overflow years)', () =>
// appended to the Sunday's own label per this app's usual // appended to the Sunday's own label per this app's usual
// commemorated-Simplex display. // commemorated-Simplex display.
expect(getDayLabel(resolveDay('2024-11-10'))).toBe( expect(getDayLabel(resolveDay('2024-11-10'))).toBe(
'Ss. Tryphon, Respicius, and Nympha, Martyrs — The 5th Sunday after Epiphany', 'Ss. Tryphon, Respicius, and Nympha, Martyrs — The 5th Sunday after Epiphany (Semiduplex)',
); );
// 2024-11-11 is St. Martin of Tours (Duplex, pre-existing content) -- // 2024-11-11 is St. Martin of Tours (Duplex, pre-existing content) --
// an ordinary (non-privileged) Monday fully yields to a real winning // an ordinary (non-privileged) Monday fully yields to a real winning
@@ -83,7 +83,7 @@ describe('getDayLabel — resumed post-Epiphany Sunday (overflow years)', () =>
// Nov-dated Monday near this Sunday pair is clean any more, now that // Nov-dated Monday near this Sunday pair is clean any more, now that
// Nov 4/8/15/16/20 are all fixed real content). // Nov 4/8/15/16/20 are all fixed real content).
expect(getDayLabel(resolveDay('2024-11-11'))).toBe('St. Martin of Tours, Bishop and Confessor (Duplex Majus)'); expect(getDayLabel(resolveDay('2024-11-11'))).toBe('St. Martin of Tours, Bishop and Confessor (Duplex Majus)');
expect(getDayLabel(resolveDay('2024-11-17'))).toBe('The 6th Sunday after Epiphany'); expect(getDayLabel(resolveDay('2024-11-17'))).toBe('The 6th Sunday after Epiphany (Semiduplex)');
// 2024-11-18 is the Dedication of the Basilicas of Ss. Peter and Paul // 2024-11-18 is the Dedication of the Basilicas of Ss. Peter and Paul
// (pre-existing fixed content) -- again a real winning saint, not the // (pre-existing fixed content) -- again a real winning saint, not the
// ferial fallback. // ferial fallback.
@@ -92,7 +92,7 @@ describe('getDayLabel — resumed post-Epiphany Sunday (overflow years)', () =>
// stretch, is genuinely clean (no sanctoral entry, no commemoration) // stretch, is genuinely clean (no sanctoral entry, no commemoration)
// -- covers the ferial "Monday in the Nth week after Epiphany" format // -- covers the ferial "Monday in the Nth week after Epiphany" format
// itself, independent of the specific 5th/6th Sunday pair above. // itself, independent of the specific 5th/6th Sunday pair above.
expect(getDayLabel(resolveDay('2035-10-29'))).toBe('Monday in the 3rd week after Epiphany'); expect(getDayLabel(resolveDay('2035-10-29'))).toBe('Monday in the 3rd week after Epiphany (Feria)');
}); });
it('the fixed final Sunday of the year always shows the fixed 23rd-after-Trinity ordinal, not a raw elapsed-week count', () => { it('the fixed final Sunday of the year always shows the fixed 23rd-after-Trinity ordinal, not a raw elapsed-week count', () => {
@@ -102,7 +102,7 @@ describe('getDayLabel — resumed post-Epiphany Sunday (overflow years)', () =>
// Every year 1900-2100 lands on this same id for its own last Sunday // Every year 1900-2100 lands on this same id for its own last Sunday
// before Advent, so this ordinal is fixed, not overflow-year-specific // before Advent, so this ordinal is fixed, not overflow-year-specific
// -- confirmed against a non-overflow year (1943) too. // -- confirmed against a non-overflow year (1943) too.
expect(getDayLabel(resolveDay('2026-11-22'))).toBe('The 23rd Sunday after Trinity'); expect(getDayLabel(resolveDay('2026-11-22'))).toBe('The 23rd Sunday after Trinity (Semiduplex)');
// 1943-11-21 is also the Presentation of the BVM (Duplex Majus, added // 1943-11-21 is also the Presentation of the BVM (Duplex Majus, added
// 2026-08 — see presentation-of-the-bvm.yml), which meets this app's // 2026-08 — see presentation-of-the-bvm.yml), which meets this app's
// existing privileged-Sunday rank threshold (commemorations.ts's // existing privileged-Sunday rank threshold (commemorations.ts's
@@ -120,7 +120,9 @@ describe('getDayLabel — active octave', () => {
// Clare's own day -- Simplex, below the octave's semiduplex threshold, // Clare's own day -- Simplex, below the octave's semiduplex threshold,
// so she's commemorated rather than winning. Matches the live engine's // so she's commemorated rather than winning. Matches the live engine's
// own title for this date directly (see saints/st-clare.yml). // own title for this date directly (see saints/st-clare.yml).
expect(getDayLabel(resolveDay('2026-08-12'))).toBe('3rd Day within the Octave of St. Lawrence, Martyr — St. Clare, Virgin'); expect(getDayLabel(resolveDay('2026-08-12'))).toBe(
'3rd Day within the Octave of St. Lawrence, Martyr (Semiduplex) — St. Clare, Virgin',
);
}); });
it('shows the octave day alone when nothing is separately commemorated that day', () => { it('shows the octave day alone when nothing is separately commemorated that day', () => {
@@ -137,7 +139,7 @@ describe('getDayLabel — active octave', () => {
// "real content closed the gap" shape as August's Lawrence/Assumption // "real content closed the gap" shape as August's Lawrence/Assumption
// overlap. Day 3 of the same octave (2028-11-03, a Friday) is still // overlap. Day 3 of the same octave (2028-11-03, a Friday) is still
// genuinely open -- no sanctoral entry, no separate commemoration. // genuinely open -- no sanctoral entry, no separate commemoration.
expect(getDayLabel(resolveDay('2028-11-03'))).toBe('3rd Day within the Octave of All Saints'); expect(getDayLabel(resolveDay('2028-11-03'))).toBe('3rd Day within the Octave of All Saints (Semiduplex)');
}); });
it("a season's own named anchor day outranks an active octave, even though Trinity Sunday is technically also day 8 of Pentecost's own octave", () => { it("a season's own named anchor day outranks an active octave, even though Trinity Sunday is technically also day 8 of Pentecost's own octave", () => {
@@ -147,14 +149,14 @@ describe('getDayLabel — active octave', () => {
// muddy this specifically octave-vs-anchor-day check with a sanctoral // muddy this specifically octave-vs-anchor-day check with a sanctoral
// winner. 2030's Trinity Sunday (June 16) has no sanctoral entry at // winner. 2030's Trinity Sunday (June 16) has no sanctoral entry at
// all, isolating the octave interaction cleanly. // all, isolating the octave interaction cleanly.
expect(getDayLabel(resolveDay('2030-06-16'))).toBe('Trinity Sunday'); expect(getDayLabel(resolveDay('2030-06-16'))).toBe('Trinity Sunday (Duplex I Class)');
}); });
it("a temporal day with real standing of its own (not ordinary-feria) never shows an octave name at all, even with one active -- live-verified counterexample: the Christmas Octave's own stack (Dec 30) titles itself off the Sunday, never any of the four octaves layered on top of it", () => { it("a temporal day with real standing of its own (not ordinary-feria) never shows an octave name at all, even with one active -- live-verified counterexample: the Christmas Octave's own stack (Dec 30) titles itself off the Sunday, never any of the four octaves layered on top of it", () => {
const day = resolveDay('2033-12-30'); const day = resolveDay('2033-12-30');
expect(day.temporalCategory).toBe('privileged-feria-minor'); expect(day.temporalCategory).toBe('privileged-feria-minor');
expect(getDayLabel(day)).not.toContain('Octave'); expect(getDayLabel(day)).not.toContain('Octave');
expect(getDayLabel(day)).toBe('Friday in Christmastide'); expect(getDayLabel(day)).toBe('Friday in Christmastide (Feria)');
}); });
it("picks the higher-ranked of two genuinely overlapping octaves for the label -- St. Lawrence's own elevated closing day (Aug 17) over the Assumption's ordinary day 3, even though the Assumption's octave started later and the Assumption is the higher-ranked feast overall -- with St. Hyacinth (Duplex, tied with Lawrence's own elevated rank) commemorated rather than winning outright", () => { it("picks the higher-ranked of two genuinely overlapping octaves for the label -- St. Lawrence's own elevated closing day (Aug 17) over the Assumption's ordinary day 3, even though the Assumption's octave started later and the Assumption is the higher-ranked feast overall -- with St. Hyacinth (Duplex, tied with Lawrence's own elevated rank) commemorated rather than winning outright", () => {
@@ -171,9 +173,11 @@ describe('getDayLabel — active octave', () => {
// day 3 (semiduplex, genuinely active but outranked by Lawrence's // day 3 (semiduplex, genuinely active but outranked by Lawrence's
// elevated duplex) still gets named, not dropped, alongside it. // elevated duplex) still gets named, not dropped, alongside it.
expect(getDayLabel(resolveDay('2026-08-17'))).toBe( expect(getDayLabel(resolveDay('2026-08-17'))).toBe(
'8th Day within the Octave of St. Lawrence, Martyr — The Assumption of the Blessed Virgin Mary — St. Hyacinth, Confessor', '8th Day within the Octave of St. Lawrence, Martyr (Duplex) — The Assumption of the Blessed Virgin Mary — St. Hyacinth, Confessor',
);
expect(getDayLabel(resolveDay('2026-08-18'))).toBe(
'4th Day within the Octave of The Assumption of the Blessed Virgin Mary (Semiduplex)',
); );
expect(getDayLabel(resolveDay('2026-08-18'))).toBe('4th Day within the Octave of The Assumption of the Blessed Virgin Mary');
}); });
it('a real Sunday with standing of its own wins over both octaves entirely, showing the plain ordinal Sunday label -- not an octave name at all', () => { it('a real Sunday with standing of its own wins over both octaves entirely, showing the plain ordinal Sunday label -- not an octave name at all', () => {
@@ -185,6 +189,19 @@ describe('getDayLabel — active octave', () => {
}); });
}); });
describe('getDayLabel — named temporal feast rank', () => {
it('shows rank after a named temporal feast winner, sourced from its own TemporalFeastRecord', () => {
// Not Christmas Day itself: `resolveTemporalId` maps every day of
// christmastide (Dec 25 included) to `christmas-octave-sunday`, not
// `christmas-day` — a pre-existing, separate gap (`christmas-day`'s
// own record only ever surfaces via the octave layer, never as a
// direct winner id) out of scope here. Pentecost Sunday's own id
// *does* resolve as the direct winner (`temporal-id.ts`'s
// EASTER_OFFSET_IDS), so it's a real, clean case for this branch.
expect(getDayLabel(resolveDay('2026-05-24'))).toBe('Pentecost (Duplex I Class)');
});
});
describe('getDayLabel — feast name combination', () => { describe('getDayLabel — feast name combination', () => {
const base: LiturgicalDay = { const base: LiturgicalDay = {
date: '2026-06-15', date: '2026-06-15',
@@ -208,10 +225,10 @@ describe('getDayLabel — feast name combination', () => {
...base, ...base,
commemorations: [{ kind: 'sanctoral', id: 'x', name: 'St. Ereden', rank: 'duplex-2-classis' }], commemorations: [{ kind: 'sanctoral', id: 'x', name: 'St. Ereden', rank: 'duplex-2-classis' }],
}; };
expect(getDayLabel(day)).toBe('St. Ereden — Monday in the 2nd week after Trinity'); expect(getDayLabel(day)).toBe('St. Ereden — Monday in the 2nd week after Trinity (Feria)');
}); });
it('shows just the temporal label when nothing is commemorated at all', () => { it('shows just the temporal label when nothing is commemorated at all', () => {
expect(getDayLabel(base)).toBe('Monday in the 2nd week after Trinity'); expect(getDayLabel(base)).toBe('Monday in the 2nd week after Trinity (Feria)');
}); });
}); });
+1 -1
View File
@@ -17,7 +17,7 @@ describe('Marian Saturday (applyMarianSaturday)', () => {
const day = resolveDay('2026-07-04'); const day = resolveDay('2026-07-04');
expect(day.winner).toEqual({ kind: 'temporal', id: 'marian-saturday' }); expect(day.winner).toEqual({ kind: 'temporal', id: 'marian-saturday' });
expect(day.commemorations).toEqual([]); expect(day.commemorations).toEqual([]);
expect(getDayLabel(day)).toBe("Our Lady's Saturday"); expect(getDayLabel(day)).toBe("Our Lady's Saturday (Simplex)");
}); });
it('wins over a Simplex saint, who is commemorated instead of winning outright', () => { it('wins over a Simplex saint, who is commemorated instead of winning outright', () => {
+1 -1
View File
@@ -92,7 +92,7 @@ describe('resolveOrdo("compline", ...)', () => {
const eve = resolveOrdo('compline', '2025-11-29'); const eve = resolveOrdo('compline', '2025-11-29');
const last = eve.parts[eve.parts.length - 1]; const last = eve.parts[eve.parts.length - 1];
expect(last?.kind === 'preces' ? last.label : undefined).toBe('Alma Redemptoris Mater'); expect(last?.kind === 'preces' ? last.label : undefined).toBe('Alma Redemptoris Mater');
expect(eve.dayLabel).toBe('St. Andrew, Apostle — The 1st Sunday of Advent'); expect(eve.dayLabel).toBe('St. Andrew, Apostle — The 1st Sunday of Advent (Semiduplex)');
const dayBefore = resolveOrdo('compline', '2025-11-28'); const dayBefore = resolveOrdo('compline', '2025-11-28');
const lastBefore = dayBefore.parts[dayBefore.parts.length - 1]; const lastBefore = dayBefore.parts[dayBefore.parts.length - 1];