All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 38s
147 lines
10 KiB
JavaScript
147 lines
10 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {readFile} from 'node:fs/promises';
|
|
|
|
const ids=['birthday','numberline','sentence'];
|
|
const learningIds=['fractions','clock','placevalue','money','wordtypes','schedule','mood','poll'];
|
|
|
|
test('nieuwe widgets zijn geladen vóór het widgetregister', async()=>{
|
|
const html=await readFile('public/index.html','utf8');
|
|
const board=html.indexOf('js/board.js');
|
|
for(const id of ids){
|
|
const pos=html.indexOf(`js/widgets/${id}.js`);
|
|
assert.ok(pos>0 && pos<board, `${id} moet vóór board.js laden`);
|
|
}
|
|
});
|
|
|
|
test('nieuwe widgets zijn geregistreerd en bewaren state', async()=>{
|
|
const registry=await readFile('public/js/board.js','utf8');
|
|
for(const id of ids){
|
|
assert.match(registry,new RegExp(`id:"${id}"`));
|
|
const source=await readFile(`public/js/widgets/${id}.js`,'utf8');
|
|
assert.match(source,/getState/);
|
|
assert.doesNotMatch(source,/innerHTML\s*=.*(?:name|word\.value)/);
|
|
}
|
|
});
|
|
|
|
test('nieuwe widgetnamen en beschrijvingen zijn tweetalig', async()=>{
|
|
const core=await readFile('public/js/core.js','utf8');
|
|
for(const id of ids){
|
|
assert.equal((core.match(new RegExp(`wg_${id}:`,'g'))||[]).length,2);
|
|
assert.equal((core.match(new RegExp(`wg_${id}_d:`,'g'))||[]).length,2);
|
|
}
|
|
});
|
|
|
|
|
|
test('educatieve widgetmodule laadt en registreert alle acht widgets', async()=>{
|
|
const html=await readFile('public/index.html','utf8');
|
|
assert.ok(html.indexOf('js/widgets/learning.js')<html.indexOf('js/board.js'));
|
|
const [board,source]=await Promise.all([readFile('public/js/board.js','utf8'),readFile('public/js/widgets/learning.js','utf8')]);
|
|
for(const id of learningIds){
|
|
assert.match(board,new RegExp('id:"'+id+'"'));
|
|
const mount='mount'+id[0].toUpperCase()+id.slice(1);
|
|
assert.match(source,new RegExp('function '+mount+'\\('));
|
|
}
|
|
assert.equal((source.match(/getState:/g)||[]).length,learningIds.length);
|
|
});
|
|
|
|
test('nieuwe widgets zijn tweetalig beschreven', async()=>{
|
|
const core=await readFile('public/js/core.js','utf8');
|
|
for(const id of learningIds){
|
|
assert.equal((core.match(new RegExp('wg_'+id+':','g'))||[]).length,2,id);
|
|
assert.equal((core.match(new RegExp('wg_'+id+'_d:','g'))||[]).length,2,id);
|
|
}
|
|
});
|
|
|
|
test('widgetbibliotheek bevat zoeken favorieten sjablonen en borddata-acties', async()=>{
|
|
const [html,board]=await Promise.all([readFile('public/index.html','utf8'),readFile('public/js/board.js','utf8')]);
|
|
for(const id of ['gallerySearch','galleryFavOnly','galleryTemplates','boardUndo','boardRedo','boardExport','boardImport'])assert.match(html,new RegExp('id="'+id+'"'));
|
|
for(const feature of ['teachWidgetFavorites','structuredClone','application/json','widgetDuplicate','widgetLock','widgetPreview'])assert.ok(board.includes(feature),feature);
|
|
});
|
|
|
|
|
|
test('oefenklok biedt drie niveaus leerwijzen en meerdere spellen', async()=>{
|
|
const [source,core]=await Promise.all([readFile('public/js/widgets/learning.js','utf8'),readFile('public/js/core.js','utf8')]);
|
|
for(const value of ["'analogue'","'digital'","'words'","'both'","'set'","'read'","'choose'","amode:'blocks'","clock-type-answer","clock-task-digital","clock-task-words"])assert.ok(source.includes(value),value);
|
|
assert.match(source,/S\.level===1\?\[0,30\]:S\.level===2\?Array\.from\(\{length:12\}/);
|
|
for(const key of ['clockLevel','clockLearningStyle','clockGame','clockScore','clockLevel1Hint','clockLevel2Hint','clockLevel3Hint','clockTypeHint','clock24Hour'])assert.equal((core.match(new RegExp(key+':','g'))||[]).length,2,key);
|
|
});
|
|
|
|
|
|
test('analoge oefenklok is bedienbaar en houdt opdrachtacties buiten instellingen', async()=>{
|
|
const [source,css,core]=await Promise.all([readFile('public/js/widgets/learning.js','utf8'),readFile('public/css/teach.css','utf8'),readFile('public/js/core.js','utf8')]);
|
|
for(const feature of ['onpointerdown','onpointermove','setPointerCapture','releasePointerCapture','getBoundingClientRect','clock-time-blocks','dotGrid'])assert.ok(source.includes(feature),feature);
|
|
assert.match(css,/clock\.interactive[^}]*touch-action:none/);
|
|
assert.match(source,/learningShell\(root,\[field\(T\('clockLearningStyle'\),style\),field\(T\('clockGame'\),game\),linkSetting,hour24Setting\],stage\)/);
|
|
const clockSource=source.slice(source.indexOf('function mountClock'),source.indexOf('function mountPlacevalue'));
|
|
assert.doesNotMatch(clockSource,/learningShell\(root,\[[^\]]*(?:numberlineCheck|numberlineNew|clockHour|clockMinute)/);
|
|
for(const feature of ['answerDigits','clock-answer-slots','clock-block-tray','dragClone','droptarget',"'0123456789'.split('')",'clock-keypad',"S.amode==='blocks'?'🧱':'⌨️'"])assert.ok(clockSource.includes(feature),feature);
|
|
assert.match(clockSource,/String\(hour\)\.padStart\(2,'0'\)\+String\(minute\)\.padStart\(2,'0'\)/);
|
|
assert.equal((core.match(/clockIncomplete:/g)||[]).length,2);
|
|
assert.ok(clockSource.includes("'clock-tick hour'"), 'uurposities krijgen een eigen korte markering');
|
|
assert.match(css,/clock-tick\.hour[^}]*width:4px[^}]*height:5%[^}]*background:var\(--blue\)/);
|
|
assert.match(css,/translateY\(calc\(var\(--clock-size\)\*-.34\)\)/);
|
|
assert.doesNotMatch(css,/clock-score\{margin-left:auto\{/);
|
|
assert.ok(clockSource.includes("linkHands:true"));
|
|
assert.ok(clockSource.includes("linkHands.type='checkbox'"));
|
|
assert.ok(clockSource.includes("linkSetting.hidden=game.value!=='set'"));
|
|
assert.ok(clockSource.includes('for(let hour=1;hour<=12;hour++)'), 'gekoppelde urenwijzer rekent de volledige tijd uit');
|
|
assert.ok(clockSource.includes("game.onchange=()=>{syncSettings();newTask()}"));
|
|
assert.doesNotMatch(clockSource,/clockStartTask|clockWelcomeHint/);
|
|
assert.equal((core.match(/clockLinkHands:/g)||[]).length,2);
|
|
assert.ok(clockSource.includes('Math.floor(Math.random()*24)'), 'niveau 3 gebruikt de volledige dag');
|
|
assert.ok(clockSource.includes('S.level===3&&S.use24'), '24-uursklok is een niveau-3-optie');
|
|
assert.ok(clockSource.includes("hour24Setting.hidden=S.level!==3"));
|
|
assert.ok(clockSource.includes("use24.onchange=()=>{S.use24=use24.checked;newTask()}"));
|
|
assert.ok(clockSource.includes("hourHand.onpointerdown=e=>start(e,'hour')"));
|
|
assert.ok(clockSource.includes("minuteHand.onpointerdown=e=>start(e,'minute')"));
|
|
assert.ok(!clockSource.includes('face.onpointerdown='), 'de hele klok is geen sleepzone');
|
|
assert.doesNotMatch(css,/\.clock-hand::(?:before|after)/, 'geen extra sleepzones rond de wijzers');
|
|
for(const key of ['clockMorning','clockAfternoon','clockEvening','clockNight'])assert.equal((core.match(new RegExp(key+':','g'))||[]).length,2,key);
|
|
assert.match(css,/clock-top\{[^}]*position:sticky[^}]*flex-wrap:wrap/);
|
|
assert.match(css,/clock-app\{[^}]*justify-content:flex-start/);
|
|
});
|
|
|
|
test('geldrekenen biedt drie niveaus, leerwijzen, spellen en directe bediening', async()=>{
|
|
const [source,css,core,board]=await Promise.all([readFile('public/js/widgets/learning.js','utf8'),readFile('public/css/teach.css','utf8'),readFile('public/js/core.js','utf8'),readFile('public/js/board.js','utf8')]);
|
|
const moneySource=source.slice(source.indexOf('function mountMoney'),source.indexOf('function mountWordtypes'));
|
|
for(const value of ["['make','moneyGameMake']","['count','moneyGameCount']","['change','moneyGameChange']","['amount','moneyStyleAmount']","['words','moneyStyleWords']","['both','moneyStyleBoth']","S.amode==='blocks'?'🧱':'⌨️'"])assert.ok(moneySource.includes(value),value);
|
|
assert.ok(moneySource.includes("S.level===1?(1+rnd(9))*100:S.level===2?(10+rnd(190))*5:1+rnd(4999)"));
|
|
assert.ok(moneySource.includes("S.level===1?[200,100]:S.level===2?[500,200,100,50,20,10,5]"));
|
|
for(const feature of ['money-wallet','money-cash-tray','money-answer-slots','money-digit-tray','money-keypad','dragClone','droptarget','getBoundingClientRect','amountWords','paymentFor','money-piece-image','money-product-icon'])assert.ok(moneySource.includes(feature),feature);
|
|
assert.ok(moneySource.includes("learningShell(root,[styleSetting,gameSetting],stage)"),'alleen voorkeuren staan onder instellingen');
|
|
assert.ok(moneySource.includes("style.onchange=draw"));
|
|
assert.ok(moneySource.includes("game.onchange=()=>{S.game=game.value;syncSettings();newTask()}"));
|
|
assert.doesNotMatch(moneySource,/learningShell\(root,\[[^\]]*(?:numberlineCheck|numberlineNew|moneyClear|moneyNextTask)/);
|
|
for(const key of ['moneyLearningStyle','moneyStyleAmount','moneyStyleWords','moneyStyleBoth','moneyGame','moneyGameMake','moneyGameCount','moneyGameChange','moneyLevel','moneyLevel1Hint','moneyLevel2Hint','moneyLevel3Hint','moneyScore','moneyMakeTitle','moneyCountTitle','moneyChangeTitle','moneyWallet','moneyTray','moneyBuildHint','moneyTypeHint','moneyCorrect','moneyNextTask','moneyRemove'])assert.equal((core.match(new RegExp(key+':','g'))||[]).length,2,key);
|
|
assert.match(css,/money-app\{[^}]*align-self:flex-start[^}]*justify-content:flex-start/);
|
|
assert.match(css,/money-wallet\.droptarget/);
|
|
assert.match(css,/money-wallet\.money-remove-target/);
|
|
assert.ok(moneySource.includes("pile(S.selected,true,wallet)"));
|
|
assert.ok(moneySource.includes("const moved=hasMoved(ev),outside=!over(ev)"));
|
|
assert.ok(moneySource.includes("moved&&outside"));
|
|
assert.match(css,/coin\[data-value="1"\]\{width:36px;height:36px/);
|
|
assert.match(css,/coin\[data-value="200"\]\{width:57px;height:57px/);
|
|
assert.match(css,/note\[data-value="500"\]\{width:82px;height:42px/);
|
|
assert.match(css,/note\[data-value="5000"\]\{width:93px;height:51px/);
|
|
assert.match(css,/money-piece\.coin\[data-value="100"\]/);
|
|
assert.match(css,/money-piece-image\{[^}]*object-fit:contain/);
|
|
assert.match(css,/@media\(max-width:520px\)[\s\S]*?money-shop-task\{grid-template-columns:minmax\(0,.8fr\) minmax\(0,1.2fr\)/);
|
|
assert.ok(moneySource.includes("image.src="));
|
|
assert.ok(moneySource.includes("/img/money/"));
|
|
assert.ok(moneySource.includes("image.draggable=false"));
|
|
assert.doesNotMatch(moneySource,/money-(?:arch|hologram|serial|stars|map|unit)/);
|
|
for(const file of ['coin-1.png','coin-2.png','coin-5.png','coin-10.png','coin-20.png','coin-50.png','coin-100.png','coin-200.png']){
|
|
const asset=await readFile('public/img/money/'+file);
|
|
assert.ok(asset.length>5000,file);
|
|
}
|
|
assert.ok(moneySource.includes("extension=value>=500?'svg':'png'"));
|
|
for(const file of ['note-500.svg','note-1000.svg','note-2000.svg','note-5000.svg']){
|
|
const asset=await readFile('public/img/money/'+file,'utf8');
|
|
assert.match(asset,/<svg[^>]+viewBox=/,file);
|
|
assert.match(asset,/>OEFENGELD<\/text>/,file);
|
|
assert.match(asset,/>GEEN BETAALMIDDEL<\/text>/,file);
|
|
assert.doesNotMatch(asset,/SPECIMEN/i,file);
|
|
}
|
|
assert.match(board,/id:"money"[^\n]*w:580, h:560, minW:380, minH:360/);
|
|
});
|