teach/test/language-library.test.js
Ramon dc3c591c76
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 1m5s
feat: stem taalwidgets af op beginnende geletterdheid
2026-07-21 22:27:44 +02:00

153 lines
8.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import test from 'node:test';
import assert from 'node:assert/strict';
import {readFile} from 'node:fs/promises';
import {runInNewContext} from 'node:vm';
async function languageData(extra=''){
const source=await readFile('public/js/data.js','utf8');
const context={LANG:'nl',T:key=>key};
runInNewContext(source+`; ${extra}
globalThis.__language={
concepts:LANGUAGE_CONCEPTS,
progression:LANGUAGE_NL_PROGRESSION_WORDS,
themesNl:languageThemeList("nl"),
themesEn:languageThemeList("en"),
words:WORDS,
catalogNl:languagePictureCatalog("nl")
};`,context);
return JSON.parse(JSON.stringify(context.__language));
}
test('taalwidgets delen ruim duizend extra Nederlandse woorden over leerjaar en thema',async()=>{
const data=await languageData();
assert.ok(data.concepts.length>=300,'verwacht minimaal 300 vaste woordplaatjes');
assert.ok(data.progression.length>=1000,'verwacht minimaal 1000 aanvullende Nederlandse woorden');
assert.equal(data.themesNl.length,16);
assert.equal(data.themesEn.length,12);
assert.deepEqual(data.themesNl.map(theme=>theme.id),[
'animals','food','nature','travel','home','body','actions','feelings',
'people','play','places','time','arts','science','learning','society'
]);
const all=data.concepts.concat(data.progression);
assert.equal(new Set(all.map(item=>item.id)).size,all.length);
assert.equal(new Set(all.map(item=>item.nl)).size,all.length);
for(const concept of data.concepts){
assert.ok(concept.nl&&concept.en&&concept.pic,concept.id);
assert.ok(data.themesNl.some(theme=>theme.id===concept.theme),concept.theme);
}
for(const word of data.progression){
assert.ok(word.nl&&word.pic,word.id);
assert.ok(word.yearGroup>=1&&word.yearGroup<=8,word.nl);
assert.ok(data.themesNl.some(theme=>theme.id===word.theme),word.theme);
}
for(let year=1;year<=8;year++)
assert.ok(data.progression.filter(word=>word.yearGroup===year).length>=60,'te weinig nieuwe woorden voor groep '+year);
for(const theme of data.themesNl)assert.ok(theme.words.length>=60,theme.id);
for(const theme of data.themesEn)assert.ok(theme.words.length>=18,theme.id);
assert.equal(data.themesNl.reduce((sum,theme)=>sum+theme.words.length,0),all.length);
assert.equal(data.themesEn.reduce((sum,theme)=>sum+theme.words.length,0),data.concepts.length);
});
test('woordaanbod volgt beginnende geletterdheid en een expliciete opbouw per groep',async()=>{
const data=await languageData();
const words=data.themesNl.flatMap(theme=>theme.words);
const byYear=year=>words.filter(([, ,introduced])=>introduced<=year).map(([word])=>word);
const group1=byYear(1),group2=byYear(2);
assert.ok(group1.length>=35&&group1.length<=60);
assert.ok(group2.length>=100&&group2.length<=180);
for(const word of ['aap','kat','vis','zon','boek','hand'])assert.ok(group1.includes(word),word);
for(const word of ['hond','appel','bloem','fiets','lezen','school'])assert.ok(group2.includes(word),word);
for(const word of ['som','hoek','team','puber','wet','computer','onderzoek'])assert.ok(!group2.includes(word),word);
const introduced=Object.fromEntries(words.map(([word,,year])=>[word,year]));
for(const word of ['toast','team','pizza','cadeau'])assert.ok(introduced[word]>=5,word);
for(const word of ['aardappel','ziekenhuis'])assert.ok(introduced[word]<=5,word);
assert.ok(introduced.pannenkoek<=6,'pannenkoek');
for(const [, ,year] of words)assert.ok(year>=1&&year<=8);
for(let year=1;year<=8;year++)assert.ok(words.some(([, ,introduced])=>introduced===year),'geen instroom in groep '+year);
});
test('alle aanvullende woorden hebben een gecontroleerd en correct gekoppeld woordplaatje',async()=>{
const data=await languageData();
const builtinByName=new Map(data.catalogNl.images.filter(image=>image.builtin).map(image=>[image.name,image]));
assert.ok(new Set(data.progression.map(word=>word.pic)).size>=400,'verwacht brede beeldvariatie');
for(const word of data.progression){
assert.equal(word.pictureChecked,true,word.nl);
const image=builtinByName.get(word.nl);
assert.ok(image,word.nl);
assert.equal(image.value,word.pic,word.nl);
assert.ok(image.src.startsWith('data:image/svg+xml;charset=utf-8,'),word.nl);
}
const expected={
kattenbak:'🐈📦',kattenvoer:'🐈🍽️',groentekraam:'🥕🏪',landingsbaan:'🛬',
kast:'🗄️',slaapkamerraam:'🪟',bewegingsapparaat:'🦴',bewegingsrichting:'🧭',
digitaal:'💻',kaart:'🃏',landbouwer:'🧑‍🌾',straat:'🛣️',plattegrond:'🗺️',
veehouderij:'🐄',wastafel:'🚰',tekstproductie:'✍️📄',verdrietverwerking:'😢🧠',
bezorgd:'😟',schoolslag:'🏊',toneelspeler:'🎭',spellingspatroon:'🔤',
meetinstrument:'📐',ruimtestation:'🛰️',grondrecht:'⚖️'
};
for(const [word,picture] of Object.entries(expected))
assert.equal(data.progression.find(item=>item.nl===word)?.pic,picture,word);
});
test('alle oude startwoorden blijven staan en krijgen ook een bibliotheekplaatje',async()=>{
const data=await languageData();
const oldNl=['aap','vis','zon','kat','bal','ei','boom','huis','maan','boot','kip','muis','boek','ster','roos','vuur','fiets','taart','jas','bril','klok','peer','kaas','eend','hand','voet','neus','oog','oor','mond'];
const oldEn=['cat','dog','sun','egg','ball','fish','tree','moon','boat','star','book','cake','house','mouse','apple','train','fire','rose','bear','duck','frog','bee','cow','pig','hand','foot','nose','eye','ear','milk'];
assert.deepEqual(data.words.nl.map(([word])=>word),oldNl);
assert.deepEqual(data.words.en.map(([word])=>word),oldEn);
const names=new Set(data.catalogNl.images.map(image=>image.name));
for(const word of oldNl)assert.ok(names.has(word),word);
const startYears=Object.fromEntries(data.words.nl.map(([word,,year])=>[word,year]));
assert.equal(startYears.aap,1);assert.equal(startYears.eend,2);assert.equal(startYears.fiets,2);
});
test('vaste en eigen woordplaatjes worden als herbruikbare catalogusitems aangeboden',async()=>{
const data=await languageData('GENERAL_EXTRA.nl=[["test","🧪"]]; THEMES.nl=[{name:"Boerderij",folder:"School",words:[["stal","🏚️"]]}];');
const builtins=data.catalogNl.images.filter(image=>image.builtin);
assert.equal(builtins.length,data.concepts.length+data.progression.length);
assert.equal(data.catalogNl.themes.filter(theme=>theme.languageBuiltin).length,16);
for(const image of builtins){
assert.ok(image.id<0);
assert.ok(image.src.startsWith('data:image/svg+xml;charset=utf-8,'));
assert.ok(image.value&&image.themes.length===1);
assert.equal(image.canManage,false);
}
const testPic=data.catalogNl.images.find(image=>!image.builtin&&image.name==='test');
const stablePic=data.catalogNl.images.find(image=>!image.builtin&&image.name==='stal');
assert.equal(testPic.value,'🧪');
assert.equal(stablePic.value,'🏚️');
assert.equal(stablePic.folder,'Taal/Mijn themas/School/Boerderij');
for(const word of ['familiegeschiedenis','competitiewedstrijd','mediageletterdheid','waterkringloop']){
const image=data.catalogNl.images.find(item=>item.name===word);
assert.ok(image&&image.yearGroup>=7&&image.src.startsWith('data:image/svg+xml'),word);
}
});
test('alle vier taalspellen gebruiken themabronnen en kunnen de bibliotheek openen',async()=>{
const [data,catalog,picker,letters,css,catalogCss]=await Promise.all([
readFile('public/js/data.js','utf8'),
readFile('public/js/image-catalog.js','utf8'),
readFile('public/js/picker.js','utf8'),
readFile('public/js/widgets/letters.js','utf8'),
readFile('public/css/teach.css','utf8'),
readFile('public/css/image-catalog.css','utf8'),
]);
assert.match(data,/card\.startsWith\("b:"\)/);
assert.match(data,/builtinGroup\.label = "📚 "/);
for(const widget of ['letters','flits','hak','ball']){
const source=await readFile(`public/js/widgets/${widget}.js`,'utf8');
assert.match(source,/wordSourceList\(/,widget);
assert.match(source,/fillWordSourceSel\(/,widget);
}
assert.match(catalog,/refreshWordPictureCatalog/);
assert.match(catalog,/languagePictureCatalog\(LANG\)/);
assert.match(catalog,/ic-word-themes/);
assert.match(picker,/data-t="library"/);
assert.match(picker,/uploadImageToCatalog\(f,/);
assert.match(letters,/data-t="library"/);
assert.match(letters,/openImageCatalog\(\(src,image\)/);
assert.match(letters,/uploadImageToCatalog\(f,/);
assert.match(css,/\.ed-tabs\{[^}]*overflow-x:auto/);
assert.match(catalogCss,/\.ic-word-themes\{[^}]*overflow-x:auto/);
assert.match(catalogCss,/@media\(max-width:760px\)/);
});