Files
haptika/mobile/scripts/design.test.cjs
T
2026-09-08 09:07:27 +02:00

59 lines
3.7 KiB
JavaScript

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;i<order.length-1;i++){const scene=scenes.find(s=>s.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);
});