Recently Written · git

subplz-web

git clone https://github.com/equwal/subplz-web

Log | Files | Refs


tests/engine/language.test.mjs (3933 bytes)

1 /* The languages SubRead is checked against: English, Portuguese, Spanish,
2  * Russian and Japanese. The text of each comes from the golden fixtures, so
3  * the same public-domain excerpts serve the guess of the language and the
4  * alignment.
5  *
6  *   node --test tests/engine
7  */
8 import test from 'node:test';
9 import assert from 'node:assert/strict';
10 import { readdirSync, readFileSync } from 'node:fs';
11 import { fileURLToPath } from 'node:url';
12 import path from 'node:path';
13 
14 import { LANGUAGES } from '../../frontend/engine/languages.js';
15 import { detectLanguage } from '../../frontend/engine/book.js';
16 import { language, align, UNMATCHED } from '../../frontend/engine/align.js';
17 
18 export const CHECKED = ['en', 'pt', 'es', 'ru', 'ja'];
19 
20 const here = path.dirname(fileURLToPath(import.meta.url));
21 const fixtures = readdirSync(path.join(here, 'golden')).filter((f) => f.endsWith('.json')).sort()
22   .map((f) => JSON.parse(readFileSync(path.join(here, 'golden', f), 'utf8')));
23 
24 /** The first fixture of each language. */
25 export const byLanguage = new Map(CHECKED.map((code) => [code, fixtures.find((c) => c.language === code)]));
26 
27 test('each checked language has a golden fixture', () => {
28   for (const code of CHECKED) assert.ok(byLanguage.get(code), `no golden fixture for ${code}`);
29 });
30 
31 test('each checked language is offered to the visitor', () => {
32   const offered = new Set(LANGUAGES.map((l) => l.code));
33   for (const code of CHECKED) assert.ok(offered.has(code), `${code} is not in LANGUAGES`);
34 });
35 
36 test('the language of a book is guessed from its text', () => {
37   for (const [code, c] of byLanguage) assert.equal(detectLanguage(c.paragraphs), code, `guess for ${code}`);
38 });
39 
40 test('a page of a book is enough to guess the language', () => {
41   for (const [code, c] of byLanguage) {
42     const page = c.paragraphs.slice(0, 3);
43     assert.equal(detectLanguage(page), code, `guess for ${code} from ${page.join('').length} characters`);
44   }
45 });
46 
47 test('cleaning folds case in every script and keeps the letters', () => {
48   assert.equal(language('ru').clean('Ёлка И Её'), 'ёлка и её');
49   assert.equal(language('pt').clean('Não É Coração'), 'não é coração');
50   assert.equal(language('es').clean('¿Qué? ¡Ñandú!'), '¿qué? ¡ñandú!');
51   assert.equal(language('en').clean('The Yellow Wallpaper'), 'the yellow wallpaper');
52   // Only Japanese drops punctuation and spacing, and folds the particles the recogniser confuses.
53   assert.equal(language('ja').clean('吾輩は 猫である。'), '吾輩わ猫でわる。');
54 });
55 
56 test('a transcript with no words in common with the book gives unmatched cues, not a crash', () => {
57   for (const [code, c] of byLanguage) {
58     const other = byLanguage.get(code === 'ja' ? 'en' : 'ja');
59     const cues = align(other.transcript.slice(0, 20), c.paragraphs.slice(0, 5), language(code));
60     assert.equal(cues.length, 20, code);
61     for (const cue of cues) assert.ok(typeof cue.text === 'string' && cue.text.length > 0, code);
62   }
63 });
64 
65 test('the whole pipeline keeps the words of the book, in order, for each language', () => {
66   for (const [code, c] of byLanguage) {
67     const cues = align(c.transcript, c.paragraphs, language(code));
68     const matched = cues.filter((cue) => !cue.text.startsWith(UNMATCHED));
69     assert.ok(matched.length / cues.length >= 0.95, `${code}: ${matched.length}/${cues.length} matched`);
70     // Every matched cue is a slice of the book, and the slices come in reading order.
71     const squeeze = (s) => s.replace(/\s+/g, '');
72     const book = squeeze(c.paragraphs.join(''));
73     let at = 0;
74     for (const cue of matched) {
75       const found = book.indexOf(squeeze(cue.text), at);
76       assert.ok(found >= 0, `${code}: cue not in the book after position ${at}: ${cue.text}`);
77       at = found;
78     }
79     for (let i = 1; i < cues.length; i++) assert.ok(cues[i].start >= cues[i - 1].start, `${code}: cue ${i} starts before cue ${i - 1}`);
80   }
81 });