From 9bab7f39c02c8a08fe92b6861e978c282b411b80 Mon Sep 17 00:00:00 2001 From: Ramon Date: Sun, 19 Jul 2026 21:46:38 +0200 Subject: [PATCH] feat: koppel widgets aan kerndoelen (v0.4.38-beta) --- VERSION | 2 +- public/css/teach.css | 13 +++++ public/index.html | 1 - public/js/board.js | 100 ++++++++++++++++++++++++++++++++++- public/js/core.js | 15 ++++-- public/js/curriculum.js | 10 ++-- public/js/settings.js | 4 +- public/js/wiki.js | 9 ++-- test/curriculum-wiki.test.js | 44 ++++++++++++--- 9 files changed, 174 insertions(+), 24 deletions(-) diff --git a/VERSION b/VERSION index bf44e5c..e735d60 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.37-beta +0.4.38-beta diff --git a/public/css/teach.css b/public/css/teach.css index a53c8f6..5699a76 100644 --- a/public/css/teach.css +++ b/public/css/teach.css @@ -637,6 +637,19 @@ .st-sec-title{font-size:17px; font-weight:800;} .st-sec-sub{font-size:12.5px; color:var(--muted); margin-top:2px;} /* curriculumwiki: volwassen, informatief en bruikbaar op tablet/telefoon */ + + .wiki-goal-focus{outline:3px solid var(--accent);outline-offset:3px;animation:wikiFocus 1.4s ease-out} + @keyframes wikiFocus{from{background:var(--accent-soft)}to{background:var(--surface)}} + .winfo[hidden]{display:none}\n .widget-curriculum-modal{width:min(620px,94vw);max-height:min(760px,88vh);overflow:auto} + .widget-curriculum-head{display:flex;align-items:center;gap:12px;margin-bottom:8px} + .widget-curriculum-head h2{flex:1;margin:0;font-size:20px} + .widget-curriculum-goals{display:flex;flex-direction:column;gap:8px;margin:14px 0} + .widget-curriculum-goal{display:flex;flex-direction:column;gap:4px;padding:11px 12px;border:1px solid var(--line);border-radius:12px;background:var(--surface-2);color:var(--ink);text-decoration:none} + .widget-curriculum-goal:hover,.widget-curriculum-goal:focus-visible{border-color:var(--accent);outline:2px solid var(--accent-soft)} + .widget-curriculum-goal span{font-size:13px;line-height:1.4}.widget-curriculum-goal small{color:var(--accent-ink);font-weight:750} + .widget-curriculum-empty,.widget-curriculum-careful{padding:10px 12px;border-radius:10px;background:var(--surface-2);font-size:13px;line-height:1.45} + .widget-curriculum-careful{color:var(--muted)}.widget-curriculum-all{display:inline-flex;text-decoration:none} + #curriculumWiki{display:flex;flex-direction:column;gap:14px;padding-bottom:10px;} .wiki-hero{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;padding:18px;border:1px solid var(--line);border-radius:12px;background:linear-gradient(135deg,var(--surface),var(--surface-2));} .wiki-hero-text{max-width:680px}.wiki-kicker{margin-bottom:5px;color:var(--accent);font-size:11px;font-weight:750;letter-spacing:.6px;text-transform:uppercase} diff --git a/public/index.html b/public/index.html index 0d4ee62..c2266c4 100644 --- a/public/index.html +++ b/public/index.html @@ -70,7 +70,6 @@ - diff --git a/public/js/board.js b/public/js/board.js index b3e01c4..4fd06ed 100644 --- a/public/js/board.js +++ b/public/js/board.js @@ -95,6 +95,92 @@ function clampWidgetSize(w, h, def){ blijft vinden ook als er widgets vóór/na worden toegevoegd of verplaatst */ function genWidgetId(){ return "w" + Date.now().toString(36) + Math.random().toString(36).slice(2,8); } +function curriculumGoalsForWidget(widgetId){ + const matches=[]; + CURRICULUM_PO_2026.areas.forEach(area=>area.goals.forEach(goal=>{ + if(goal.widgets.includes(widgetId))matches.push({area,goal}); + })); + return matches; +} +let widgetCurriculumWrap=null; +function closeWidgetCurriculumInfo(){ + if(widgetCurriculumWrap)widgetCurriculumWrap.classList.remove("open"); +} +function openWidgetCurriculumInfo(def){ + if(!currentUser||!can("curriculum.view"))return; + if(!widgetCurriculumWrap){ + widgetCurriculumWrap=document.createElement("div"); + widgetCurriculumWrap.className="modalwrap"; + widgetCurriculumWrap.id="widgetCurriculumWrap"; + widgetCurriculumWrap.setAttribute("role","dialog"); + widgetCurriculumWrap.setAttribute("aria-modal","true"); + widgetCurriculumWrap.addEventListener("click",event=>{ + if(event.target===widgetCurriculumWrap)closeWidgetCurriculumInfo(); + }); + document.body.appendChild(widgetCurriculumWrap); + } + const panel=document.createElement("div"); + panel.className="modal widget-curriculum-modal"; + const head=document.createElement("div"); + head.className="widget-curriculum-head"; + const title=document.createElement("h2"); + title.id="widgetCurriculumTitle"; + title.textContent=def.icon+" "+T("wg_"+def.id); + const close=document.createElement("button"); + close.type="button";close.className="wclose";close.textContent="✕"; + close.title=T("close");close.setAttribute("aria-label",T("close")); + close.addEventListener("click",closeWidgetCurriculumInfo); + head.append(title,close);panel.appendChild(head); + panel.appendChild(document.createTextNode(T("widgetCurriculumIntro"))); + const goals=curriculumGoalsForWidget(def.id); + if(goals.length){ + const list=document.createElement("div"); + list.className="widget-curriculum-goals"; + goals.forEach(({area,goal})=>{ + const link=document.createElement("a"); + link.className="widget-curriculum-goal"; + link.href="#wiki-goal-"+goal.id; + const label=document.createElement("strong"); + label.textContent=area.icon+" "+T("wikiGoal")+" "+goal.id+" · "+area.title; + const summary=document.createElement("span"); + summary.textContent=goal.summary; + const status=document.createElement("small"); + status.textContent=T(goal.coverage==="partial"?"wikiCoveragePartial":goal.coverage==="support"?"wikiCoverageSupport":"wikiCoverageOpen")+" · "+T("widgetCurriculumWiki"); + link.append(label,summary,status); + link.addEventListener("click",event=>{ + event.preventDefault();closeWidgetCurriculumInfo(); + openSettings("curriculum",{area:area.id,goal:goal.id}); + }); + list.appendChild(link); + }); + panel.appendChild(list); + }else{ + const empty=document.createElement("p"); + empty.className="widget-curriculum-empty"; + empty.textContent=T("widgetCurriculumNone"); + panel.appendChild(empty); + } + const careful=document.createElement("p"); + careful.className="widget-curriculum-careful"; + careful.textContent=T("widgetCurriculumCareful"); + panel.appendChild(careful); + const all=document.createElement("a"); + all.className="tbtn widget-curriculum-all"; + all.href="#curriculum"; + all.textContent=T("widgetCurriculumAll"); + all.addEventListener("click",event=>{ + event.preventDefault();closeWidgetCurriculumInfo();openSettings("curriculum"); + }); + panel.appendChild(all); + widgetCurriculumWrap.setAttribute("aria-labelledby",title.id); + widgetCurriculumWrap.replaceChildren(panel); + widgetCurriculumWrap.classList.add("open"); + close.focus(); +} +document.addEventListener("userchange",()=>{ + if(!currentUser||!can("curriculum.view"))closeWidgetCurriculumInfo(); +}); + function makeWidget(def, saved){ const win = document.createElement("div"); win.className = "widget"; @@ -117,6 +203,10 @@ function makeWidget(def, saved){ const head = document.createElement("div"); head.className = "widget-head"; head.innerHTML = `${def.icon}`; + const infoBtn = document.createElement("button"); + infoBtn.className="wmin winfo";infoBtn.type="button";infoBtn.textContent="ⓘ";infoBtn.hidden=true; + infoBtn.addEventListener("click",event=>{event.stopPropagation();openWidgetCurriculumInfo(def);}); + head.appendChild(infoBtn); const dupBtn = document.createElement("button"); dupBtn.className="wmin wdup"; dupBtn.textContent="⧉"; head.appendChild(dupBtn); const lockBtn = document.createElement("button"); lockBtn.className="wmin wlock"; lockBtn.textContent="🔓"; head.appendChild(lockBtn); const previewBtn = document.createElement("button"); previewBtn.className="wmin wpreview"; previewBtn.textContent="◉"; head.appendChild(previewBtn); @@ -227,7 +317,7 @@ function makeWidget(def, saved){ } maxBtn.addEventListener("click", ()=>{ setMax(win.dataset.max!=="1"); markBoardChange(); }); head.addEventListener("dblclick", e=>{ - if(e.target===close || e.target===minBtn || e.target===maxBtn) return; + if(e.target.closest("button")) return; setMin(true); }); @@ -236,14 +326,18 @@ function makeWidget(def, saved){ minBtn.title = T("wMin"); maxBtn.title = T(win.dataset.max==="1" ? "wUnfull" : "wFull"); zBtn.title = T("wZoom"); + infoBtn.title = T("widgetCurriculumInfo"); + infoBtn.setAttribute("aria-label",infoBtn.title); if(pill){ pill.querySelector(".dl").textContent = T("wg_"+def.id); pill.title = T("wMax"); pill.querySelector(".dock-close").title = T("close"); } }; - setTitle(); + const setCurriculumInfoVisibility=()=>{infoBtn.hidden=!(currentUser&&can("curriculum.view"));}; + setTitle();setCurriculumInfoVisibility(); document.addEventListener("langchange", setTitle); + document.addEventListener("userchange",setCurriculumInfoVisibility); win.addEventListener("pointerdown", ()=>{ win.style.zIndex = ++zTop; }); head.addEventListener("pointerdown", e=>{ @@ -271,6 +365,8 @@ function makeWidget(def, saved){ function removeWidget(){ markBoardChange(); if(pill){ pill.remove(); pill = null; } + document.removeEventListener("langchange",setTitle); + document.removeEventListener("userchange",setCurriculumInfoVisibility); instances.delete(inst); win.remove(); updateEmptyHint(); } close.addEventListener("click", removeWidget); diff --git a/public/js/core.js b/public/js/core.js index 1ce90f2..ec01380 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.37-beta"; +const VERSION = "0.4.38-beta"; (function(){ const tag = document.getElementById("verTag"); tag.textContent = "v"+VERSION; @@ -234,7 +234,7 @@ const I18N = { stGroupPersonal:"Persoonlijk", stGroupSchool:"School", stGroupSystem:"Systeem", stAccountSub:"Je gegevens, wachtwoord en reservekopieën.", stViewSub:"Weergave op dit apparaat: thema, taal en menubalk.", - wikiTitle:"Curriculumwiki", wikiShort:"Wiki", + wikiTitle:"Curriculumwiki", wikiSub:"Nieuwe SLO-kerndoelen voor het primair onderwijs, per leergebied.", wikiKicker:"Primair onderwijs · juli 2026", wikiIntro:"Bekijk de 40 nieuwe kerndoelen, hun onderdelen en de huidige aansluiting van Teach.", @@ -254,6 +254,10 @@ const I18N = { wikiWidgets:"Huidige widgets", wikiNoResults:"Geen kerndoelen gevonden.", wikiCarefulTitle:"Zorgvuldig gebruiken.", wikiCarefulText:"Een widget die één onderdeel oefent, dekt niet automatisch het volledige kerndoel. School- en ervaringsdoelen vragen andere vormen van bewijs dan een goed/fout-score.", + widgetCurriculumInfo:"Kerndoelinformatie", widgetCurriculumIntro:"Deze widget draagt bij aan de onderstaande kerndoelen.", + widgetCurriculumNone:"Voor deze ondersteunende widget is geen directe koppeling met een kerndoel vastgesteld.", + widgetCurriculumWiki:"Bekijk dit kerndoel in de curriculumwiki", widgetCurriculumAll:"Open de volledige curriculumwiki", + widgetCurriculumCareful:"Een widget ondersteunt onderdelen van een kerndoel en vormt op zichzelf geen bewijs dat het volledige doel is behaald.", wikiLanguageNote:"The official SLO goals and these implementation summaries are shown in Dutch.", stUsersSub:"Accounts beheren: leerlingen, staf, ouders en aanmeldingen.", stKlassenSub:"Klassen aanmaken, koppelen en beheren.", @@ -603,7 +607,7 @@ const I18N = { stGroupPersonal:"Personal", stGroupSchool:"School", stGroupSystem:"System", stAccountSub:"Your details, password and backups.", stViewSub:"Display on this device: theme, language and toolbar.", - wikiTitle:"Curriculum wiki", wikiShort:"Wiki", + wikiTitle:"Curriculum wiki", wikiSub:"New SLO attainment targets for Dutch primary education, grouped by learning area.", wikiKicker:"Primary education · July 2026", wikiIntro:"Browse all 40 new attainment targets, their components and Teach's current coverage.", @@ -623,6 +627,10 @@ const I18N = { wikiWidgets:"Current widgets", wikiNoResults:"No attainment targets found.", wikiCarefulTitle:"Use with care.", wikiCarefulText:"A widget practising one component does not automatically cover the full target. School-level and experience targets require different evidence from a right/wrong score.", + widgetCurriculumInfo:"Attainment target information", widgetCurriculumIntro:"This widget contributes to the attainment targets below.", + widgetCurriculumNone:"No direct attainment-target link has been established for this supporting widget.", + widgetCurriculumWiki:"View this target in the curriculum wiki", widgetCurriculumAll:"Open the complete curriculum wiki", + widgetCurriculumCareful:"A widget supports parts of a target and is not, by itself, evidence that the full target has been achieved.", wikiLanguageNote:"The official SLO goals and these implementation summaries are shown in Dutch.", stUsersSub:"Manage accounts: pupils, staff, parents and enrolments.", stKlassenSub:"Create, link and manage classes.", @@ -1011,7 +1019,6 @@ const roleKey = r => r==="super" ? "roleSuper" : r==="admin" ? "roleAdmin" : r== function updateUserUI(){ document.getElementById("userLbl").textContent = currentUser ? currentUser.displayName : T("login"); document.getElementById("btnUser").classList.toggle("on", !!currentUser); - document.getElementById("btnWiki").style.display = can("curriculum.view") ? "" : "none"; /* welke instellingen-secties zichtbaar zijn bepaalt settings.js per rol (sectieregister); hier alleen nog de accountgegevens zelf */ if(currentUser){ diff --git a/public/js/curriculum.js b/public/js/curriculum.js index 3c2d85b..e6b023e 100644 --- a/public/js/curriculum.js +++ b/public/js/curriculum.js @@ -30,8 +30,8 @@ const CURRICULUM_PO_2026=Object.freeze({ id:"rekenen-en-wiskunde",icon:"🔢",title:"Rekenen en wiskunde",goalsRange:"10–18",page:32, description:"Wiskundige concepten, denk-werkwijzen, taal en gereedschap begrijpen en toepassen in de wereld.", goals:[ - {id:10,domain:"Wiskundige concepten",summary:"Leerlingen redeneren en rekenen met getallen, breuken en verhoudingen.",parts:["Gehele en decimale getallen","Breuken","Verhoudingen"],coverage:"partial",widgets:["madd","msub","mmul","mdiv","mpct","numberline","fractions","placevalue"]}, - {id:11,domain:"Wiskundige concepten",summary:"Leerlingen meten, redeneren en rekenen met grootheden, eenheden en passende meetinstrumenten.",parts:["Grootheden en eenheden"],coverage:"partial",widgets:["clock","money"]}, + {id:10,domain:"Wiskundige concepten",summary:"Leerlingen redeneren en rekenen met getallen, breuken en verhoudingen.",parts:["Gehele en decimale getallen","Breuken","Verhoudingen"],coverage:"partial",widgets:["madd","msub","mmul","mdiv","mpct","numberline","fractions","placevalue","dice"]}, + {id:11,domain:"Wiskundige concepten",summary:"Leerlingen meten, redeneren en rekenen met grootheden, eenheden en passende meetinstrumenten.",parts:["Grootheden en eenheden"],coverage:"partial",widgets:["clock","money","timer","schedule"]}, {id:12,domain:"Wiskundige concepten",summary:"Leerlingen representeren en interpreteren data en beoordelen conclusies bij gegevens kritisch.",parts:["Data"],coverage:"support",widgets:["poll"]}, {id:13,domain:"Wiskundige concepten",summary:"Leerlingen herkennen, beschrijven en onderzoeken patronen en verbanden.",parts:["Patronen en verbanden"],coverage:"open",widgets:[]}, {id:14,domain:"Wiskundige concepten",summary:"Leerlingen redeneren over meetkundige figuren en plaatsbepaling en voeren transformaties uit.",parts:["Meetkunde"],coverage:"open",widgets:[]}, @@ -74,7 +74,7 @@ const CURRICULUM_PO_2026=Object.freeze({ description:"Natuurwetenschap, technologie, organismen, gezondheid en systeem aarde onderzoekend leren begrijpen.", goals:[ {id:29,domain:"Natuurwetenschappen en technologie",summary:"Leerlingen onderzoeken vraagstukken met natuurwetenschappelijke en technologische denk- en werkwijzen.",parts:["Vraagstukken","Denkwijzen","Werkwijzen","Aard van natuurwetenschappen en technologie"],coverage:"partial",widgets:["trees","birds","plants"]}, - {id:30,domain:"Natuur- en scheikunde en technische systemen",summary:"Leerlingen onderzoeken technische systemen, stoffen en verschijnselen rond licht, geluid, energie en krachten.",parts:["Technische systemen","Stoffen en hun eigenschappen","Licht, geluid, energie en krachten"],coverage:"open",widgets:[]}, + {id:30,domain:"Natuur- en scheikunde en technische systemen",summary:"Leerlingen onderzoeken technische systemen, stoffen en verschijnselen rond licht, geluid, energie en krachten.",parts:["Technische systemen","Stoffen en hun eigenschappen","Licht, geluid, energie en krachten"],coverage:"support",widgets:["noise"]}, {id:31,domain:"Organismen en gezondheid",summary:"Leerlingen onderzoeken organismen en hun levensprocessen en leren onderbouwde keuzes rond lichamelijke en mentale gezondheid.",parts:["Organismen","Gezond leven"],coverage:"partial",widgets:["trees","birds","plants","breathe","mood","feelings"]}, {id:32,domain:"Systeem aarde",summary:"Leerlingen onderzoeken de aarde, weer, klimaat, water en samenhang en menselijke invloed in ecosystemen.",parts:["Aarde","Weer, klimaat en water","Ecosystemen"],coverage:"partial",widgets:["trees","birds","plants","topography"]} ] @@ -91,8 +91,8 @@ const CURRICULUM_PO_2026=Object.freeze({ id:"kunst-en-cultuur",icon:"🎨",title:"Kunst en cultuur",goalsRange:"35–37",page:92, description:"Artistiek creatief vermogen ontwikkelen, kunstzinnige uitingen maken en kunst en cultuur beleven en duiden.", goals:[ - {id:35,domain:"Artistiek creatief vermogen",summary:"Leerlingen gebruiken creatieve strategieën in een iteratief proces en geven betekenis aan eigen en andermans werk.",parts:["Creatieve maak- en denkstrategieën","Ontwikkelen van creatieve vermogens","Betekenis geven"],coverage:"support",widgets:["write","mind","media"]}, - {id:36,domain:"Maken en betekenis geven",summary:"Leerlingen verbeelden ideeën en gebruiken technieken, vaardigheden, materialen en vaktaal uit verschillende kunstdisciplines.",parts:["Verbeelden en creëren","Kunstzinnige technieken en vaardigheden"],coverage:"support",widgets:["write","media"]}, + {id:35,domain:"Artistiek creatief vermogen",summary:"Leerlingen gebruiken creatieve strategieën in een iteratief proces en geven betekenis aan eigen en andermans werk.",parts:["Creatieve maak- en denkstrategieën","Ontwikkelen van creatieve vermogens","Betekenis geven"],coverage:"support",widgets:["write","mind","media","birthday","avatar"]}, + {id:36,domain:"Maken en betekenis geven",summary:"Leerlingen verbeelden ideeën en gebruiken technieken, vaardigheden, materialen en vaktaal uit verschillende kunstdisciplines.",parts:["Verbeelden en creëren","Kunstzinnige technieken en vaardigheden"],coverage:"support",widgets:["write","media","birthday","avatar"]}, {id:37,domain:"Meemaken en betekenis geven",summary:"Leerlingen beleven kunst en cultuur actief en verbinden uitingen aan maker, tijd, plaats, functie en context.",parts:["Kunst en cultuur actief meemaken","Kunst en cultuur in context"],coverage:"open",widgets:[]} ] }, diff --git a/public/js/settings.js b/public/js/settings.js index b0b3315..f9e4f3d 100644 --- a/public/js/settings.js +++ b/public/js/settings.js @@ -148,13 +148,13 @@ function registerSettingsSection(sec){ ST_SECTIONS.push(sec); } }); /* ---- openen ---------------------------------------------------------------- */ - window.openSettings = function(section){ + window.openSettings = function(section, payload){ const msg = document.getElementById("settingsMsg"); msg.textContent = ""; msg.classList.remove("ok"); searchInp.value = ""; searchInp.placeholder = T("stSearchPh"); buildNav(); - goSection(section || (currentUser ? "account" : "view")); + goSection(section || (currentUser ? "account" : "view"), payload); /* beheerdata alvast op de achtergrond laden zodat zoeken op namen van gebruikers/klassen/scholen meteen wat oplevert (admin.js bewaakt zelf dat er maar één keer geladen wordt) */ diff --git a/public/js/wiki.js b/public/js/wiki.js index 80b892c..0f3a3be 100644 --- a/public/js/wiki.js +++ b/public/js/wiki.js @@ -3,7 +3,6 @@ (function(){ const root=document.getElementById("curriculumWiki"); - const wikiButton=document.getElementById("btnWiki"); let selectedArea="all"; let query=""; @@ -57,6 +56,7 @@ const grid=node("div","wiki-grid"); goals.forEach(goal=>{ const card=node("article","wiki-goal"); + card.id="wiki-goal-"+goal.id; const head=node("div","wiki-goal-head"); head.appendChild(node("span","wiki-goal-id",T("wikiGoal")+" "+goal.id)); head.appendChild(node("span","wiki-domain",goal.domain)); @@ -94,7 +94,7 @@ } function renderCurriculumWiki(payload){ - if(payload&&payload.area)selectedArea=payload.area; + if(payload&&payload.area){selectedArea=payload.area;query="";} root.replaceChildren(); const header=node("div","wiki-hero"); const heroText=node("div","wiki-hero-text"); @@ -169,6 +169,10 @@ drawGoals(holder,filters); }); drawGoals(holder,filters); + if(payload&&payload.goal)requestAnimationFrame(()=>{ + const target=document.getElementById("wiki-goal-"+payload.goal); + if(target){target.classList.add("wiki-goal-focus");target.scrollIntoView({block:"center"});} + }); const disclaimer=node("footer","wiki-disclaimer"); disclaimer.appendChild(node("strong",null,T("wikiCarefulTitle"))); @@ -198,5 +202,4 @@ } }); - wikiButton.addEventListener("click",()=>openSettings("curriculum")); })(); diff --git a/test/curriculum-wiki.test.js b/test/curriculum-wiki.test.js index 15827da..d387350 100644 --- a/test/curriculum-wiki.test.js +++ b/test/curriculum-wiki.test.js @@ -27,16 +27,48 @@ test('curriculumdataset bevat negen leergebieden en alle veertig kerndoelen',asy } }); -test('curriculumwiki is alleen zichtbaar voor alle volwassen rollen',async()=>{ +test('iedere kerndoelkoppeling verwijst naar een bestaande widget en de informatieknop linkt diep naar de wiki',async()=>{ + const [curriculumSource,board,settings,wiki,css]=await Promise.all([ + readFile('public/js/curriculum.js','utf8'), + readFile('public/js/board.js','utf8'), + readFile('public/js/settings.js','utf8'), + readFile('public/js/wiki.js','utf8'), + readFile('public/css/teach.css','utf8') + ]); + const context={}; + runInNewContext(curriculumSource+';globalThis.__curriculum=CURRICULUM_PO_2026',context); + const data=JSON.parse(JSON.stringify(context.__curriculum)); + const registered=new Set([...board.matchAll(/\{\s*id:"([^"]+)"\s*,\s*cat:/g)].map(match=>match[1])); + const linked=new Set(data.areas.flatMap(area=>area.goals.flatMap(goal=>goal.widgets))); + for(const widget of linked)assert.ok(registered.has(widget),widget+' is gekoppeld maar niet geregistreerd'); + for(const widget of ['letters','sentence','madd','fractions','dice','timer','schedule','trees','topography','noise','birthday','avatar']){ + assert.ok(linked.has(widget),widget+' mist een inhoudelijke kerndoelkoppeling'); + } + assert.match(board,/function curriculumGoalsForWidget/); + assert.match(board,/link\.href="#wiki-goal-"\+goal\.id/); + assert.match(board,/openSettings\("curriculum",\{area:area\.id,goal:goal\.id\}\)/); + assert.match(settings,/window\.openSettings = function\(section, payload\)/); + assert.match(wiki,/card\.id="wiki-goal-"\+goal\.id/); + assert.match(wiki,/target\.scrollIntoView\(\{block:"center"\}\)/); + assert.match(css,/\.winfo\[hidden\]\{display:none\}/); + assert.match(css,/\.widget-curriculum-modal/); +}); + +test('curriculumwiki en widgetinformatie zijn alleen zichtbaar voor volwassen rollen',async()=>{ assert.deepEqual(PERMISSIONS['curriculum.view'],['super','admin','teacher','parent']); assert.ok(!PERMISSIONS['curriculum.view'].includes('pupil')); - const [client,wiki]=await Promise.all([ + const [client,wiki,board,html]=await Promise.all([ readFile('public/js/permissions.js','utf8'), - readFile('public/js/wiki.js','utf8') + readFile('public/js/wiki.js','utf8'), + readFile('public/js/board.js','utf8'), + readFile('public/index.html','utf8') ]); assert.match(client,/'curriculum\.view':\s*\['super', 'admin', 'teacher', 'parent'\]/); assert.match(wiki,/visible:\(\)=>!!currentUser&&can\("curriculum\.view"\)/); - assert.match(wiki,/wikiButton\.addEventListener\("click",\(\)=>openSettings\("curriculum"\)\)/); + assert.match(board,/infoBtn\.hidden=!\(currentUser&&can\("curriculum\.view"\)\)/); + assert.match(board,/if\(!currentUser\|\|!can\("curriculum\.view"\)\)return/); + assert.doesNotMatch(html,/id="btnWiki"/); + assert.doesNotMatch(wiki,/wikiButton/); assert.doesNotMatch(wiki,/innerHTML\s*=/); }); @@ -51,7 +83,7 @@ test('curriculumwiki is geladen, doorzoekbaar, responsief en tweetalig bediend', assert.ok(html.indexOf('js/settings.js')]*display:none/); + assert.doesNotMatch(html,/id="btnWiki"/); assert.match(html,/data-section="curriculum"/); assert.match(wiki,/input\.type="search"/); assert.match(wiki,/dataset\.area/); @@ -60,6 +92,6 @@ test('curriculumwiki is geladen, doorzoekbaar, responsief en tweetalig bediend', } assert.match(css,/\.wiki-grid\{[^}]*grid-template-columns:repeat\(2/); assert.match(css,/@media \(max-width:720px\)[\s\S]*?\.wiki-grid\{grid-template-columns:1fr\}/); - assert.ok(core.includes('document.getElementById("btnWiki").style.display = can("curriculum.view")')); + assert.doesNotMatch(core,/btnWiki/); assert.ok(core.includes('const VERSION = "'+version.trim()+'"')); });