// Rendert de echte install-modal met Alpine in een DOM en klikt erop. // Vangt bindingsfouten die je met losse logica niet ziet: :disabled="0" // schakelt een knop juist UIT, want Alpine haalt een boolean-attribuut // alleen weg bij null, undefined of false. const fs = require('fs'), path = require('path'); // jsdom en Alpine zijn geen onderdeel van dit project; zonder die twee slaat // deze test zichzelf over (afsluitcode 2). Installeren kan met: // cd tests/js && npm install jsdom alpinejs let JSDOM, alpinePad; try { ({ JSDOM } = require('jsdom')); alpinePad = require.resolve('alpinejs/dist/cdn.js'); } catch (e) { console.log('overgeslagen: jsdom of alpinejs ontbreekt (npm install in tests/js)'); process.exit(2); } const WORTEL = path.resolve(__dirname, '..', '..'); const html = fs.readFileSync(WORTEL + '/server-up/templates/index.html', 'utf8'); const dom = new JSDOM('', { runScripts: 'dangerously', pretendToBeVisual: true, url: 'http://localhost/' }); const w = dom.window; w.fetch = async () => ({ ok: true, json: async () => ({ ok: true }) }); const start = html.indexOf('function app() {'); w.eval(html.slice(start, html.indexOf('', start))); // De component vóór Alpine klaarzetten: erna is het een proxy die dit weigert. const echt = w.app; w.app = function () { const c = echt(); c.t = k => k; c.bpFieldVisible = () => true; c.toast = () => {}; c.rpc = async () => ({ ok: true, preview_files: { 'compose.yaml': 'services: {}' }, env_preview: '' }); c.connectTargets = () => []; c.pickIp = () => {}; c.netList = []; c.sharedNetwork = 'serverup'; c.modal = 'install'; c.install = { format: 'boilerplate', loading: false, stack: 'test', repo_id: 'r', instance: 'test', values: { service_name: 'test', port: 8080 }, fields: [ {name:'service_name',title:'Naam',step:'basis',type:'str',group:'Algemeen',required:true,advanced:false}, {name:'port',title:'Poort',step:'basis',type:'int',group:'Algemeen',required:true,advanced:false}, {name:'db_password',title:'Wachtwoord',step:'geheimen',type:'str',group:'Algemeen',required:true,secret:true,advanced:false}, ], step: 0, preview_files: {}, composeText: '', envText: '', editing: false, tab: 'compose', mode: 'install', connect_apps: true, network: '', ip: '', envVars: [], images: [], envOverrides: {}, imageTags: {}, }; return c; }; const m = html.match(/
\n/); w.document.body.innerHTML = `
${m[0]}
`; w.eval(fs.readFileSync(alpinePad, 'utf8')); const wacht = ms => new Promise(r => setTimeout(r, ms)); (async () => { await wacht(100); const comp = w.Alpine.$data(w.document.querySelector('[x-data]')); const knoppen = [...w.document.querySelectorAll('.modal-foot button')]; console.log('knoppen in de voet:'); knoppen.forEach(b => console.log( ` "${b.textContent.trim().slice(0,14)}" zichtbaar=${b.style.display !== 'none'} disabled=${b.disabled}`)); let fouten = 0; const check = (naam, waar) => { console.log((waar ? ' ✓ ' : ' ✖ ') + naam); if (!waar) fouten++; }; check('velden worden gerenderd', w.document.querySelectorAll('.modal-body input').length > 0); check('niets ontbreekt op stap 1', comp.stepMissing().length === 0); const volgende = knoppen.find(b => b.textContent.includes('next')); check('Volgende staat in de DOM', !!volgende); if (!volgende) process.exit(1); // Alpine haalt een boolean-attribuut alleen weg bij null, undefined of false. // Een lege lijst gaf dus .length === 0, en dat zette disabled juist AAN. check('Volgende is niet uitgeschakeld met alles ingevuld', volgende.disabled === false); volgende.click(); await wacht(120); check('klikken gaat naar de volgende stap', comp.install.step === 1); // Leeg verplicht veld hoort wél te blokkeren. comp.install.step = 2; await wacht(60); const geheimen = comp.wizardSteps()[2]; check('stap 3 is de geheimenstap', geheimen && geheimen.id === 'geheimen'); check('leeg wachtwoord blokkeert', comp.stepMissing().length === 1); await wacht(60); check('knop is dan uitgeschakeld', volgende.disabled === true); comp.install.values.db_password = 'ietsGeheims'; await wacht(80); check('knop weer bruikbaar na invullen', volgende.disabled === false); console.log(fouten === 0 ? '\nAlle DOM-controles geslaagd' : `\n${fouten} FOUTEN`); process.exit(fouten ? 1 : 0); })();