Files
vu/tests/hours/antiphon.test.ts
will 72b30fad80 Add flexa-mark (‡) boundary computation for antiphon/psalm text
Ports Divinum Officium's getantcross/depunct (horas.pl): finds where a
displayed antiphon's words stop matching a psalm's first verse, so the
mark can later show where the antiphon leaves off and the psalm's own
text resumes. Not yet wired into any hour builder.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UCvvgFLrhXNVFUThz6kanw
2026-08-23 06:30:23 -04:00

101 lines
5.3 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { applyFlexaMark, computeFlexaBoundary } from '../../src/hours/antiphon';
import type { ResolvedText, ResolvedVerse } from '../../src/hours/types';
describe('computeFlexaBoundary', () => {
it('finds the boundary after an exact word-for-word prefix match', () => {
const boundary = computeFlexaBoundary(
'The king rejoices in thy strength, O Lord; * and in thy salvation he shall rejoice exceedingly.',
'The king rejoices',
);
expect(boundary).not.toBeNull();
expect(boundary).not.toBe('full');
const text = 'The king rejoices in thy strength, O Lord; * and in thy salvation he shall rejoice exceedingly.';
expect(text.slice(boundary as number)).toBe('in thy strength, O Lord; * and in thy salvation he shall rejoice exceedingly.');
});
it('matches accented Latin words against their unaccented/depunctuated form', () => {
const boundary = computeFlexaBoundary('Dómine, in virtúte tua lætábitur rex: * et super salutáre tuum exsultábit veheménter.', 'Dómine');
expect(boundary).not.toBeNull();
expect(boundary).not.toBe('full');
});
it('returns null when the antiphon is longer than the verse', () => {
const boundary = computeFlexaBoundary('Short verse text here.', 'Short verse text here and then some more words');
expect(boundary).toBeNull();
});
it('returns null when the words diverge', () => {
const boundary = computeFlexaBoundary('Dixit Dominus Domino meo.', 'Laudate pueri Dominum');
expect(boundary).toBeNull();
});
it("returns 'full' when the antiphon consumes the entire verse", () => {
const boundary = computeFlexaBoundary('Dixit Dominus Domino meo.', 'Dixit Dominus Domino meo.');
expect(boundary).toBe('full');
});
it('skips over trailing punctuation-only tokens before the boundary', () => {
// A stray standalone punctuation token between the matched words and
// the next real word shouldn't become the insertion point itself.
const text = 'Alpha beta : gamma delta.';
const boundary = computeFlexaBoundary(text, 'Alpha beta');
expect(boundary).not.toBeNull();
expect(boundary).not.toBe('full');
expect(text.slice(boundary as number)).toBe('gamma delta.');
});
});
describe('applyFlexaMark', () => {
const verse = (n: number, la: string, en: string): ResolvedVerse => ({
n,
text: { la, en },
status: { la: 'verified', en: 'verified' },
});
const antiphon = (la: string, en: string): ResolvedText => ({
text: { la, en },
status: { la: 'verified', en: 'verified' },
});
it('inserts the flexa mark into the first verse at the matched boundary, per language', () => {
const verses = [verse(2, 'Dómine, in virtúte tua lætábitur rex: * et super salutáre tuum exsultábit veheménter.', 'The king rejoices in thy strength, O Lord; * and in thy salvation he shall rejoice exceedingly.')];
const result = applyFlexaMark(verses, antiphon('Ant. Dómine', 'Ant. The king rejoices'));
expect(result.verses[0]!.text.la).toBe('Dómine, ‡ in virtúte tua lætábitur rex: * et super salutáre tuum exsultábit veheménter.');
expect(result.verses[0]!.text.en).toBe('The king rejoices ‡ in thy strength, O Lord; * and in thy salvation he shall rejoice exceedingly.');
});
it('also appends the mark to the end of the displayed antiphon text, in both languages', () => {
const verses = [verse(2, 'Dómine, in virtúte tua lætábitur rex: * et super salutáre tuum exsultábit veheménter.', 'The king rejoices in thy strength, O Lord; * and in thy salvation he shall rejoice exceedingly.')];
const result = applyFlexaMark(verses, antiphon('Ant. Dómine', 'Ant. The king rejoices'));
expect(result.antiphon?.text.la).toBe('Ant. Dómine ‡');
expect(result.antiphon?.text.en).toBe('Ant. The king rejoices ‡');
});
it('is a no-op when no antiphon is given', () => {
const verses = [verse(2, 'Dómine, in virtúte tua lætábitur rex.', 'The king rejoices in thy strength.')];
const result = applyFlexaMark(verses, undefined);
expect(result.verses).toBe(verses);
expect(result.antiphon).toBeUndefined();
});
it('is a no-op when the antiphon does not share opening words with the verse', () => {
const verses = [verse(1, 'Beátus vir qui non ábiit in consílio impiórum.', 'Blessed is the man who hath not walked in the counsel of the ungodly.')];
const theAntiphon = antiphon('Ant. Fidélia', 'Ant. Faithful');
const result = applyFlexaMark(verses, theAntiphon);
expect(result.verses).toEqual(verses);
expect(result.antiphon).toBe(theAntiphon);
});
it('moves the mark to the start of the second verse when the antiphon matches the entire first verse', () => {
const verses = [
verse(2, 'Dixit Dóminus Dómino meo:', 'The Lord said to my Lord:'),
verse(3, 'Sede a dextris meis.', 'Sit thou at my right hand.'),
];
const result = applyFlexaMark(verses, antiphon('Ant. Dixit Dóminus Dómino meo:', 'Ant. The Lord said to my Lord:'));
expect(result.verses[0]!.text.la).toBe('Dixit Dóminus Dómino meo:');
expect(result.verses[1]!.text.la).toBe('‡ Sede a dextris meis.');
expect(result.verses[1]!.text.en).toBe('‡ Sit thou at my right hand.');
expect(result.antiphon?.text.la).toBe('Ant. Dixit Dóminus Dómino meo: ‡');
});
});