import test from 'node:test'; import assert from 'node:assert/strict'; import {readFile} from 'node:fs/promises'; import {runInNewContext} from 'node:vm'; async function coloringContext(){ const [extra,widget]=await Promise.all([ readFile('public/js/widgets/coloring-scenes-extra.js','utf8'),readFile('public/js/widgets/coloring.js','utf8') ]),source=extra+'\n'+widget; const context={}; runInNewContext(source+`; globalThis.__coloring={ pages:COLORING_PAGES, themes:COLORING_THEME_KEYS, pagesFor:coloringPagesFor, pencilWidth:coloringPencilWidth, safeColor:coloringSafeColor }`,context); return{source,api:context.__coloring}; } test('kleurplatenwidget laadt vóór het bord en is als creatieve leerlingwidget geregistreerd',async()=>{ const [html,board,pupil,cast,core,css]=await Promise.all([ readFile('public/index.html','utf8'),readFile('public/js/board.js','utf8'), readFile('public/js/pupil.js','utf8'),readFile('public/js/cast.js','utf8'), readFile('public/js/core.js','utf8'),readFile('public/css/coloring.css','utf8') ]); assert.ok(html.indexOf('js/widgets/coloring-scenes-extra.js')>0); assert.ok(html.indexOf('js/widgets/coloring-scenes-extra.js')0); assert.ok(html.indexOf('js/widgets/coloring.js'){ const {api}=await coloringContext(); const pages=JSON.parse(JSON.stringify(api.pages)); assert.equal(pages.length,28); assert.equal(new Set(pages.map(page=>page.id)).size,pages.length); const added=[ 'farmyard','rainbow-weather','construction-site','forest-picnic', 'dinosaur-valley','spring-nest','fire-station','bee-meadow', 'coral-reef','mountain-camp','robot-workshop','canal-street', 'solar-observatory','savanna-web','japanese-garden' ]; assert.equal(added.filter(id=>pages.some(page=>page.id===id)).length,15); for(let year=1;year<=8;year++){ const suitable=JSON.parse(JSON.stringify(api.pagesFor(year))); assert.equal(suitable.length,7,`groep ${year} heeft zeven kleurplaten`); assert.ok(suitable.every(page=>year>=page.years[0]&&year<=page.years[1])); assert.equal(new Set(suitable.map(page=>page.theme)).size,5,`groep ${year} heeft alle vijf thema's`); } for(const page of pages){ assert.ok(api.themes[page.theme],page.id+' heeft een bekend thema'); assert.ok(page.title.nl&&page.title.en,page.id+' heeft een tweetalige titel'); const parts=[...page.svg.matchAll(/data-part="([^"]+)"/g)].map(match=>match[1]); const details=(page.svg.match(/class="coloring-detail"/g)||[]).length; const openFillablePaths=[...page.svg.matchAll(/]* d="([^"]+)"/g)] .filter(match=>!/[zZ]\s*$/.test(match[2])).map(match=>match[1]); assert.ok(parts.length>=14,page.id+' heeft voldoende afzonderlijke kleurvakken'); assert.equal(new Set(parts).size,parts.length,page.id+' heeft unieke kleurvakken'); assert.deepEqual(openFillablePaths,[],page.id+' heeft alleen gesloten aanklikbare paden'); assert.ok(details>=4,page.id+' heeft voldoende herkenbaar binnenlijnwerk'); assert.match(page.svg,new RegExp(`data-scene="${page.id}"`)); assert.match(page.svg,/data-level="2"/); assert.match(page.svg,/data-level="3"/); assert.doesNotMatch(page.svg,/item.id===id); names.forEach(name=>assert.ok(page.svg.includes(`data-part="${name}"`),`${id} mist ${name}`)); } assert.equal(api.pagesFor(5,'science').length,2); assert.equal(api.pagesFor(3,'science').length,1); }); test('potlood reageert op echte pendruk en bouwt kleur transparant op',async()=>{ const {source,api}=await coloringContext(); assert.ok(api.pencilWidth(7,.9,'pen')>api.pencilWidth(7,.2,'pen')); assert.equal(api.pencilWidth(7,0,'mouse'),7); assert.equal(api.safeColor('#AABBCC'),'#aabbcc'); assert.equal(api.safeColor('red'),'#e0554d'); for(const feature of ['getCoalescedEvents','event.pressure','erase?1:.27','quadraticCurveTo','Math.random()'])assert.ok(source.includes(feature),feature); const css=await readFile('public/css/coloring.css','utf8'); assert.match(css,/mix-blend-mode:multiply/); assert.match(css,/\.coloring-detail\{stroke-width:4\.5;pointer-events:none\}/); }); test('verfemmer vult een SVG-vak in één keer en state bewaart potlood én vlakkleuren',async()=>{ const {source}=await coloringContext(); assert.match(source,/event\.target\.closest\("\[data-fillable\]\[data-part\]\"\)/); assert.match(source,/part\.setAttribute\("fill",color\)/); assert.match(source,/fills\[part\.dataset\.part\]=color/); assert.match(source,/canvas\.toDataURL\("image\/png"\)/); assert.match(source,/return\{theme,pageId,fills:\{\.\.\.fills\},ink,tool,color,size,completed,yearGroup,level/); assert.match(source,/opts\.onProgress/); }); test('creatief niveau is beschikbaar per klas, leerling en thuiskind',async()=>{ const [api,admin,progressions,parent,wiki,migration,live]=await Promise.all([ readFile('src/api.js','utf8'),readFile('public/js/admin.js','utf8'), readFile('public/js/learning-progressions.js','utf8'),readFile('public/js/parent.js','utf8'), readFile('public/js/wiki.js','utf8'), readFile('db/024_creative_pupil_levels.sql','utf8'),readFile('src/live-classroom.js','utf8') ]); assert.match(api,/LEVEL_SUBJECTS = \[[^\]]*'creatief'/); assert.match(admin,/KM_SUBJECTS = \[[^\]]*"creatief"/); assert.match(progressions,/LEARNING_SUBJECTS=\[[^\]]*"creatief"/); assert.match(parent,/\["taal","rekenen","creatief"\]\.forEach/); assert.match(wiki,/creatief:"parentLevelCreative"/); assert.match(migration,/CHECK \(subject IN \('taal','rekenen','world','creatief'\)\)/); assert.match(live,/LEVEL_SUBJECTS = new Set\(\[[^\]]*'creatief'/); });