Download app.test.js from elvis-hf/ensemble: direct link, hf CLI and curl.
- Browser
- Download file 7.15 kB
-
https://huggingface.co/spaces/elvis-hf/ensemble/resolve/main/app.test.js
- Command line
-
hf download hf://spaces/elvis-hf/ensemble/app.test.js
-
curl -L -o app.test.js https://huggingface.co/spaces/elvis-hf/ensemble/resolve/main/app.test.js
7.15 kB
| const {test}=require('node:test'); | |
| const assert=require('node:assert/strict'); | |
| const vm=require('node:vm'); | |
| const fs=require('node:fs'); | |
| const Ensemble=require('./ensemble.js'); | |
| const Datasets=require('./datasets.js'); | |
| test('async training ignores stale results, accepts parameters and preserves hover/removal',async()=>{ | |
| const elements=new Map(),calls=[]; | |
| const fills=[]; | |
| const ctx=new Proxy({}, {get:(target,key)=>key==='fillRect'?()=>fills.push(target.fillStyle):(key in target?target[key]:()=>{})}); | |
| const document={ | |
| getElementById(id){ | |
| if(!elements.has(id)) elements.set(id,{ | |
| width:6,height:6,value:id==='method'?'boosting':id==='params'?'{}':id==='seed'?'42':'DecisionTreeClassifier', | |
| getContext:()=>ctx,addEventListener(){},replaceChildren(...children){this.children=children;}, | |
| setAttribute(key,value){this[key]=value;},removeAttribute(){},style:{},offsetWidth:240,offsetHeight:60, | |
| }); | |
| return elements.get(id); | |
| },querySelectorAll:()=>[],createElement:()=>({}), | |
| }; | |
| let resolveReady; | |
| class PythonBackend{ | |
| ready=new Promise(resolve=>{resolveReady=resolve;}); | |
| add(request){return new Promise(resolve=>calls.push({request,resolve}));} | |
| } | |
| const sandbox=vm.createContext({document,Ensemble,Datasets,PythonBackend,window:{innerWidth:1000,innerHeight:800}}); | |
| vm.runInContext(fs.readFileSync(require.resolve('./app.js'),'utf8'),sandbox); | |
| const get=id=>document.getElementById(id); | |
| assert.equal(get('runtime-status')['data-state'],'loading'); | |
| assert.equal(get('runtime-status-text').textContent,'Loading Pythong runtime...'); | |
| get('dataset-type').value='two_moons';get('dataset-type').onchange(); | |
| assert.equal(get('add').disabled,true); | |
| resolveReady([{name:'DecisionTreeClassifier',weighted:true}]); | |
| await Promise.resolve(); | |
| assert.equal(get('runtime-status')['data-state'],'ready'); | |
| assert.equal(get('runtime-status-text').textContent,'Python runtime ready'); | |
| assert.equal(get('add').disabled,false); | |
| assert.equal(get('method-badge').textContent,'AdaBoost'); | |
| get('dataset-type').value='custom';get('dataset-type').onchange(); | |
| vm.runInContext('points=[{x:.2,y:.2,label:-1},{x:.8,y:.8,label:1}]; reset();',sandbox); | |
| const model={tree:{size:2,grid:[-1,-1,1,1]},predictions:[-1,1],alpha:1,perfect:true}; | |
| let pending=get('add').onclick(); | |
| assert.equal(get('add').disabled,true); | |
| assert.equal(calls[0].request.seed,42); | |
| get('params').value='{"max_depth":2}';get('params').oninput(); | |
| calls[0].resolve(model);await pending; | |
| assert.equal(get('count').textContent,0); | |
| for(let i=1;i<=2;i++){ | |
| pending=get('add').onclick();assert.equal(calls[i].request.params.max_depth,2); | |
| calls[i].resolve(model);await pending; | |
| assert.equal(get('count').textContent,i);assert.equal(get('add').disabled,false); | |
| } | |
| assert.match(get('models').children[0].title,/Training error: 0\.0%/); | |
| get('models').children[0].onmouseenter();assert.equal(vm.runInContext('hoveredModel',sandbox),0); | |
| get('models').children[0].onmouseleave();assert.equal(vm.runInContext('hoveredModel',sandbox),null); | |
| get('remove').onclick();assert.equal(get('count').textContent,1); | |
| get('boundary').getBoundingClientRect=()=>({left:0,top:0,width:602,height:602}); | |
| get('boundary').onpointermove({clientX:121,clientY:481}); | |
| assert.match(get('point-tooltip').textContent,/Sample weight: 0.5000/); | |
| get('params').value='[]';await get('add').onclick(); | |
| assert.match(get('status').textContent,/Invalid arguments/); | |
| // The hovered learner disagrees with the ensemble across the entire plot. | |
| vm.runInContext('models=[{classes:[-1,1,2],alpha:1,predictions:[2,2],tree:{size:1,grid:[2]}},{classes:[-1,1,2],alpha:2,predictions:[1,1],tree:{size:1,grid:[1]}}]; render();',sandbox); | |
| assert.equal(fills.at(-1),'#fae8d8'); | |
| assert.match(get('models').children[0].title,/Training error: 100\.0%/); | |
| assert.match(get('models').children[1].title,/Training error: 50\.0%/); | |
| fills.length=0;get('models').children[0].onmouseenter(); | |
| assert.ok(fills.length>0);assert.ok(fills.every(color=>color==='#e6f5f0')); | |
| fills.length=0;get('models').children[0].onmouseleave(); | |
| assert.ok(fills.every(color=>color==='#fae8d8')); | |
| fills.length=0;get('models').children[0].onfocus(); | |
| assert.ok(fills.every(color=>color==='#e6f5f0')); | |
| get('models').children[0].onblur(); | |
| // Each model's weighted error uses only the learners preceding it. | |
| vm.runInContext('models=[{alpha:Math.log(3),predictions:[1,1],tree:{size:1,grid:[1]}},{alpha:2,predictions:[1,1],tree:{size:1,grid:[1]}}]; render();',sandbox); | |
| assert.equal(get('models').children[0].title,'Training error: 50.0%\nWeighted error: 50.0%\nVote weight: 1.099'); | |
| assert.equal(get('models').children[1].title,'Training error: 50.0%\nWeighted error: 75.0%\nVote weight: 2.000'); | |
| get('method').value='bagging'; | |
| vm.runInContext('render();',sandbox); | |
| assert.equal(get('models').children[1].title,'Training error: 50.0%\nWeighted error: 50.0%\nVote weight: 2.000'); | |
| // Presets prevent both pointer and keyboard editing and restore custom data. | |
| const custom=vm.runInContext('JSON.stringify(points)',sandbox); | |
| get('dataset-type').value='xor';get('dataset-type').onchange(); | |
| assert.equal(get('count').textContent,0); | |
| assert.equal(get('points').textContent,4); | |
| assert.equal(get('undo').disabled,true); | |
| assert.equal(get('clear').disabled,true); | |
| const xor=vm.runInContext('JSON.stringify(points)',sandbox); | |
| assert.deepEqual(JSON.parse(xor),Datasets.create('xor')); | |
| get('boundary').onpointerdown({button:0}); | |
| get('boundary').onkeydown({key:'Enter'}); | |
| get('undo').onclick();get('clear').onclick(); | |
| assert.equal(vm.runInContext('JSON.stringify(points)',sandbox),xor); | |
| get('dataset-type').value='custom';get('dataset-type').onchange(); | |
| assert.equal(vm.runInContext('JSON.stringify(points)',sandbox),custom); | |
| assert.equal(get('undo').disabled,false); | |
| get('boundary').onkeydown({key:'Enter',preventDefault(){}}); | |
| assert.equal(get('points').textContent,3); | |
| // Switching datasets also invalidates an in-flight training result. | |
| get('params').value='{}'; | |
| pending=get('add').onclick(); | |
| get('dataset-type').value='circles';get('dataset-type').onchange(); | |
| calls.at(-1).resolve(model);await pending; | |
| assert.equal(get('count').textContent,0); | |
| assert.equal(get('points').textContent,120); | |
| const callCount=calls.length; | |
| for(const invalid of ['', '-1', '1.5', '4294967296']){ | |
| get('seed').value=invalid;get('seed').oninput(); | |
| await get('add').onclick(); | |
| assert.match(get('status').textContent,/Invalid random seed/); | |
| assert.equal(calls.length,callCount); | |
| } | |
| get('seed').value='0';get('seed').oninput(); | |
| pending=get('add').onclick(); | |
| assert.equal(calls.at(-1).request.seed,0); | |
| get('seed').value='17';get('seed').oninput(); | |
| calls.at(-1).resolve(model);await pending; | |
| assert.equal(get('count').textContent,0); | |
| pending=get('add').onclick(); | |
| assert.equal(calls.at(-1).request.seed,17); | |
| calls.at(-1).resolve(model);await pending; | |
| assert.equal(get('count').textContent,1); | |
| get('seed').value='42';get('seed').oninput(); | |
| assert.equal(get('count').textContent,0); | |
| }); | |