62 lines
4.2 KiB
JavaScript
62 lines
4.2 KiB
JavaScript
/* Run only our generated local script in a minimal DOM, with no browser or network.
|
|
* This checks event handlers and serializes the produced SVG for static inspection.
|
|
* It is not a replacement for a native browser layout test.
|
|
*/
|
|
const fs=require('node:fs'),path=require('node:path'),vm=require('node:vm'),assert=require('node:assert');
|
|
const rootDir=path.resolve(__dirname,'..');
|
|
const source=fs.readFileSync(path.join(rootDir,'output/q1/question1_full.html'),'utf8');
|
|
const script=source.match(/<script>([\s\S]*?)<\/script>/)[1];
|
|
class Element {
|
|
constructor(tag) {this.tag=tag;this.attrs={};this.children=[];this.style={};this.events={};this.value='';this.max='3';this.textContent='';}
|
|
setAttribute(k,v){this.attrs[k]=String(v);}
|
|
appendChild(x){this.children.push(x);return x;}
|
|
replaceChildren(...x){this.children=x;this.textContent='';}
|
|
addEventListener(k,f){this.events[k]=f;}
|
|
add(o){this.children.push(o);}
|
|
getBoundingClientRect(){return {width:width};}
|
|
}
|
|
const esc=s=>String(s).replace(/&/g,'&').replace(/</g,'<').replace(/"/g,'"');
|
|
const palette={light:{'--foreground':'#24282e','--border':'#d9dfe5','--viz-series-1':'#146e9a','--viz-series-2':'#c6503a','--viz-series-3':'#198064'},dark:{'--foreground':'#eceff3','--border':'#424a55','--viz-series-1':'#6cc8ed','--viz-series-2':'#ff947d','--viz-series-3':'#64d6ac'}};
|
|
function serialize(el,theme){
|
|
if(el.tag==='text')Object.assign(el.attrs,{fill:palette[theme]['--foreground'],'font-size':12,'font-family':'Microsoft YaHei, sans-serif'});
|
|
const attrs=Object.entries(el.attrs).map(([k,v])=>`${k}="${esc(String(v).replace(/var\(([^)]+)\)/g,(_,n)=>palette[theme][n]||n))}"`).join(' ');
|
|
return `<${el.tag} ${attrs}>${esc(el.textContent)}${el.children.map(x=>serialize(x,theme)).join('')}</${el.tag}>`;
|
|
}
|
|
function text(el){return el.textContent+el.children.map(text).join(' ');}
|
|
const elements={};
|
|
for(const key of ['case','step','view','plot','step-label','stats','status','rows'])elements[key]=new Element(key==='plot'?'svg':'div');
|
|
elements.view.value='region';
|
|
const root=new Element('div');
|
|
root.querySelector=s=>{const m=s.match(/data-q1="([^"]+)"/);assert(m&&elements[m[1]],'missing selector '+s);return elements[m[1]];};
|
|
const document={getElementById:id=>{assert.equal(id,'q1-localization');return root;},createElement:tag=>new Element(tag),createElementNS:(_,tag)=>new Element(tag)};
|
|
let width=736;
|
|
vm.runInNewContext(script,{document,Option:function(label,value){this.label=label;this.value=String(value);},ResizeObserver:class{constructor(f){this.f=f;}observe(){}},Number,Math,String,Object,console},{timeout:5000});
|
|
const output=path.join(rootDir,'output/q1/figures');fs.mkdirSync(output,{recursive:true});
|
|
let checks=0;
|
|
for(let i=0;i<elements.case.children.length;i++) {
|
|
elements.case.value=String(i);elements.case.events.change();
|
|
for(let n=1;n<=+elements.step.max;n++) {
|
|
elements.step.value=String(n);elements.step.events.input();
|
|
assert.equal(elements['step-label'].textContent,`${n} / ${elements.step.max}`);
|
|
for(const v of ['global','region']) {
|
|
elements.view.value=v;elements.view.events.change();
|
|
const svg=serialize(elements.plot,'light');
|
|
assert(!/NaN|undefined|Infinity/.test(svg));
|
|
assert(svg.includes('data-axis="x"')&&svg.includes('data-axis="y"'));
|
|
checks++;
|
|
}
|
|
}
|
|
}
|
|
for(const w of [360,736])for(const theme of ['light','dark'])for(const index of [1,elements.case.children.length-1]) {
|
|
width=w;elements.case.value=String(index);elements.case.events.change();
|
|
elements.view.value='region';elements.view.events.change();
|
|
const height=elements.plot.attrs.viewBox.split(' ')[3];
|
|
const svg=serialize(elements.plot,theme).replace('<svg ',`<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${height}" `);
|
|
const filename=(index===1?'counterexample':'practice')+`-${w}-${theme}.svg`;
|
|
fs.writeFileSync(path.join(output,filename),svg,'utf8');
|
|
}
|
|
const report={interaction_states_checked:checks,missing_selectors:0,nonfinite_svg_values:0,browser_layout_verified:false,
|
|
note:'Browser local-file navigation blocked by browser URL policy. Event handlers checked in minimal DOM; SVG statically inspected separately.'};
|
|
fs.writeFileSync(path.join(rootDir,'output/q1/visualization_checks.json'),JSON.stringify(report,null,2));
|
|
console.log(report);
|