const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const scenes = require('../src/design/scenes.json'); const source = require('../design/scenes.json'); const files = require('../design/asset-files.json'); const frames = require('../../qa/mobile-frame-inventory.json').filter(f=>f.width===402||f.width===362); test('Every mobile frame and explanatory panel is implemented, without Watch screens',()=>{ assert.equal(scenes.length,146); assert.equal(scenes.filter(s=>s.w===402).length,110); assert.deepEqual(new Set(scenes.map(s=>s.id)),new Set(frames.map(f=>f.id))); assert.ok(scenes.every(s=>s.w!==198)); }); test('All referenced illustrations and icons are bundled, nonempty original exports',()=>{ for(const scene of scenes)for(const layer of scene.layers)if(layer.asset){assert.ok(files[layer.asset],layer.asset);assert.ok(fs.statSync(path.resolve(__dirname,'../assets/figma',files[layer.asset])).size>0);} }); test('All navigation and overlay destinations exist',()=>{ const ids=new Set(scenes.map(s=>s.id)); for(const scene of scenes)for(const layer of scene.layers)if(layer.action?.target)assert.ok(ids.has(layer.action.target),`${scene.id} ${layer.action.label} -> ${layer.action.target}`); }); test('The navigation compiler does not change any Figma text or styled text segment',()=>{ const original=new Map(source.map(s=>[s.id,s])); for(const scene of scenes){const copy=original.get(scene.id);assert.deepEqual(scene.layers.filter(l=>l.text!==undefined).map(l=>[l.id,l.text,l.runs]),copy.layers.filter(l=>l.text!==undefined).map(l=>[l.id,l.text,l.runs]));} }); test('Onboarding reaches the seven correct source frames in order',()=>{ const order=['2:2','2:30','677:64','2:268','2:334','2:213','2:431']; for(let i=0;is.id===order[i]);assert.ok(scene.layers.some(l=>l.action?.target===order[i+1]),scene.id);} assert.ok(scenes.find(s=>s.id==='2:431').layers.some(l=>l.action?.type==='finish-onboarding')); }); test('Both mobile variants expose working primary navigation and breathing flows',()=>{ for(const [home,tools,breath,prepare,session]of [['431:2','565:958','565:1148','565:1624','565:1584'],['560:251','566:4392','566:4582','566:5207','566:5167']]){ for(const [from,to]of [[home,tools],[tools,breath],[breath,prepare],[prepare,session]])assert.ok(scenes.find(s=>s.id===from).layers.some(l=>l.action?.target===to),`${from} -> ${to}`); } }); test('Scene layer keys are unique, including multipart Figma illustrations',()=>{ for(const scene of scenes)assert.equal(new Set(scene.layers.map(l=>l.id)).size,scene.layers.length,scene.id); }); test('Short Figma text boxes retain their no-wrap layout instruction',()=>{ const layer=scenes.find(s=>s.id==='2:268').layers.find(l=>l.id==='2:275'); assert.equal(layer.nowrap,true); assert.equal(layer.text,'Personnalisons votre\nexpérience'); }); test('Every explanatory panel is reachable from a source information icon',()=>{ const targets=new Set(scenes.flatMap(s=>s.layers.filter(l=>l.action?.type==='overlay').map(l=>l.action.target))); assert.deepEqual(targets,new Set(scenes.filter(s=>s.w===362).map(s=>s.id))); }); test('Reference typography distinguishes text colors from text sizes',()=>{ const profile=scenes.find(s=>s.id==='565:3818'); assert.equal(profile.layers.find(l=>l.id==='565:3824').runs[0].size,28); assert.equal(profile.layers.find(l=>l.id==='565:3836').runs[0].size,10); }); test('Exported vector icons are not painted over rectangular placeholder fills',()=>{ for(const scene of scenes)for(const layer of scene.layers)if(layer.t==='VECTOR'&&scene.layers.some(c=>c.asset&&c.path.includes(layer.id)))assert.equal(layer.f?.length??0,0,layer.id); });