kgames

KGames — free keyboard games for kids
git clone https://www.keyboard.games/code/kgames.git
Log | Files | Refs | README | LICENSE

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 });