import test from 'node:test';
import assert from 'node:assert/strict';
import {readFile} from 'node:fs/promises';
const {JSDOM}=await import(process.env.QC26_JSDOM||'jsdom');
import {baseLabs} from '../dist/labs.js';
import {advancedLabs} from '../dist/labs-advanced.js';
const mounts={...baseLabs,...advancedLabs};
function root(name){
 const dom=new JSDOM('<!doctype html><div id="lab"></div>',{url:'https://example.test/'});globalThis.document=dom.window.document;globalThis.window=dom.window;
 const el=document.querySelector('#lab');mounts[name](el);
 const set=(name,v)=>{const e=el.querySelector('[name="'+name+'"]');assert.ok(e,'missing field '+name);if(e.type==='checkbox')e.checked=v;else e.value=String(v);e.dispatchEvent(new dom.window.Event('input',{bubbles:true}));};
 const click=(action)=>{const b=el.querySelector('[data-action="'+action+'"]');assert.ok(b,'missing action '+action);b.click();};
 const healthy=()=>{assert.ok(el.querySelector('.lab-error').hidden,el.querySelector('.lab-error').textContent);assert.ok(el.querySelector('.lab-result').innerHTML.length>30);assert.ok(!/NaN|undefined|Infinity/.test(el.querySelector('.lab-result').textContent),el.textContent.slice(-300));};
 return{el,set,click,healthy,dom};
}
for(const name of Object.keys(mounts))test('laboratory '+name+' initializes with computed output',()=>root(name).healthy());
test('interactive exact derivation displays the requested Y fractions',()=>{const t=root('basis');t.set('basis','Y');t.click('derive');t.healthy();assert.match(t.el.textContent,/25\/26/);assert.match(t.el.textContent,/1\/26/);assert.match(t.el.textContent,/√26/);assert.equal(t.el.querySelectorAll('.derivation li').length,10);t.set('basis','X');assert.match(t.el.textContent,/1\/2/);});
test('zero-vector input gives a recoverable error and removes stale probabilities',()=>{const t=root('basis');for(const n of ['ar','ai','br','bi'])t.set(n,0);assert.ok(!t.el.querySelector('.lab-error').hidden);assert.equal(t.el.querySelector('.lab-result').textContent,'');t.set('ar',1);t.healthy();});
test('random problems reveal one step, full derivation, and a new problem',()=>{const t=root('practice');const before=t.el.querySelector('.problem').textContent;t.click('hint');assert.match(t.el.textContent,/Conjugate/);t.click('step');assert.equal(t.el.querySelectorAll('.derivation li:not([hidden])').length,1);t.click('all');assert.equal(t.el.querySelectorAll('.derivation li:not([hidden])').length,5);t.click('new');assert.notEqual(t.el.querySelector('.problem').textContent,before);t.healthy();});
test('circuit builder adds gates, steps, samples, and handles measurement control',()=>{const t=root('circuit');t.click('prev');assert.match(t.el.textContent,/1 \/ 2/);t.click('end');t.set('n',3);t.click('ghz');t.healthy();assert.match(t.el.textContent,/\|111⟩/);t.click('clear');t.set('gate','CP');t.set('target',0);t.set('control',1);t.click('append');t.healthy();t.click('clear');t.set('gate','X');t.set('target',0);t.click('append');t.set('gate','M');t.click('append');t.set('target',2);t.set('gate','X');t.set('cond','0');t.click('append');t.healthy();assert.match(t.el.textContent,/\|101⟩/);t.set('target',3);t.click('append');assert.ok(!t.el.querySelector('.lab-error').hidden);t.set('target',1);t.healthy();});
test('protocol controls recompute their output',()=>{let t=root('teleport');t.set('branch','11');t.set('correctX',false);assert.ok(!/Output fidelity1\b/.test(t.el.textContent));t.set('correctX',true);t.healthy();t=root('dense');t.set('message','01');t.healthy();t=root('entanglement');t.click('product');t.healthy();assert.match(t.el.textContent,/Purity A1/);});
test('algorithm controls and actions respond',()=>{let t=root('oracles');t.set('secret',2);t.set('type','Deutsch');t.healthy();t=root('simon');t.set('shots',1);t.click('resample');t.healthy();t=root('grover');t.set('marked','1,3');t.set('iterations',1);t.healthy();t=root('qft');t.set('input','comb');t.set('inverse','yes');t.healthy();t=root('qpe');t.set('phi',.51);t.set('m',5);t.healthy();t=root('shor');t.click('sample');t.healthy();t.set('N',21);t.set('a',3);t.healthy();assert.match(t.el.textContent,/classical gcd/);});
test('coding and optimization actions compute real model changes',()=>{let t=root('surface');t.click('center');t.healthy();t.click('logicalX');t.healthy();t.click('clear');t.healthy();t=root('vqe');t.click('solve');t.healthy();assert.match(t.el.textContent,/−?1\.11803|-1\.11803/);t.click('step');t.healthy();t=root('bb84');t.set('eve',1);t.healthy();});
test('manifest matches every mounted laboratory and all exercise solutions',async()=>{const m=JSON.parse(await readFile(new URL('../dist/book-manifest.json',import.meta.url)));assert.deepEqual(m.labs.map(x=>x.key).sort(),Object.keys(mounts).sort());assert.equal(m.chapters.reduce((a,c)=>a+c.exercises,0),120);assert.equal(m.chapters.reduce((a,c)=>a+c.solutions,0),120);});
