const {test}=require('node:test'); const assert=require('node:assert/strict'); const {readFileSync,readdirSync}=require('node:fs'); const {join}=require('node:path'); const vm=require('node:vm'); const {transport}=require('./download-actions'); test('transport symbols cover all actions and layouts with monochrome SVG',()=>{ for(const kind of ['play','pause','previous','next','back10','forward10','back15','forward']){ assert.match(transport.svg(kind),new RegExp(`href="#transport-${kind}"`)); assert.match(transport.svg(kind),/stroke="currentColor"/); assert.equal((transport.svg(kind).match(/transport.svg('unknown'),/Unknown transport/); }); test('playback UI swaps shared symbols and accessible names without changing handlers',()=>{ const buttons=Object.fromEntries(['playBtn','stgPlay','miniPlayBtn','servicePlayBtn'].map(id=>[id,{onclick:()=>id,setAttribute(k,v){this[k]=v;}}])); const handlers=Object.values(buttons).map(b=>b.onclick);let changes=0; const Player={master:{paused:true}},classes=[]; const app=readFileSync(join(__dirname,'app.js'),'utf8'); const body=app.slice(app.indexOf('function updatePlayBtn() {'),app.indexOf('// "⚠ Broken"')); const sandbox={Player,TransportIcons:transport,Remote:{changed(){changes++;}},$:id=>buttons[id],document:{querySelector:()=>({classList:{toggle:(...args)=>classes.push(args)}})}}; vm.runInNewContext(body,sandbox); for(const paused of [true,false,true]){ Player.master.paused=paused;sandbox.updatePlayBtn(); for(const b of Object.values(buttons)){assert.match(b.innerHTML,new RegExp(`#transport-${paused?'play':'pause'}`));assert.equal(b['aria-label'],paused?'Play':'Pause');} } assert.equal(changes,3);assert.deepEqual(Object.values(buttons).map(b=>b.onclick),handlers); assert.deepEqual(classes,[['is-paused',true],['is-paused',false],['is-paused',true]]); }); test('runtime sources never render legacy transport glyphs',()=>{ const failures=[]; for(const file of readdirSync(__dirname).filter(f=>/\.(js|html|css)$/.test(f)&&!f.endsWith('.test.js'))){ readFileSync(join(__dirname,file),'utf8').split('\n').forEach((line,i)=>{ // Legacy input decoder accepts old labels; its output uses shared symbols. if(file==='glass-controls.js'&&line.startsWith(' const glyphs = '))return; if(/[▶⏸⏮⏭⏪⏩⏯◀❚]/u.test(line))failures.push(`${file}:${i+1}`); }); } assert.deepEqual(failures,[]); }); test('state changes preserve named SVG nodes used by editor controls',()=>{ let icon={id:'playIcon'}; const button={querySelector(){return icon;},set innerHTML(value){this.markup=value;icon={id:''};},setAttribute(name,value){this[name]=value;}}; transport.set(button,'pause'); assert.equal(icon.id,'playIcon');assert.equal(button['aria-label'],'Pause');assert.match(button.markup,/#transport-pause-f7-swipe/); });