story-stamp-assets.test.js (2930B)
1 // The committed Story Stamp assets must match the content in lib.js: every 2 // phoneme has a recorded clip of the right kind of length, every picture 3 // exists, every clip is credited. 4 const { test } = require('node:test'); 5 const assert = require('node:assert'); 6 const fs = require('fs'); 7 const path = require('path'); 8 const vm = require('vm'); 9 const SS = require('../../games/story-stamp/lib.js'); 10 11 const GAME = path.join(__dirname, '..', '..', 'games', 'story-stamp'); 12 const sandbox = { window: {} }; 13 vm.runInNewContext(fs.readFileSync(path.join(GAME, 'assets', 'manifest.js'), 'utf8'), sandbox); 14 const M = sandbox.window.SS_ASSETS; 15 16 test('manifest lists a recorded clip for every phoneme, and the files exist', () => { 17 for (const p of Object.keys(SS.PHONEMES)) { 18 assert.ok(M.phonemes[p], `phoneme ${p} has no recording`); 19 const f = path.join(GAME, 'assets', 'audio', M.phonemes[p].file); 20 assert.ok(fs.existsSync(f), f); 21 const dur = M.phonemes[p].dur; 22 const ph = SS.PHONEMES[p]; 23 if (ph.stretch) assert.ok(dur >= 0.8 && dur <= 1.2, `${p} is a held sound and should last about a second: ${dur}s`); 24 else if (ph.type === 'stop') assert.ok(dur >= 0.06 && dur <= 0.2, `${p} is a stop and should be short but audible: ${dur}s`); 25 else assert.ok(dur >= 0.12 && dur <= 0.8, `${p}: ${dur}s`); 26 } 27 }); 28 29 test('most words have recordings and every listed word file exists', () => { 30 const have = SS.WORDS.filter((w) => M.words[w.w]).length; 31 assert.ok(have / SS.WORDS.length > 0.8, `${have}/${SS.WORDS.length} words recorded`); 32 for (const w of Object.keys(M.words)) { 33 assert.ok(SS.WORD_BY_NAME[w], `manifest word ${w} not in lib`); 34 assert.ok(fs.existsSync(path.join(GAME, 'assets', 'audio', M.words[w].file))); 35 } 36 }); 37 38 test('every word, topic, patron, avatar and scene picture is present as a local WebP', () => { 39 const need = new Set(SS.WORDS.map((w) => w.art).concat(SS.TOPICS.map((t) => t.art), [SS.ANY_TOPIC.art], SS.PATRONS.map((c) => c.art), SS.AVATARS, SS.UI_ART)); 40 for (const name of need) { 41 assert.ok(M.images[name], `no image for ${name}`); 42 assert.ok(fs.existsSync(path.join(GAME, 'assets', M.images[name])), name); 43 } 44 // Every art name the game's scene code uses is registered in lib.js. 45 const game = fs.readFileSync(path.join(GAME, 'game.js'), 'utf8'); 46 for (const m of game.matchAll(/img\('([^']+)'/g)) assert.ok(need.has(m[1]), `game.js uses unregistered art: ${m[1]}`); 47 }); 48 49 test('CREDITS.md attributes every audio source used', () => { 50 const credits = fs.readFileSync(path.join(GAME, 'CREDITS.md'), 'utf8'); 51 for (const p of Object.keys(SS.PHONEMES)) assert.ok(credits.includes(`phoneme /${p}/`), `credit for /${p}/`); 52 for (const w of Object.keys(M.words)) assert.ok(credits.includes(`word "${w}"`), `credit for ${w}`); 53 assert.ok(credits.includes('Fluent Emoji')); 54 assert.ok(credits.includes('MIT')); 55 assert.ok(credits.includes('CC BY-SA 3.0')); 56 });