From dc3c591c769044cf5944bf66d725bb5d0c792e3e Mon Sep 17 00:00:00 2001 From: Ramon Date: Tue, 21 Jul 2026 22:27:44 +0200 Subject: [PATCH] feat: stem taalwidgets af op beginnende geletterdheid --- VERSION | 2 +- public/js/core.js | 2 +- public/js/data.js | 46 +++++++++++++++++++++++++++--- public/js/learning-progressions.js | 21 +++++++++++--- public/js/widgets/ball.js | 11 +++++-- public/js/widgets/flits.js | 13 ++++++--- public/js/widgets/hak.js | 28 ++++++++++++------ public/js/widgets/letters.js | 16 +++++++++-- test/language-library.test.js | 20 +++++++++++++ test/learning-progressions.test.js | 9 +++++- 10 files changed, 138 insertions(+), 30 deletions(-) diff --git a/VERSION b/VERSION index 9080e31..5fbcef1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.44-beta +0.4.45-beta diff --git a/public/js/core.js b/public/js/core.js index 8b73329..8718269 100644 --- a/public/js/core.js +++ b/public/js/core.js @@ -2,7 +2,7 @@ "use strict"; /* version — shown until /api/version resolves (or if the fetch fails, e.g. offline). Kept in sync by hand with the VERSION file at the repo root on every release. */ -const VERSION = "0.4.44-beta"; +const VERSION = "0.4.45-beta"; (function(){ const tag = document.getElementById("verTag"); tag.textContent = "v"+VERSION; diff --git a/public/js/data.js b/public/js/data.js index 1445b73..9904ccd 100644 --- a/public/js/data.js +++ b/public/js/data.js @@ -529,6 +529,42 @@ const LANGUAGE_NL_PROGRESSION_WORDS=(()=>{ return out })(); +/* Instapwoorden voor beginnende geletterdheid. In groep 1 zijn woorden alleen + beeldankers om een letter en beginklank te herkennen; kinderen hoeven deze + woorden nog niet te lezen. Groep 2 voegt korte, vertrouwde woorden toe voor + letter-klankkoppeling en het aanvullen van een ontbrekende klank. */ +const LANGUAGE_EARLY_WORDS=Object.freeze({ + 1:new Set("aap arm bal bed been boek boom boot bos bus deur ei hand huis jas kaas kat kip koe maan mier mond muis neus oog oor pen peer pet pop rok roos sok tas uil vis voet vuur wiel zon".split(" ")), + 2:new Set("appel beer bij eend geit haas hond kikker paard vos slak spin varken melk taart brood soep rijst mais druif snoep sap koek wortel ster bloem blad wolk regen water steen zand zee gras wind auto fiets trein taxi tram weg zeil step stoel klok bril blok sleutel lamp beker verf schaar school haar hoofd schoen jurk shirt want broek tand lezen eten slapen lopen lachen huilen zingen dansen spelen zitten rennen kijken blij boos bang moe ziek trots rustig baby kind jongen meisje vader moeder zus broer oma opa buur man voetbal puzzel spel knikker winkel boer park klas kok stad dorp dag week jaar feest kaars vlag uur".split(" ")) +}); +const LANGUAGE_LOANWORDS_FROM_YEAR5=new Set("wc ski toast jet rail brie pizza citroen cadeau team club cake score hockey bowlen shirt flat".split(" ")); + +function languageDataComplexity(word){ + const w=String(word||"").toLocaleLowerCase("nl-NL").replace(/[^a-z]/g,""); + const clusters=(w.match(/[bcdfghjklmnpqrstvwxz]{2,}/g)||[]).reduce((n,x)=>n+x.length-1,0); + const patterns=(w.match(/aai|ooi|oei|eeuw|ieuw|sch|ng|nk|ch|aa|ee|oo|uu|oe|ie|ei|ij|ou|au|ui|eu/g)||[]).length; + const syllables=(w.match(/[aeiouy]+/g)||[]).length; + return w.length+clusters*2+patterns+Math.max(0,syllables-1)*2; +} + +/* Een woord stroomt pas in als zowel de geplande woordfamilie, de spelling + als de inhoud bij het leerjaar passen. Dit voorkomt bijvoorbeeld dat korte + maar abstracte woorden als 'som', 'wet' en 'team' in de kleuterpool komen. */ +function languageRecommendedYear(item){ + const word=String(item.nl||"").toLocaleLowerCase("nl-NL"); + if(LANGUAGE_EARLY_WORDS[1].has(word))return 1; + if(LANGUAGE_EARLY_WORDS[2].has(word))return 2; + const complexity=languageDataComplexity(word); + const syllables=(word.match(/[aeiouy]+/g)||[]).length; + const patternYear=/eeuw|ieuw|aai|ooi|oei/.test(word)?5:/sch|ng|nk|ch|oe|ie|ei|ij|ou|au|ui|eu/.test(word)?4:3; + const loanwordYear=LANGUAGE_LOANWORDS_FROM_YEAR5.has(word)?5:3; + const spellingYear=Math.max(complexity<=9?3:complexity<=14?4:complexity<=19?5:complexity<=24?6:complexity<=30?7:8,syllables>=3?5:syllables>=2?4:3,patternYear,loanwordYear); + const themeYear={learning:4,science:4,society:5,arts:3,feelings:3}[item.theme]||3; + return Math.max(3,Number(item.yearGroup)||3,spellingYear,themeYear); +} +const LANGUAGE_NL_BUILTIN_WORDS=LANGUAGE_CONCEPTS.concat(LANGUAGE_NL_PROGRESSION_WORDS) + .map(item=>Object.assign({},item,{yearGroup:languageRecommendedYear(item)})); + /* De vertrouwde startkaart blijft exact dezelfde 30 woorden houden. */ const START_WORD_KEYS = { nl:["monkey","fish","sun","cat","ball","egg","tree","house","moon","boat","hen","mouse","book","star","rose","fire","bike","cake","coat","glasses","clock","pear","cheese","duck","hand","foot","nose","eye","ear","mouth"], @@ -537,12 +573,12 @@ const START_WORD_KEYS = { const WORDS = Object.fromEntries(["nl","en"].map(lang=>[ lang, START_WORD_KEYS[lang].map(id=>{ const concept=LANGUAGE_CONCEPTS.find(item=>item.id===id); - return [concept[lang],concept.pic]; + return [concept[lang],concept.pic,lang==="nl"?languageRecommendedYear(concept):null]; }) ])); function languageBuiltinWords(lang=LANG){ - return lang==="nl"?LANGUAGE_CONCEPTS.concat(LANGUAGE_NL_PROGRESSION_WORDS):LANGUAGE_CONCEPTS + return lang==="nl"?LANGUAGE_NL_BUILTIN_WORDS:LANGUAGE_CONCEPTS } function languageThemeList(lang=LANG){ return LANGUAGE_THEME_DEFS.map(theme=>({ @@ -663,7 +699,7 @@ function wordSourceList(card){ if(card.startsWith("a")) return anchorSources()[+card.slice(1)].words; return generalList(); } -function fillWordSourceSel(sel, card){ +function fillWordSourceSel(sel, card, yearGroup=null, level=2){ sel.innerHTML = ""; const og = document.createElement("option"); og.value = "g"; og.textContent = "⭐ "+T("cardStart"); @@ -671,9 +707,11 @@ function fillWordSourceSel(sel, card){ const builtinGroup = document.createElement("optgroup"); builtinGroup.label = "📚 "+T("wordThemes"); languageThemeList().forEach(theme=>{ + const available=yearGroup&&typeof languageWordsForYear==="function"?languageWordsForYear(theme.words,yearGroup,level):theme.words; + if(!available.length)return; const option=document.createElement("option"); option.value="b:"+theme.id; - option.textContent=`${theme.icon} ${theme.name} (${theme.words.length})`; + option.textContent=`${theme.icon} ${theme.name} (${available.length})`; builtinGroup.appendChild(option); }); sel.appendChild(builtinGroup); diff --git a/public/js/learning-progressions.js b/public/js/learning-progressions.js index 090b16c..b0da5eb 100644 --- a/public/js/learning-progressions.js +++ b/public/js/learning-progressions.js @@ -35,9 +35,9 @@ const LEARNING_YEAR_PROFILES=Object.freeze({ }); const LANGUAGE_WIDGET_TASKS=Object.freeze({ letters:["","letters en beginklanken","letters en ontbrekende klanken","klankzuivere woorden opbouwen","clusters en tweetekenklanken","meerlettergrepige woorden","regelwoorden en langere woorden","complexe spelling en werkwoorden","spellingkennis zelfstandig toepassen"], - flits:["","korte bekende woorden","korte klankzuivere woorden","woorden vlot herkennen","langere woorden automatiseren","complexere woorden vlot lezen","schooltaalwoorden automatiseren","laagfrequente schooltaalwoorden","complexe woorden nauwkeurig lezen"], - hak:["","woorden in klanken horen","klanken hakken en plakken","klankgroepen samenvoegen","spellingpatronen herkennen","lettergrepen onderzoeken","complexe klankgroepen","woordopbouw en morfemen","woordstructuur verklaren"], - ball:["","korte beeldwoorden","korte woorden raden","woorden actief herkennen","langere woorden spellen","complexere spelling","woordenschat consolideren","complexe woordbeelden","gevorderde spelling"], + flits:["","letters herkennen en benoemen","korte klankzuivere woorden","woorden vlot herkennen","langere woorden automatiseren","complexere woorden vlot lezen","schooltaalwoorden automatiseren","laagfrequente schooltaalwoorden","complexe woorden nauwkeurig lezen"], + hak:["","beginklanken horen en koppelen","klanken hakken en plakken","klankgroepen samenvoegen","spellingpatronen herkennen","lettergrepen onderzoeken","complexe klankgroepen","woordopbouw en morfemen","woordstructuur verklaren"], + ball:["","letters bij beeldankers kiezen","korte woorden raden","woorden actief herkennen","langere woorden spellen","complexere spelling","woordenschat consolideren","complexe woordbeelden","gevorderde spelling"], sentence:["","eenvoudige zinnen vertellen","wie-doet-wat-zinnen","enkelvoudige zinnen","zinnen uitbreiden","informatieve zinnen","zinsverband en leestekens","argumenterende zinnen","gevarieerde teksten formuleren"], wordtypes:["","woorden benoemen","naamwoorden en doe-woorden","naamwoord en werkwoord","drie woordsoorten","woordsoorten in context","woordsoorten functioneel benoemen","woordsoort en zinsfunctie","taalstructuur analyseren"] }); @@ -77,15 +77,28 @@ function languageWordsForYear(words,yearGroup,level){ if(!Array.isArray(words)||!words.length)return[]; const year=learningYearGroup(yearGroup);if(!year)return words; const introduced=words.filter(item=>!Array.isArray(item)||item[2]==null||Number(item[2])<=year); + const hasExplicitYears=words.some(item=>Array.isArray(item)&&item[2]!=null); + if(!introduced.length&&hasExplicitYears)return[]; const available=introduced.length?introduced:words; const caps=[0,5,7,9,12,15,18,22,99],cap=caps[year]+(learningLevel(level)-2)*2; const filtered=available.filter(item=>languageWordComplexity(Array.isArray(item)?item[0]:item)<=cap); if(filtered.length>=Math.min(4,available.length))return filtered; return [...available].sort((a,b)=>languageWordComplexity(a[0])-languageWordComplexity(b[0])).slice(0,Math.min(4,available.length)) } +function languageWidgetWords(words,yearGroup,level,widgetId){ + const selected=languageWordsForYear(words,yearGroup,level),year=learningYearGroup(yearGroup); + if(year!==1||!["flits","hak","ball"].includes(widgetId))return selected; + const initials=new Map(); + selected.forEach(item=>{ + const word=String(Array.isArray(item)?item[0]:item||"").toLocaleLowerCase("nl-NL"); + const letter=(word.match(/[a-z]/)||[])[0]; + if(letter&&!initials.has(letter))initials.set(letter,[letter,Array.isArray(item)?item[1]:"",1,word]); + }); + return [...initials.values()] +} function languageTaskRules(yearGroup,level){ const year=learningYearGroup(yearGroup);if(!year)return null;const l=learningLevel(level); - return{letterLevel:year<=2?Math.min(2,l):year===3?Math.max(2,l):3,flashSeconds:year<=2?3:year<=4?2:year<=6?1:.5,lives:year<=3?7:year<=5?6:5,wordtypeCount:year<=3?2:year===4?3:4} + return{literacyMode:year===1?"letters":year===2?"sounds":"words",letterLevel:year<=2?Math.min(2,l):year===3?Math.max(2,l):3,flashSeconds:year<=2?3:year<=4?2:year<=6?1:.5,lives:year<=3?7:year<=5?6:5,wordtypeCount:year<=3?2:year===4?3:4} } function mathTaskRules(yearGroup,level){ const year=learningYearGroup(yearGroup);if(!year)return null;const l=learningLevel(level),factor=l===1?.25:l===2?.6:1; diff --git a/public/js/widgets/ball.js b/public/js/widgets/ball.js index 9284782..4801ea7 100644 --- a/public/js/widgets/ball.js +++ b/public/js/widgets/ball.js @@ -17,6 +17,7 @@ function mountBall(root, initState, opts){
+
@@ -25,13 +26,16 @@ function mountBall(root, initState, opts){
`; const el = s => root.querySelector(s); - const srcSel = el(".bl-src"), balloonsEl = el(".bl-balloons"), + const srcSel = el(".bl-src"), balloonsEl = el(".bl-balloons"), picEl=el(".bl-pic"), wordEl = el(".bl-word"), msgEl = el(".bl-msg"), nextBtn = el(".bl-next"), kbdEl = el(".bl-kbd"), scoreEl = el(".bl-score"), sndBtn = el(".lg-snd"); - function list(){ S.card = normWordSource(S.card); return languageWordsForYear(wordSourceList(S.card),S.yearGroup,S.level); } - function buildSrc(){ fillWordSourceSel(srcSel, S.card); } + function list(){ + S.card = normWordSource(S.card);const selected=languageWidgetWords(wordSourceList(S.card),S.yearGroup,S.level,"ball"); + if(selected.length)return selected;S.card="g";return languageWidgetWords(wordSourceList("g"),S.yearGroup,S.level,"ball"); + } + function buildSrc(){ fillWordSourceSel(srcSel, S.card,S.yearGroup,S.level);S.card=srcSel.value||"g"; } "abcdefghijklmnopqrstuvwxyz".split("").forEach(l=>{ const b = document.createElement("button"); b.textContent = l; b.dataset.l = l; @@ -55,6 +59,7 @@ function mountBall(root, initState, opts){ msgEl.textContent = ""; msgEl.className = "bl-msg"; nextBtn.classList.remove("show"); kbdEl.querySelectorAll("button").forEach(b=>{ b.disabled = false; b.style.opacity = 1; }); + const current=list()[S.order[S.idx]];setPic(picEl,current[1]);picEl.title=current[3]||current[0]; drawWord(false); drawBalloons(); scoreEl.textContent = S.stars ? S.stars+" ★" : ""; } diff --git a/public/js/widgets/flits.js b/public/js/widgets/flits.js index 4a15686..a6b8add 100644 --- a/public/js/widgets/flits.js +++ b/public/js/widgets/flits.js @@ -34,9 +34,13 @@ function mountFlits(root, initState){ const srcSel = el(".fl-src"), durSel = el(".fl-dur"), wordEl = el(".fl-word"), picEl = el(".fl-pic"), cntEl = el(".fl-cnt"), reflect=el(".fl-reflect"); - function list(){ S.card = normWordSource(S.card); return languageWordsForYear(wordSourceList(S.card),S.yearGroup,S.level); } + function list(){ + S.card = normWordSource(S.card);const selected=languageWidgetWords(wordSourceList(S.card),S.yearGroup,S.level,"flits"); + if(selected.length)return selected;S.card="g";return languageWidgetWords(wordSourceList("g"),S.yearGroup,S.level,"flits"); + } function buildSrc(){ - fillWordSourceSel(srcSel, S.card); + fillWordSourceSel(srcSel, S.card,S.yearGroup,S.level); + S.card=srcSel.value||"g"; durSel.innerHTML = ""; [0.5,1,2,3,5].forEach(d=>{ const o = document.createElement("option"); @@ -97,8 +101,9 @@ function mountFlits(root, initState){ el(".fl-flash").textContent = T("flFlash"); el(".fl-show").textContent = T("flShow"); el(".fl-next").textContent = T("flNext"); - el(".fl-mission").textContent=T("flMission"); - el(".fl-question").textContent=T("flQuestion"); + const letters=languageTaskRules(S.yearGroup,S.level)?.literacyMode==="letters"; + el(".fl-mission").textContent=letters?(LANG==="nl"?"Bekijk en benoem de letter.":"Look at and name the letter."):T("flMission"); + el(".fl-question").textContent=letters?(LANG==="nl"?"Herkende je de letter?":"Did you recognise the letter?"):T("flQuestion"); el(".fl-got").textContent=T("flGotIt"); el(".fl-again").textContent=T("flAgain"); buildSrc(); diff --git a/public/js/widgets/hak.js b/public/js/widgets/hak.js index e08b087..1fd603a 100644 --- a/public/js/widgets/hak.js +++ b/public/js/widgets/hak.js @@ -45,14 +45,20 @@ function mountHak(root, initState){ const srcSel = el(".hk-src"), wordEl = el(".hk-word"), picEl = el(".hk-pic"), cntEl = el(".hk-cnt"); - function list(){ S.card = normWordSource(S.card); return languageWordsForYear(wordSourceList(S.card),S.yearGroup,S.level); } - function buildSrc(){ fillWordSourceSel(srcSel, S.card); } + function list(){ + S.card = normWordSource(S.card);const selected=languageWidgetWords(wordSourceList(S.card),S.yearGroup,S.level,"hak"); + if(selected.length)return selected;S.card="g";return languageWidgetWords(wordSourceList("g"),S.yearGroup,S.level,"hak"); + } + function buildSrc(){ fillWordSourceSel(srcSel, S.card,S.yearGroup,S.level);S.card=srcSel.value||"g"; } function cur(){ return list()[S.order[S.idx]]; } function newRound(){ S.order = shuffle(list().map((_,i)=>i)); S.idx = 0; } function display(){ - const [w, pic] = cur(); + const [w, pic,, anchor] = cur(); + const letters=languageTaskRules(S.yearGroup,S.level)?.literacyMode==="letters"; wordEl.innerHTML = ""; - if(S.chopped){ + if(letters&&!S.chopped){ + const s=document.createElement("span");s.className="hk-whole";s.textContent="?";wordEl.appendChild(s); + }else if(S.chopped){ soundChunks(w).forEach((l,i)=>{ if(i>0){ const sep = document.createElement("span"); @@ -72,6 +78,7 @@ function mountHak(root, initState){ wordEl.appendChild(s); } setPic(picEl, pic); + picEl.title = anchor||w; cntEl.textContent = `${T("word")} ${S.idx+1}/${S.order.length}`; } function speak(text, rate){ @@ -83,8 +90,10 @@ function mountHak(root, initState){ speechSynthesis.speak(u); } el(".hk-say").addEventListener("click", ()=>{ - if(S.chopped) speak(soundChunks(cur()[0]).join(", "), 0.7); - else speak(cur()[0], 0.85); + const [word,,,anchor]=cur(),letters=languageTaskRules(S.yearGroup,S.level)?.literacyMode==="letters"; + if(letters)speak(anchor||word,0.75); + else if(S.chopped)speak(soundChunks(word).join(", "),0.7); + else speak(word,0.85); }); el(".hk-chop").addEventListener("click", ()=>{ S.chopped = true; display(); play(sndGood); }); el(".hk-glue").addEventListener("click", ()=>{ S.chopped = false; display(); play(sndGood); }); @@ -98,10 +107,11 @@ function mountHak(root, initState){ newRound(); S.chopped = false; display(); }); function labels(){ + const letters=languageTaskRules(S.yearGroup,S.level)?.literacyMode==="letters"; el(".hk-say").textContent = T("hakSay"); - el(".hk-chop").textContent = T("hakChop"); - el(".hk-glue").textContent = T("hakGlue"); - el(".hk-hint").textContent = T("hakHint"); + el(".hk-chop").textContent = letters?(LANG==="nl"?"Toon letter":"Show letter"):T("hakChop"); + el(".hk-glue").textContent = letters?(LANG==="nl"?"Verberg letter":"Hide letter"):T("hakGlue"); + el(".hk-hint").textContent = letters?(LANG==="nl"?"Luister naar het woord. Welke letter hoor je vooraan?":"Listen to the word. Which letter do you hear first?"):T("hakHint"); buildSrc(); } document.addEventListener("langchange", ()=>{ S.card=0; labels(); newRound(); S.chopped=false; display(); }); diff --git a/public/js/widgets/letters.js b/public/js/widgets/letters.js index c611910..7aa603e 100644 --- a/public/js/widgets/letters.js +++ b/public/js/widgets/letters.js @@ -155,7 +155,8 @@ function mountLetterGame(root, initState, opts){ cardsEl.innerHTML = ""; srcSel = document.createElement("select"); srcSel.className = "fl-sel"; - fillWordSourceSel(srcSel, S.card); + fillWordSourceSel(srcSel, S.card,S.yearGroup,S.level); + S.card=srcSel.value||"g"; srcSel.addEventListener("change", ()=>{ S.card = srcSel.value; newRound(); render(); }); cardsEl.appendChild(srcSel); if(!readonly){ @@ -185,7 +186,9 @@ function mountLetterGame(root, initState, opts){ function fullList(){ S.card = normWordSource(S.card); - return languageWordsForYear(wordSourceList(S.card),S.yearGroup,S.level); + const selected=languageWordsForYear(wordSourceList(S.card),S.yearGroup,S.level); + if(selected.length)return selected; + S.card="g";return languageWordsForYear(wordSourceList("g"),S.yearGroup,S.level); } function newRound(){ const list = fullList(); @@ -245,13 +248,20 @@ function mountLetterGame(root, initState, opts){ if(allOpt) allOpt.textContent = T("all"); const [word, pic] = currentWord(); + const literacyMode=languageTaskRules(S.yearGroup,S.level)?.literacyMode||"words"; setPic(picEl, pic); countEl.textContent = `${T("word")} ${S.wordIdx+1}/${S.order.length}`; starsEl.textContent = S.stars ? S.stars+" ★" : ""; slotsEl.innerHTML = ""; trayEl.innerHTML = ""; const letters = word.split(""); - if(S.level===1){ + if(literacyMode==="letters"){ + const first=letters[0]; + instrEl.textContent=LANG==="nl"?"Welke letter hoor je vooraan?":"Which letter do you hear first?"; + slotsEl.appendChild(makeSlot(first,false)); + shuffle([first].concat(distractors(word,S.level===1?2:3))).forEach(l=>trayEl.appendChild(makeBlock(l))); + } + else if(S.level===1){ instrEl.innerHTML = T("instr1"); letters.forEach(l=>slotsEl.appendChild(makeSlot(l,true))); shuffle(letters.concat(distractors(word,3))).forEach(l=>trayEl.appendChild(makeBlock(l))); diff --git a/test/language-library.test.js b/test/language-library.test.js index 4bd1ec6..31b964c 100644 --- a/test/language-library.test.js +++ b/test/language-library.test.js @@ -48,6 +48,24 @@ test('taalwidgets delen ruim duizend extra Nederlandse woorden over leerjaar en 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])); @@ -79,6 +97,8 @@ test('alle oude startwoorden blijven staan en krijgen ook een bibliotheekplaatje 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()=>{ diff --git a/test/learning-progressions.test.js b/test/learning-progressions.test.js index 46b2c36..cbc361c 100644 --- a/test/learning-progressions.test.js +++ b/test/learning-progressions.test.js @@ -10,6 +10,7 @@ async function progressionContext(){ runInNewContext(pure+` ;globalThis.__profiles=LEARNING_YEAR_PROFILES; globalThis.__languageWordsForYear=languageWordsForYear; + globalThis.__languageWidgetWords=languageWidgetWords; globalThis.__languageTaskRules=languageTaskRules; globalThis.__mathTaskRules=mathTaskRules; `,context); @@ -63,6 +64,11 @@ test('taalprofiel selecteert eenvoudiger woorden in de onderbouw en bouwt taakst assert.equal(c.__languageTaskRules(2,2).flashSeconds,3); assert.equal(c.__languageTaskRules(7,2).flashSeconds,.5); assert.ok(c.__languageTaskRules(2,2).lives>c.__languageTaskRules(7,2).lives); + assert.equal(c.__languageTaskRules(1,2).literacyMode,'letters'); + assert.equal(c.__languageTaskRules(2,2).literacyMode,'sounds'); + assert.equal(c.__languageTaskRules(3,2).literacyMode,'words'); + const letters=JSON.parse(JSON.stringify(c.__languageWidgetWords(words,1,2,'flits'))); + assert.ok(letters.every(([letter,,,anchor])=>letter.length===1&&anchor.startsWith(letter))); }); test('rekenprofiel vergroot getalgebied, tafels en breuken per leerjaar',async()=>{ @@ -92,7 +98,8 @@ test('alle taal- en rekenwidgets ontvangen de centrale leerjaarstate en tonen he assert.match(pupil,/applyLearningLevelToState/); assert.match(pupil,/appendLearningProfileBadge/); assert.match(progressions,/managedLearningLevel/); - for(const source of [letters,flits,hak,ball])assert.match(source,/languageWordsForYear/); + assert.match(letters,/languageWordsForYear/); + for(const source of [flits,hak,ball])assert.match(source,/languageWidgetWords/); assert.match(math,/mathTaskRules/); assert.match(numberline,/mathTaskRules/); for(const mount of ['mountFractions','mountClock','mountPlacevalue','mountMoney','mountWordtypes']){