import assert from 'node:assert/strict'; import {readFileSync} from 'node:fs'; import {question,nodes,relations,kinds,kindClass,byId,configure,relatedTo,makeBrief} from '../build/case-study.mjs'; let groups=0; function test(name,run){run();groups++;console.log('PASS',name);} test('Unique claims, valid taxonomy and primary-source provenance',()=>{ assert.equal(nodes.length,8);assert.equal(new Set(nodes.map(n=>n.id)).size,8); for(const node of nodes){ assert.ok(kinds.includes(node.kind));assert.ok(kindClass[node.kind]); assert.ok(node.claim && node.detail && node.confidence); if(node.source){ const url=new URL(node.sourceUrl); assert.equal(url.protocol,'https:'); assert.ok(['www.who.int','www.ipcc.ch'].includes(url.host)); assert.match(node.published,/^\d{4}-\d{2}-\d{2}$/); assert.ok(node.sourceType && node.limitation); } } assert.equal(byId.N1.kind,'Reported estimate'); assert.equal(byId.N2.kind,'Research assessment'); assert.match(byId.N1.detail,/2021/);assert.match(byId.N2.detail,/C.8.2/); }); test('Relations are typed, closed and preserve inference distinctions',()=>{ assert.equal(new Set(relations.map(([a,b])=>a+'-'+b)).size,relations.length); for(const [from,to,verb] of relations){ assert.ok(byId[from]&&byId[to]);assert.notEqual(from,to);assert.ok(verb); assert.ok(relatedTo(from).some(r=>r.to===to));assert.ok(relatedTo(to).some(r=>r.from===from)); } assert.equal(relations.find(([a,b])=>a==='N4'&&b==='N5')[2],'is an assumption behind'); assert.throws(()=>relatedTo('N999')); }); test('Every control combination is accepted without mutating the previous state',()=>{ const original={selectedId:'N8',filter:'All',view:'map'}; for(const node of nodes)for(const filter of kinds)for(const view of ['map','brief']){ const next=configure(original,{selectedId:node.id,filter,view}); assert.equal(next.selectedId,node.id);assert.equal(next.filter,filter);assert.equal(next.view,view); assert.deepEqual(original,{selectedId:'N8',filter:'All',view:'map'}); } }); test('Mixed invalid configurations fail atomically',()=>{ const original={selectedId:'N8',filter:'All',view:'map'}; for(const input of [{selectedId:'N1',view:'bad'},{selectedId:'N999'},{filter:null},{selectedId:1}, {view:true},{unknown:1},[],null,JSON.parse('{"__proto__":{}}')]){ assert.throws(()=>configure(original,input)); assert.deepEqual(original,{selectedId:'N8',filter:'All',view:'map'}); } }); test('Export retains all claims, exact citations, dates, limits and relationships',()=>{ const brief=makeBrief();assert.ok(brief.includes(question)); for(const node of nodes){ assert.ok(brief.includes('### '+node.id+' ยท '+node.kind)); assert.ok(brief.includes(node.claim));assert.ok(brief.includes(node.detail)); if(node.source)for(const field of ['sourceUrl','published','limitation'])assert.ok(brief.includes(node[field])); } for(const [a,b,v]of relations)assert.ok(brief.includes(a+' '+v+' '+b)); assert.match(brief,/No local data/);assert.match(brief,/Before\/after differences alone/); assert.equal(brief,makeBrief()); }); test('Desktop diagram cards stay inside the declared working surface',()=>{ const width=1120,height=860,cardWidth=220,cardHeight=210; for(const n of nodes){ assert.ok(n.x/100*width+cardWidth<=width); assert.ok(n.y/100*height+cardHeight<=height); } }); test('Static interface references complete DOM targets and local assets',()=>{ const html=readFileSync(new URL('../build/index.html',import.meta.url),'utf8'); const js=readFileSync(new URL('../build/app.js',import.meta.url),'utf8'); const ids=[...html.matchAll(/\bid="([^"]+)"/g)].map(m=>m[1]); assert.equal(ids.length,new Set(ids).size); for(const [,id]of js.matchAll(/querySelector\('#([^']+)'\)/g))assert.ok(ids.includes(id),'Missing '+id); assert.ok(html.includes('type="module"')); assert.ok(html.includes('brief-sources')); assert.ok(html.includes('rel="canonical" href="https://proof.skai-line.com/"'),'Verified canonical URL'); }); console.log('\n'+groups+' test groups passed; 128 valid state combinations checked.');