import test from 'node:test'; import assert from 'node:assert/strict'; import { existsSync, readFileSync } from 'node:fs'; import { story } from '../src/story/story.js'; import { Game } from '../src/engine/game.js'; import { voiceLines, voiceKey, Voices } from '../src/engine/voices.js'; import { settle } from './helpers.mjs'; const index = JSON.parse(readFileSync(new URL('../assets/voices/index.json', import.meta.url), 'utf8')); const voci = JSON.parse(readFileSync(new URL('../tools/voci.json', import.meta.url), 'utf8')); test('le battute si trovano ovunque: introduzione, scene, oggetti, dialoghi e rami if', () => { const lines = voiceLines(story), has = (who, part) => lines.some(l => l.who === who && l.text.includes(part)); assert.ok(has('andrea', 'Fine maggio 2007'), 'introduzione'); assert.ok(has('andrea', 'Il palio è sparito!'), 'ingresso in una scena, dentro un if'); assert.ok(has('vecchietta', 'cadde dal cielo'), 'dialogo, con chi parla'); assert.ok(has('castello', 'vicino alle sue impronte'), 'ramo else annidato'); assert.ok(has('andrea', 'pagina sola'), 'azione di un oggetto'); assert.ok(!lines.some(l => /^[.…\s]+$/.test(l.text)), 'i silenzi («...») non hanno voce'); }); test('ogni battuta ha la sua voce (sennò: make voci) e ogni personaggio una voce in tools/voci.json', () => { for (const { who, text } of voiceLines(story)) { assert.ok(voci[who]?.speaker, `manca la voce di ${who} in tools/voci.json`); const entry = index[voiceKey(who, text)]; assert.ok(entry, `senza voce: ${who}: ${text}`); assert.ok(existsSync(new URL(`../assets/voices/${entry.file}`, import.meta.url)), `manca il file ${entry.file}`); assert.ok(entry.sec > 0.3 && entry.sec < 30, `durata strana: ${entry.sec} s per «${text}»`); } }); test('il castello e il leone hanno voci diverse da tutti gli altri', () => { for (const special of ['castello', 'leone']) for (const [who, v] of Object.entries(voci)) if (who !== special && !who.startsWith('_')) assert.notEqual(v.speaker, voci[special].speaker, `${special} e ${who}`); }); test('il fumetto resta a schermo almeno quanto la voce', () => { const g = new Game(story, { voiceTime: (who, text) => (text.startsWith('Il palio') ? 9 : undefined) }); g.go(story.start.scene, story.start.at); g.update(0.05); assert.ok(g.speech.text.startsWith('Il palio')); assert.ok(g.speech.left > 9, 'la voce lunga allunga il fumetto'); settle(g); const quiet = new Game(story); quiet.go(story.start.scene, story.start.at); quiet.update(0.05); assert.ok(quiet.speech.left < 9, 'senza voce vale la durata di lettura'); }); test('le voci si cercano per chi parla e testo; senza indice il gioco resta muto', () => { const v = new Voices({ [voiceKey('leone', 'Grr')]: { file: 'leone-x.mp3', sec: 1.5 } }); assert.equal(v.seconds('leone', 'Grr'), 1.5); assert.equal(v.seconds('andrea', 'Grr'), undefined); assert.equal(new Voices().play('andrea', 'Ciao'), false); assert.equal(v.playing, false); }); test('il leone e il castello parlano dalla loro posizione, non da quella di Andrea', () => { const g = new Game(story); g.go('salita_villa_adami', [340, 352]); assert.deepEqual(g.speakerPoint('leone'), story.scenes.salita_villa_adami.speakers.leone); g.go('trebbio', [110, 345]); assert.deepEqual(g.speakerPoint('castello'), story.scenes.trebbio.speakers.castello); });