fix: onzichtbare knopteksten, lege klassenlijst en correcte klas-schoolregels (v0.3.93-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 32s
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 32s
- Onzichtbare knoppen opgelost: .tbtn houdt standaard witte tekst (wit-op-blauw), maar in het beheer staat hij op een lichte achtergrond -> tekst was wit-op-wit. Nu expliciet donkere tekst; de primaire accentknop houdt bewust witte tekst - Systeemmanager kreeg standaard "alle scholen" waardoor klassen leeg leken en niet toe te voegen waren; er wordt nu meteen een concrete school gekozen (klassen/gebruikers direct gevuld). Contextbalk laat wisselen; "alle scholen" blijft een bewuste keuze - Lege school toont nu een duidelijke uitleg dat bestaande klassen bij een andere school kunnen horen, met verwijzing naar de schoolkeuze - Correcte gegevensregels: classes.school_id is NOT NULL met ON DELETE CASCADE, dus een klas kan niet "los" bestaan (die eerdere aanname is teruggedraaid). Een klas is nu te VERPLAATSEN naar een andere school (PATCH weigert null/ongeldig); een verwijderde school neemt zijn klassen mee (cascade) - dat verklaart verdwenen klassen - Bij een leerling naar een andere school koppelen blijft de klas behouden als die al bij de doelschool hoort - Verificatie uitgebreid met een echte kleurcontrast-scan (vangt wit-op-wit) over alle secties + de default-school-flow; alle 94 servertests en 8 Playwright-suites groen Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
This commit is contained in:
parent
aee1c59fbd
commit
c86abcf794
6 changed files with 76 additions and 73 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.3.92-beta
|
0.3.93-beta
|
||||||
|
|
|
||||||
|
|
@ -1914,9 +1914,13 @@ body.dark .world-fact{background:#1d3b2a;color:#a9e5b7}body.dark .world-fact .wo
|
||||||
}
|
}
|
||||||
#settingsModal .am-btn, #settingsModal .am-copy, #settingsModal .tbtn{
|
#settingsModal .am-btn, #settingsModal .am-copy, #settingsModal .tbtn{
|
||||||
border:1px solid var(--line); border-radius:var(--radius-s); background:var(--surface);
|
border:1px solid var(--line); border-radius:var(--radius-s); background:var(--surface);
|
||||||
|
color:var(--ink); /* .tbtn is standaard wit-op-blauw; hier wit-op-wit dus overschrijven */
|
||||||
font-size:12.5px; font-weight:550; box-shadow:none; padding:0 12px;
|
font-size:12.5px; font-weight:550; box-shadow:none; padding:0 12px;
|
||||||
}
|
}
|
||||||
#settingsModal .am-btn:hover, #settingsModal .tbtn:hover{background:var(--surface-2);}
|
#settingsModal .am-btn:hover, #settingsModal .tbtn:hover{background:var(--surface-2);}
|
||||||
|
/* de primaire knop (accent) houdt bewust witte tekst */
|
||||||
|
#settingsModal .am-btn.am-primary, #settingsModal .tbtn.am-primary{color:#fff;}
|
||||||
|
#settingsModal .tbtn:disabled{opacity:.5;}
|
||||||
#settingsModal .am-btn.am-danger{background:transparent; border:1px solid var(--line); color:var(--red); font-weight:600;}
|
#settingsModal .am-btn.am-danger{background:transparent; border:1px solid var(--line); color:var(--red); font-weight:600;}
|
||||||
#settingsModal .am-btn.am-danger.armed{background:var(--red); border-color:var(--red); color:#fff;}
|
#settingsModal .am-btn.am-danger.armed{background:var(--red); border-color:var(--red); color:#fff;}
|
||||||
#settingsModal .am-edit{border:1px solid transparent; border-radius:var(--radius-s); font-size:12.5px; opacity:.75;}
|
#settingsModal .am-edit{border:1px solid transparent; border-radius:var(--radius-s); font-size:12.5px; opacity:.75;}
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,10 @@
|
||||||
if(force) loadPromise = null;
|
if(force) loadPromise = null;
|
||||||
if(!loadPromise){
|
if(!loadPromise){
|
||||||
loadPromise = (async ()=>{
|
loadPromise = (async ()=>{
|
||||||
selSchool = currentUser.role==="super" ? (selSchool ?? "") : currentUser.schoolId;
|
/* super: selSchool null = nog niet gekozen (reload() zet dan de eerste
|
||||||
|
school als default zodat klassen/gebruikers meteen een concrete
|
||||||
|
school tonen); "" = bewust "alle scholen". Andere rollen: eigen school. */
|
||||||
|
if(currentUser.role!=="super") selSchool = currentUser.schoolId;
|
||||||
await reload();
|
await reload();
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
@ -252,11 +255,17 @@
|
||||||
if(!currentUser) resetResults = null;
|
if(!currentUser) resetResults = null;
|
||||||
loadPromise = null;
|
loadPromise = null;
|
||||||
selectedUserKey = null;
|
selectedUserKey = null;
|
||||||
|
selSchool = null; /* nieuwe sessie: laat reload() opnieuw defaulten */
|
||||||
});
|
});
|
||||||
|
|
||||||
async function reload(){
|
async function reload(){
|
||||||
try{
|
try{
|
||||||
if(currentUser.role==="super") SCHOOLS = await api("/schools");
|
if(currentUser.role==="super"){
|
||||||
|
SCHOOLS = await api("/schools");
|
||||||
|
/* eerste bezoek: kies meteen een concrete school i.p.v. het verwarrende
|
||||||
|
"alle scholen" (klassen en per-school-lijsten zijn dan direct gevuld) */
|
||||||
|
if(selSchool == null) selSchool = SCHOOLS.length ? String(SCHOOLS[0].id) : "";
|
||||||
|
}
|
||||||
const q = currentUser.role==="super" && selSchool ? "?school="+selSchool : "";
|
const q = currentUser.role==="super" && selSchool ? "?school="+selSchool : "";
|
||||||
USERS = (await api("/admin/users"+q)).users;
|
USERS = (await api("/admin/users"+q)).users;
|
||||||
/* het Systeem-tabblad toont schoolbeheerders van álle scholen, ook als
|
/* het Systeem-tabblad toont schoolbeheerders van álle scholen, ook als
|
||||||
|
|
@ -266,7 +275,7 @@
|
||||||
SYS_ADMINS = q ? ((await api("/admin/users")).users || []).filter(u=>u.role==="admin")
|
SYS_ADMINS = q ? ((await api("/admin/users")).users || []).filter(u=>u.role==="admin")
|
||||||
: USERS.filter(u=>u.role==="admin");
|
: USERS.filter(u=>u.role==="admin");
|
||||||
}
|
}
|
||||||
const cq = currentUser.role==="super" ? (selSchool ? "?school="+selSchool : "?school=none") : "";
|
const cq = currentUser.role==="super" && selSchool ? "?school="+selSchool : "";
|
||||||
CLASSES = (await api("/admin/classes"+cq)).classes || [];
|
CLASSES = (await api("/admin/classes"+cq)).classes || [];
|
||||||
ASSIGNMENTS = {};
|
ASSIGNMENTS = {};
|
||||||
if(CLASSES.length){
|
if(CLASSES.length){
|
||||||
|
|
@ -867,37 +876,35 @@
|
||||||
if(creatableRoles().includes("super")) panel.appendChild(addForm("super"));
|
if(creatableRoles().includes("super")) panel.appendChild(addForm("super"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* klassen (admin/super binnen een school); de systeemmanager ziet zonder
|
/* klassen (admin/super binnen de gekozen school). Een klas hoort altijd bij
|
||||||
gekozen school de losse klassen (school verwijderd) om ze terug te koppelen */
|
een school; de systeemmanager kan een klas naar een andere school
|
||||||
|
verplaatsen. Zonder gekozen school ("alle scholen") is er geen concrete
|
||||||
|
school om klassen bij te tonen of aan te maken. */
|
||||||
function renderKlassenPanel(panel){
|
function renderKlassenPanel(panel){
|
||||||
const looseView = currentUser.role==="super" && !selSchool;
|
if(currentUser.role==="super" && !selSchool){
|
||||||
const canResetPw = can("schools.resetPasswords");
|
panel.appendChild(h("div","guestnote", T("amPickSchoolFirst")));
|
||||||
if(looseView){
|
|
||||||
if(!CLASSES.length){
|
|
||||||
panel.appendChild(h("div","guestnote", T("amPickSchoolHint")));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
panel.appendChild(h("div","guestnote", T("amLooseClassesHint")));
|
const canResetPw = can("schools.resetPasswords");
|
||||||
}
|
const otherSchools = SCHOOLS.filter(sc=>String(sc.id)!==String(selSchool));
|
||||||
CLASSES.forEach(c=>{
|
CLASSES.forEach(c=>{
|
||||||
const pupilCount = USERS.filter(u=>u.role==="pupil" && Number(u.classId)===Number(c.id)).length;
|
const pupilCount = USERS.filter(u=>u.role==="pupil" && Number(u.classId)===Number(c.id)).length;
|
||||||
let edit = null;
|
let edit = null;
|
||||||
if(canResetPw){
|
if(canResetPw){
|
||||||
const tools = h("div","am-tools");
|
const tools = h("div","am-tools");
|
||||||
/* systeemmanager: klas aan een school (terug)koppelen */
|
/* systeemmanager: klas naar een andere school verplaatsen */
|
||||||
if(currentUser.role==="super"){
|
if(currentUser.role==="super" && otherSchools.length){
|
||||||
const ss = h("select","am-sel");
|
const ss = h("select","am-sel");
|
||||||
ss.appendChild(new Option(T("amNoSchool"), ""));
|
ss.appendChild(new Option(T("amMoveToSchool"), ""));
|
||||||
SCHOOLS.forEach(sc=>ss.appendChild(new Option(sc.name, sc.id)));
|
otherSchools.forEach(sc=>ss.appendChild(new Option(sc.name, sc.id)));
|
||||||
ss.value = looseView ? "" : (selSchool || "");
|
|
||||||
ss.addEventListener("change", async ()=>{
|
ss.addEventListener("change", async ()=>{
|
||||||
try{ await api("/admin/classes/"+c.id, {method:"PATCH", body:{ schoolId: ss.value ? +ss.value : null }}); reload(); }
|
if(!ss.value) return;
|
||||||
|
try{ await api("/admin/classes/"+c.id, {method:"PATCH", body:{ schoolId: +ss.value }}); reload(); }
|
||||||
catch(e){ msg(e.message); }
|
catch(e){ msg(e.message); }
|
||||||
});
|
});
|
||||||
tools.appendChild(field("amFieldSchool", ss));
|
tools.appendChild(field("amFieldMoveSchool", ss));
|
||||||
}
|
}
|
||||||
const acts = h("div","am-actions");
|
const acts = h("div","am-actions");
|
||||||
if(!looseView){
|
|
||||||
acts.appendChild(dangerBtn(T("amResetClass"), "amConfirmReset", async ()=>{
|
acts.appendChild(dangerBtn(T("amResetClass"), "amConfirmReset", async ()=>{
|
||||||
try{
|
try{
|
||||||
const r = await api("/admin/classes/"+c.id+"/reset-passwords", {method:"POST"});
|
const r = await api("/admin/classes/"+c.id+"/reset-passwords", {method:"POST"});
|
||||||
|
|
@ -906,7 +913,6 @@
|
||||||
render();
|
render();
|
||||||
}catch(e){ msg(e.message); }
|
}catch(e){ msg(e.message); }
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
acts.appendChild(dangerBtn(T("amDelete"), "amDeleteClassWarn", async ()=>{
|
acts.appendChild(dangerBtn(T("amDelete"), "amDeleteClassWarn", async ()=>{
|
||||||
try{ await api("/admin/classes/"+c.id, {method:"DELETE"}); reload(); }
|
try{ await api("/admin/classes/"+c.id, {method:"DELETE"}); reload(); }
|
||||||
catch(e){ msg(e.message); }
|
catch(e){ msg(e.message); }
|
||||||
|
|
@ -920,7 +926,11 @@
|
||||||
edit,
|
edit,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
if(looseView) return;
|
/* lege school: maak duidelijk dat er (nog) geen klassen zijn en dat
|
||||||
|
bestaande klassen bij een andere school kunnen horen */
|
||||||
|
if(!CLASSES.length){
|
||||||
|
panel.appendChild(h("div","guestnote", T("amNoClassesHere")));
|
||||||
|
}
|
||||||
if(canResetPw){
|
if(canResetPw){
|
||||||
const f = h("div","am-add");
|
const f = h("div","am-add");
|
||||||
const inp = h("input","am-inp");
|
const inp = h("input","am-inp");
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
/* version — shown until /api/version resolves (or if the fetch fails, e.g. offline).
|
/* 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. */
|
Kept in sync by hand with the VERSION file at the repo root on every release. */
|
||||||
const VERSION = "0.3.92-beta";
|
const VERSION = "0.3.93-beta";
|
||||||
(function(){
|
(function(){
|
||||||
const tag = document.getElementById("verTag");
|
const tag = document.getElementById("verTag");
|
||||||
tag.textContent = "v"+VERSION;
|
tag.textContent = "v"+VERSION;
|
||||||
|
|
@ -280,7 +280,8 @@ const I18N = {
|
||||||
amResultPw:"Nieuw wachtwoord", amResultCode:"Nieuwe koppelcode",
|
amResultPw:"Nieuw wachtwoord", amResultCode:"Nieuwe koppelcode",
|
||||||
amColStatus:"Status", amNeverLoggedIn:"nog niet ingelogd", amFieldSchool:"School", amStatusActive:"actief",
|
amColStatus:"Status", amNeverLoggedIn:"nog niet ingelogd", amFieldSchool:"School", amStatusActive:"actief",
|
||||||
amNoSchool:"— geen school —", amNoSchoolGroup:"Zonder school", amShowMore:"meer tonen",
|
amNoSchool:"— geen school —", amNoSchoolGroup:"Zonder school", amShowMore:"meer tonen",
|
||||||
amLooseClassesHint:"Deze klassen horen bij geen enkele school. Koppel ze via Bewerken.",
|
amMoveToSchool:"— verplaats naar school… —", amFieldMoveSchool:"Verplaats naar", amPickSchoolFirst:"Kies hierboven eerst een school om de klassen te zien of aan te maken.",
|
||||||
|
amNoClassesHere:"Deze school heeft nog geen klassen. Bestaande klassen kunnen bij een andere school horen — kies die school hierboven, of maak hieronder een nieuwe klas.",
|
||||||
amDetailEmpty:"Selecteer links een gebruiker om te bewerken, of maak een nieuwe aan.",
|
amDetailEmpty:"Selecteer links een gebruiker om te bewerken, of maak een nieuwe aan.",
|
||||||
amNewUser:"Nieuwe gebruiker", amRequestOne:"Aanmelding", amReqNew:"nieuw",
|
amNewUser:"Nieuwe gebruiker", amRequestOne:"Aanmelding", amReqNew:"nieuw",
|
||||||
amParents:"Ouders",
|
amParents:"Ouders",
|
||||||
|
|
@ -576,7 +577,8 @@ const I18N = {
|
||||||
amResultPw:"New password", amResultCode:"New link code",
|
amResultPw:"New password", amResultCode:"New link code",
|
||||||
amColStatus:"Status", amNeverLoggedIn:"not signed in yet", amFieldSchool:"School", amStatusActive:"active",
|
amColStatus:"Status", amNeverLoggedIn:"not signed in yet", amFieldSchool:"School", amStatusActive:"active",
|
||||||
amNoSchool:"— no school —", amNoSchoolGroup:"No school", amShowMore:"show more",
|
amNoSchool:"— no school —", amNoSchoolGroup:"No school", amShowMore:"show more",
|
||||||
amLooseClassesHint:"These classes belong to no school. Attach them via Edit.",
|
amMoveToSchool:"— move to school… —", amFieldMoveSchool:"Move to", amPickSchoolFirst:"First pick a school above to see or create its classes.",
|
||||||
|
amNoClassesHere:"This school has no classes yet. Existing classes may belong to another school — pick that school above, or create a new class below.",
|
||||||
amDetailEmpty:"Select a user on the left to edit, or create a new one.",
|
amDetailEmpty:"Select a user on the left to edit, or create a new one.",
|
||||||
amNewUser:"New user", amRequestOne:"Enrolment", amReqNew:"new",
|
amNewUser:"New user", amRequestOne:"Enrolment", amReqNew:"new",
|
||||||
amParents:"Parents",
|
amParents:"Parents",
|
||||||
|
|
|
||||||
20
src/api.js
20
src/api.js
|
|
@ -338,30 +338,26 @@ export default async function api(app) {
|
||||||
need(req, reply, PERMISSIONS['classes.view']);
|
need(req, reply, PERMISSIONS['classes.view']);
|
||||||
const schoolId = req.user.allRoles.includes('super') ? req.query.school : req.user.school_id;
|
const schoolId = req.user.allRoles.includes('super') ? req.query.school : req.user.school_id;
|
||||||
if (!schoolId) return { classes: [] };
|
if (!schoolId) return { classes: [] };
|
||||||
// 'none' (alleen super): losse klassen (school ooit verwijderd) - anders
|
|
||||||
// zijn die nergens zichtbaar en dus ook niet terug te koppelen
|
|
||||||
const loose = schoolId === 'none' && req.user.allRoles.includes('super');
|
|
||||||
const r = await pool.query(
|
const r = await pool.query(
|
||||||
`SELECT c.id, c.name,
|
`SELECT c.id, c.name,
|
||||||
COALESCE(json_agg(ct.user_id) FILTER (WHERE ct.user_id IS NOT NULL), '[]') AS teacher_ids
|
COALESCE(json_agg(ct.user_id) FILTER (WHERE ct.user_id IS NOT NULL), '[]') AS teacher_ids
|
||||||
FROM classes c LEFT JOIN class_teachers ct ON ct.class_id = c.id
|
FROM classes c LEFT JOIN class_teachers ct ON ct.class_id = c.id
|
||||||
WHERE ${loose ? 'c.school_id IS NULL' : 'c.school_id = $1'} GROUP BY c.id ORDER BY c.name`,
|
WHERE c.school_id = $1 GROUP BY c.id ORDER BY c.name`, [schoolId]);
|
||||||
loose ? [] : [schoolId]);
|
|
||||||
return { classes: r.rows.map((c) => ({ id: Number(c.id), name: c.name, teacherIds: c.teacher_ids.map(Number) })) };
|
return { classes: r.rows.map((c) => ({ id: Number(c.id), name: c.name, teacherIds: c.teacher_ids.map(Number) })) };
|
||||||
});
|
});
|
||||||
// klas aan een (andere) school koppelen of losmaken - alleen systeemmanager;
|
// een klas naar een ANDERE school verplaatsen - alleen systeemmanager.
|
||||||
// herstelinstrument voor klassen die hun school kwijt zijn
|
// Een klas hoort altijd bij een school (classes.school_id is NOT NULL);
|
||||||
|
// losmaken kan dus niet - er moet een geldige doelschool zijn.
|
||||||
app.patch('/admin/classes/:id', async (req, reply) => {
|
app.patch('/admin/classes/:id', async (req, reply) => {
|
||||||
need(req, reply, PERMISSIONS['schools.manage']);
|
need(req, reply, PERMISSIONS['schools.manage']);
|
||||||
const c = (await pool.query('SELECT * FROM classes WHERE id = $1', [req.params.id])).rows[0];
|
const c = (await pool.query('SELECT * FROM classes WHERE id = $1', [req.params.id])).rows[0];
|
||||||
if (!c) return fail(reply, 404, 'klas onbekend');
|
if (!c) return fail(reply, 404, 'klas onbekend');
|
||||||
const b = req.body ?? {};
|
const raw = req.body?.schoolId;
|
||||||
if (b.schoolId === undefined) return fail(reply, 400, 'school verplicht');
|
if (raw == null) return fail(reply, 400, 'school verplicht'); // een klas hoort altijd bij een school
|
||||||
const target = b.schoolId === null ? null : Number(b.schoolId);
|
const target = Number(raw);
|
||||||
if (target !== null) {
|
if (!Number.isInteger(target) || target <= 0) return fail(reply, 400, 'ongeldige school');
|
||||||
const school = (await pool.query('SELECT 1 FROM schools WHERE id = $1', [target])).rows[0];
|
const school = (await pool.query('SELECT 1 FROM schools WHERE id = $1', [target])).rows[0];
|
||||||
if (!school) return fail(reply, 404, 'school onbekend');
|
if (!school) return fail(reply, 404, 'school onbekend');
|
||||||
}
|
|
||||||
await pool.query('UPDATE classes SET school_id = $1 WHERE id = $2', [target, c.id]);
|
await pool.query('UPDATE classes SET school_id = $1 WHERE id = $2', [target, c.id]);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -172,34 +172,25 @@ test('school koppelen meldt een naamconflict netjes als 409', async () => {
|
||||||
await app.close();
|
await app.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('systeemmanager koppelt een losse klas terug aan een school', async () => {
|
test('systeemmanager verplaatst een klas naar een andere school; losmaken kan niet', async () => {
|
||||||
const { app, calls } = await makeAppWithSchool(superUser, null);
|
const { app, calls } = await makeAppWithSchool(superUser, null);
|
||||||
const origQuery = app.pg.query.bind(app.pg);
|
const res = await app.inject({ method: 'PATCH', url: '/api/admin/classes/100', cookies,
|
||||||
app.pg.query = async (sql, params = []) => {
|
payload: { schoolId: 3 } });
|
||||||
if (sql.startsWith('SELECT * FROM classes WHERE id')) { calls.push({ sql, params }); return { rows: [{ id: 9, name: 'Groep 4', school_id: null }] }; }
|
|
||||||
return origQuery(sql, params);
|
|
||||||
};
|
|
||||||
const res = await app.inject({ method: 'PATCH', url: '/api/admin/classes/9', cookies,
|
|
||||||
payload: { schoolId: 2 } });
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
const upd = calls.find((c) => c.sql.startsWith('UPDATE classes SET school_id'));
|
const upd = calls.find((c) => c.sql.startsWith('UPDATE classes SET school_id'));
|
||||||
assert.deepEqual(upd.params, [2, 9]);
|
assert.deepEqual(upd.params, [3, 100]);
|
||||||
|
// een klas hoort altijd bij een school: geen/null wordt geweigerd
|
||||||
|
const { app: app2 } = await makeAppWithSchool(superUser, null);
|
||||||
|
const res2 = await app2.inject({ method: 'PATCH', url: '/api/admin/classes/100', cookies,
|
||||||
|
payload: { schoolId: null } });
|
||||||
|
assert.equal(res2.statusCode, 400);
|
||||||
// schoolbeheerder mag dit niet (schools.manage = super)
|
// schoolbeheerder mag dit niet (schools.manage = super)
|
||||||
const { app: app2, calls: calls2 } = await makeAppWithSchool(admin, null);
|
const { app: app3, calls: calls3 } = await makeAppWithSchool(admin, null);
|
||||||
const res2 = await app2.inject({ method: 'PATCH', url: '/api/admin/classes/9', cookies,
|
const res3 = await app3.inject({ method: 'PATCH', url: '/api/admin/classes/100', cookies,
|
||||||
payload: { schoolId: 2 } });
|
payload: { schoolId: 3 } });
|
||||||
assert.equal(res2.statusCode, 403);
|
assert.equal(res3.statusCode, 403);
|
||||||
assert.ok(!calls2.some((c) => c.sql.startsWith('UPDATE classes SET school_id')));
|
assert.ok(!calls3.some((c) => c.sql.startsWith('UPDATE classes SET school_id')));
|
||||||
await app.close(); await app2.close();
|
await app.close(); await app2.close(); await app3.close();
|
||||||
});
|
|
||||||
|
|
||||||
test('losse klassen zijn opvraagbaar met school=none (alleen super)', async () => {
|
|
||||||
const { app, calls } = await makeApp(superUser);
|
|
||||||
const res = await app.inject({ url: '/api/admin/classes?school=none', cookies });
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
const q = calls.find((c) => c.sql.includes('FROM classes c'));
|
|
||||||
assert.ok(q.sql.includes('school_id IS NULL'), 'filtert op klassen zonder school');
|
|
||||||
await app.close();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('hernoemen weigert een lege of te lange naam', async () => {
|
test('hernoemen weigert een lege of te lange naam', async () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue