Files
vu/tests/ui/shell.test.ts
T
will de83786793 Build the Vespers ordo: mechanism, weekday content, seasonal content
Wires resolveOrdo('vespers', ...) end to end: opening versicle,
weekday psalmody (4 groups, handling the joined Ps 115+116 and the
split Ps 138/143 via a PsalmRef-like shape), the chapter/responsory/
hymn/versicle office bundle, Magnificat, day-collects — reusing
resolveEveningDay so First/Second Vespers precedence and the cross-day
commemoration (both added in the previous commit) apply automatically
— and a closing that reuses Lauds' own conclusio, since no distinct
"Vespera" version of that exact text turned up in the source.

getMagnificatAntiphon mirrors getBenedictusAntiphon's saint/temporal-id
lookup order, but also falls back to real content Benedictus has no
equivalent of: the classic ferial set that quotes the Magnificat's own
text in sequence across the week, with the correct Monastic-rubric
variant picked for Tue/Thu/Fri/Sat (two of those needed a fresh
translation where the Monastic Latin diverges from the given English
in content, not just wording).

Also includes Vespers' own seasonal content (Advent/Lent/Passiontide/
Paschaltide chapter+responsory+hymn+versicle), transcribed from the
same source blocks as the weekday content, with the Monastic-specific
hymn variants hand-applied from the source's substitution notation
against each base Roman text (one transcription slip caught and fixed
mid-pass: the Passiontide hymn's closing lines had been copied from
the Roman text instead of the Monastic one actually being
transcribed). Passiontide's own responsory/hymn correctly omit the
Gloria Patri, matching real rubric and the source data as given.

All weekday/seasonal content marked `draft`, not `verified` — read
directly from the reference source, not cross-checked against a live
rendering per day the way the psalmody was.

Six other hours' resolvePart switches needed the three new HourPart
kinds ('vespers-psalmody', 'vespers-office', 'magnificat') added to
their exhaustive not-used lists so they still typecheck; three
pre-existing tests assumed Vespers was still "coming soon" and are
updated to exercise Matins (still the genuine stub) instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 20:14:19 -04:00

89 lines
3.3 KiB
TypeScript

import { beforeEach, describe, expect, it } from 'vitest';
import { mountShell } from '../../src/ui/shell';
import { setDate, setSelectedHour, setLanguages } from '../../src/app-state';
describe('shell', () => {
// app-state is a module-level singleton, so each test must reset every
// field it might have mutated — not just the one it cares about — or
// state leaks across tests in this file.
beforeEach(() => {
document.body.innerHTML = '<div id="app"></div>';
setDate('2026-08-09');
setSelectedHour('prime');
setLanguages(['en']);
window.history.replaceState(null, '', '/vu/2026-08-09/prime');
});
it('renders the day nav, all 8 hours, and Prime content by default', () => {
const root = document.getElementById('app')!;
mountShell(root);
expect(root.querySelector('.day-nav-date')?.textContent).toContain('August 9, 2026');
const hourNames = Array.from(root.querySelectorAll('.hour-name')).map((el) => el.textContent);
expect(hourNames).toEqual([
'Prime',
'Compline',
'Terce',
'Sext',
'None',
'Lauds',
'Vespers',
'Matins',
]);
expect(root.querySelector('.hour-item.is-selected .hour-name')?.textContent).toBe('Prime');
expect(root.querySelector('.hour-view h2')?.textContent).toBe('Prime');
expect(root.querySelectorAll('.ordo-part-psalm').length).toBeGreaterThan(0);
});
it('switches to a not-yet-built hour and shows the pending message', () => {
const root = document.getElementById('app')!;
mountShell(root);
const matinsBtn = Array.from(root.querySelectorAll<HTMLButtonElement>('[data-hour]')).find(
(btn) => btn.dataset.hour === 'matins',
);
matinsBtn?.click();
expect(root.querySelector('.hour-view h2')?.textContent).toBe('Matins');
expect(root.querySelector('.hour-view-pending')).not.toBeNull();
});
it('switches to Compline and shows its real content, not the pending message', () => {
const root = document.getElementById('app')!;
mountShell(root);
const complineBtn = Array.from(root.querySelectorAll<HTMLButtonElement>('[data-hour]')).find(
(btn) => btn.dataset.hour === 'compline',
);
complineBtn?.click();
expect(root.querySelector('.hour-view h2')?.textContent).toBe('Compline');
expect(root.querySelector('.hour-view-pending')).toBeNull();
expect(root.querySelectorAll('.ordo-part-psalm').length).toBe(3);
});
it('moving to the next day updates the displayed date', () => {
const root = document.getElementById('app')!;
mountShell(root);
root.querySelector<HTMLButtonElement>('[data-action="next"]')?.click();
expect(root.querySelector('.day-nav-date')?.textContent).toContain('August 10, 2026');
});
it('switching to two-language mode renders two columns per verse', () => {
const root = document.getElementById('app')!;
mountShell(root);
const bilingualBtn = Array.from(root.querySelectorAll<HTMLButtonElement>('[data-langs]')).find(
(btn) => btn.dataset.langs === 'en,la',
);
bilingualBtn?.click();
const firstVerseColumns = root.querySelector('.psalm-verse .lang-columns');
expect(firstVerseColumns?.classList.contains('lang-columns-2')).toBe(true);
expect(firstVerseColumns?.querySelectorAll('.lang-column').length).toBe(2);
});
});