"""Executable contracts for waveform geometry and the public API UI adapter.""" import subprocess from config import ROOT def test_wave_energy_geometry_and_temporal_continuity(): source = (ROOT / 'assets/wave.js').read_text() script = source + r''' const assert = require('node:assert/strict'); assert.equal(voiceEnergy(Array(200).fill(0)),0); assert.equal(voiceEnergy(Array(200).fill(.8)),0); // DC is not audible energy. const tone=a=>Array.from({length:2048},(_,i)=>a*Math.sin(i*.18)); assert.ok(voiceEnergy(tone(.1))>voiceEnergy(tone(.015))); assert.equal(wavePath(0),'M0 70 L1000 70'); assert.ok(voiceEnergy(tone(.015))>.45); // Quiet speech remains visibly responsive. assert.equal(voiceEnergy(tone(.002)),0); // No animation floor in near-silence. for(const phase of [0,1,3,12]) { const d=wavePath(1,phase); assert.ok(d.startsWith('M0 70 L20 70 C')); assert.ok(d.endsWith('980.00 70.00 L1000 70')); const c=[...d.matchAll(/C([\d.]+) ([\d.]+) ([\d.]+) ([\d.]+) ([\d.]+) ([\d.]+)/g)].map(x=>x.slice(1).map(Number)); assert.equal(c[0][1],70);assert.equal(c.at(-1)[3],70); assert.ok(c.flatMap(p=>[p[1],p[3],p[5]]).every(y=>y>=5 && y<=135)); } const motion=new WaveMotion(); motion.step(1,1/60);assert.ok(motion.level>0 && motion.level<.32); for(let i=0;i<60;i++)motion.step(1,1/60); assert.ok(motion.level>.899); const before=motion.level;motion.step(0,1/60);assert.ok(motion.level.89); for(let i=0;i<120;i++)motion.step(0,1/60); assert.equal(motion.level,0);assert.equal(wavePath(motion.level,motion.phase),'M0 70 L1000 70'); const m30=new WaveMotion(),m120=new WaveMotion(); for(let i=0;i<30;i++)m30.step(.6,1/30); for(let i=0;i<120;i++)m120.step(.6,1/120); assert.ok(Math.abs(m30.level-m120.level)<.0001); // A quarter second changes the crest positions even at steady amplitude. const live=new WaveMotion();live.level=.7;const old=wavePath(live.level,live.phase); for(let i=0;i<15;i++)live.step(.7,1/60); assert.ok(live.phase>1);assert.notEqual(wavePath(live.level,live.phase),old); ''' subprocess.run(['node','-e',script], check=True) def test_generate_snapshots_custom_settings_in_original_api_order(): source = (ROOT / 'assets/generate.js').read_text() script = r''' const assert = require('node:assert/strict'); const settings = {source:'Voice clip',consent:true,normalize:false,seed:123,randomize:false, temperature:.9,top_p:.8,top_k:27,repetition:1.7,rate:12.5}; const nodes = {'voice-options':{dataset:{settings:JSON.stringify(settings)}}, 'status-state':{dataset:{}},'generate-speech':{},'speech-player':{dispatchEvent:()=>{}}}; global.document = {getElementById:id=>nodes[id]}; global.CustomEvent = class {}; const args=['script','picker','Preset','clip',false,true,0,true,.75,.69,80,2.4,0]; const snapshot = (''' + source + r''')(...args); assert.deepEqual(snapshot,['script','picker','Voice clip','clip',true,false,123,false,.9,.8,27,1.7,12.5]); assert.equal(nodes['status-state'].dataset.busy,'true'); assert.equal(nodes['generate-speech'].disabled,true); ''' subprocess.run(['node','-e',script], check=True) def test_activity_completion_restores_submit_after_validation_error(): source = (ROOT / 'assets/wave.js').read_text() source += (ROOT / 'assets/player.js').read_text().split("const globalListeners=")[0] script = r''' const assert = require('node:assert/strict'); const generate = {disabled:true}; const hostNode = {dataset:{}}; const path = {setAttribute:()=>{}}; const svg = {setAttribute:()=>{},querySelector:()=>path}; global.element = {querySelector:()=>svg,isConnected:true}; global.document = {getElementById:id=>id==='generate-speech'?generate:hostNode}; global.window = {matchMedia:()=>({matches:true})}; global.cancelAnimationFrame=()=>{};global.requestAnimationFrame=()=>1;global.queueMicrotask=()=>{};global.tryAutoplay=()=>{}; ''' + source + r''' setActivity(true);assert.equal(hostNode.dataset.busy,'true'); setActivity(false);assert.equal(generate.disabled,false);assert.equal(hostNode.dataset.busy,'false'); ''' subprocess.run(['node','-e',script], check=True) def test_playback_wave_retains_signal_texture_and_settles_to_silence(): source = (ROOT / 'assets/wave.js').read_text() script = source + r''' const assert=require('node:assert/strict'); const tone=cycles=>Float32Array.from({length:4096},(_,i)=>.12*Math.sin(2*Math.PI*cycles*i/4096)); const slow=new PlaybackWave(),fast=new PlaybackWave(); for(let i=0;i<50;i++){slow.step(tone(8),1/60);fast.step(tone(27),1/60);} assert.ok(Math.abs(slow.level-fast.level)<.001); // Same energy, different texture. assert.notEqual(slow.path(),fast.path()); const extrema=shape=>Array.from(shape).slice(1,-1).filter((v,i)=>(v-shape[i])*(shape[i+2]-v)<0).length; assert.ok(extrema(fast.shape)>extrema(slow.shape)*2); const raw=tone(18);raw[100]=NaN;raw[150]=Infinity; fast.step(raw,1/60); const d=fast.path();assert.ok(!/NaN|Infinity/.test(d)); assert.ok(d.startsWith('M0 70 L20 70 C')&&d.endsWith('980.00 70.00 L1000 70')); const c=[...d.matchAll(/C([\d.]+) ([\d.]+) ([\d.]+) ([\d.]+) ([\d.]+) ([\d.]+)/g)].map(x=>x.slice(1).map(Number)); assert.ok(c.flatMap(p=>[p[1],p[3],p[5]]).every(y=>y>=6&&y<=134)); for(let i=0;i<120;i++)fast.step(null,1/60); assert.equal(fast.path(),'M0 70 L1000 70'); for(let i=0;i<20;i++)fast.step(new Float32Array(4096).fill(.2),1/60); assert.equal(fast.path(),'M0 70 L1000 70'); // DC does not create fake texture. fast.step(tone(13),1/60);fast.reset();assert.equal(fast.path(),'M0 70 L1000 70'); ''' subprocess.run(['node','-e',script],check=True)