diff --git a/server/src/modules/tts/tts-text-extractor.service.test.ts b/server/src/modules/tts/tts-text-extractor.service.test.ts index 8b4b1d050..85729fc3a 100644 --- a/server/src/modules/tts/tts-text-extractor.service.test.ts +++ b/server/src/modules/tts/tts-text-extractor.service.test.ts @@ -42,6 +42,73 @@ describe('htmlToBlocks', () => { it('decodes entities and collapses whitespace within a block', () => { expect(htmlToBlocks('
Tom & Jerry say\n"hi".
')).toEqual(['Tom & Jerry say "hi".']); }); + + // Regression: block text used to be unescaped from a six-entry table, so anything + // outside it reached the TTS provider as raw markup. A Kokoro user heard + // "dell hash thirty-nine Arbatskaja" for text the reader displayed as "dell'Arbatskaja". + describe('character references', () => { + it('decodes a decimal numeric reference', () => { + expect(htmlToBlocks('Lui aveva fatto l'abitudine.
')).toEqual(["Lui aveva fatto l'abitudine."]); + }); + + it('decodes a hexadecimal numeric reference in either case', () => { + expect(htmlToBlocks('dell'Arbatskaja — sì.
')).toEqual(["dell'Arbatskaja — sì."]); + }); + + it('decodes named references beyond the five XML ones', () => { + expect(htmlToBlocks('Café à Paris… “c’est bon”
')).toEqual([ + 'Café à Paris… “c’est bon”', + ]); + }); + + it('still decodes the references the old table covered', () => { + expect(htmlToBlocks('Tom & Jerry "said" it's fine.
')).toEqual(['Tom & Jerry "said" it\'s fine.']); + }); + + it('collapses a decoded non-breaking space like any other whitespace', () => { + expect(htmlToBlocks('Chapter One begins.
')).toEqual(['Chapter One begins.']); + }); + + // A second decoding pass would turn this into an apostrophe and silently rewrite a + // book that is quoting the markup itself. + it('decodes once, leaving an escaped reference escaped', () => { + expect(htmlToBlocks('Write ' for an apostrophe.
')).toEqual(['Write ' for an apostrophe.']); + }); + + it('leaves a bare ampersand and an unknown reference alone', () => { + expect(htmlToBlocks('Fish & chips, AT&T, &zzz; too.
')).toEqual(['Fish & chips, AT&T, &zzz; too.']); + }); + + // Decoding before the split would let an escaped block tag open a new block and + // shift every later index away from Foliate's highlight blocks. + it('does not let a decoded reference create a block boundary', () => { + expect(htmlToBlocks('One <p> two.
Next.
')).toEqual(['Onetwo.', 'Next.']); + }); + + // Escaped markup is content the page displays, so it is content the narration reads. + // Discarding it again after decoding would mute a programming book's code samples. + it('keeps escaped markup that the page displays', () => { + expect(htmlToBlocks('
<div class="x">hi</div>')).toEqual(['
Se 5 < 6, allora va bene.
')).toEqual(['Se 5 < 6, allora va bene.']); + }); + + // Block indices are positional, so a paragraph that is nothing but a reference has + // to keep its slot rather than being dropped as empty. + it('keeps a block whose only content is a reference', () => { + expect(htmlToBlocks('Before.
—
After.
')).toEqual(['Before.', '—', 'After.']); + }); + + it('decodes references inside inline markup', () => { + expect(htmlToBlocks('Il tram’s l'ultimo.
')).toEqual(["Il tram’s l'ultimo."]); + }); + + it('decodes references in a table row block', () => { + expect(htmlToBlocks('| L'autore |
| L'anno |