Files

103 lines
4.7 KiB
JavaScript
Raw Permalink Normal View History

// Functional DOM test of the bulk-delete checkbox UI.
// Loads the REAL index.html into jsdom, stubs fetch, and drives the actual
// render + selection functions. Proves: checkboxes render, select-all works,
// the bar appears only when a selection exists, and the payload is right.
const fs = require('fs');
const path = require('path');
const { JSDOM } = require(path.join('/tmp/uitest/node_modules/jsdom'));
const html = fs.readFileSync('/opt/gemastik18-final/panel/static/index.html', 'utf8');
// Fake 4 teams, mirroring the real API shape.
const FAKE_TEAMS = {
teams: [
{ index: 1, label: 'Max', status: 'running', ports: {}, domain: 'max.attackdefense.imrnes.team' },
{ index: 2, label: 'Aryma', status: 'running', ports: {}, domain: 'aryma.attackdefense.imrnes.team' },
{ index: 3, label: 'Ken', status: 'running', ports: {}, domain: 'ken.attackdefense.imrnes.team' },
],
};
const calls = [];
const dom = new JSDOM(html, { runScripts: 'dangerously', url: 'https://attackdefense.imrnes.team/' });
dom.window.fetch = async (url, opts) => {
calls.push({ url: String(url), method: (opts && opts.method) || 'GET', body: opts && opts.body });
const u = String(url);
if (u.endsWith('/api/teams') && !(opts && opts.method)) {
return { ok: true, json: async () => FAKE_TEAMS };
}
if (u.includes('/api/teams/bulk-delete/') && !(opts && opts.method)) {
return { ok: true, json: async () => ({ state: 'done', done: [{ team: 2 }], errors: {} }) };
}
return { ok: true, json: async () => ({ ok: true }) };
};
dom.window.confirm = () => true;
const w = dom.window, d = w.document;
// jsdom keeps top-level `let` out of window, so read it through eval in the page.
const sel_set = () => w.eval('TEAM_SELECTED');
const call = (fn) => w.eval(`${fn}()`);
let pass = 0, fail = 0;
const ok = (name, cond, extra) => {
if (cond) { pass++; console.log(' PASS ' + name); }
else { fail++; console.log(' FAIL ' + name + (extra ? ' -> ' + extra : '')); }
};
(async () => {
// Drive the real page init: it calls loadTeams on DOMContentLoaded.
await w.loadTeams();
const boxes = () => Array.from(d.querySelectorAll('input[id^="teamSel"]'));
ok('one checkbox per team rendered', boxes().length === 3, 'got ' + boxes().length);
ok('checkbox id encodes the team index', boxes()[0] && boxes()[0].id === 'teamSel1',
boxes()[0] && boxes()[0].id);
ok('checkbox onchange passes the index', boxes()[0] && /toggleTeamSelect\(1,/.test(boxes()[0].getAttribute('onchange') || ''),
boxes()[0] && boxes()[0].getAttribute('onchange'));
// Bar hidden with no selection.
const bar = d.getElementById('bulkDelBar');
ok('bar exists', !!bar);
ok('bar hidden when nothing selected', bar && bar.style.display === 'none',
bar && bar.style.display);
// Tick team 2 -> bar appears, count updates.
boxes()[1].checked = true;
boxes()[1].dispatchEvent(new w.Event('change', { bubbles: true }));
ok('bar shows after first tick', bar.style.display !== 'none', bar.style.display);
ok('TEAM_SELECTED holds team2', sel_set().has(2) && sel_set().size === 1,
'size=' + sel_set().size);
ok('bar mentions 1 team', /1\b/.test(bar.textContent), JSON.stringify(bar.textContent.trim().slice(0, 60)));
// Select all -> every team ticked. The button drives selectAllTeams(true).
const allBtn = Array.from(d.querySelectorAll('button'))
.find(b => /selectAllTeams\(true\)/.test(b.getAttribute('onclick') || ''));
ok('select-all button exists', !!allBtn);
allBtn.click();
ok('select-all ticks every box', boxes().every(b => b.checked));
ok('TEAM_SELECTED has all 3', sel_set().size === 3, 'size=' + sel_set().size);
ok('bar shows 3 teams', /3/.test(bar.textContent));
// Bulk delete issues ONE POST with the selected indices.
calls.length = 0;
const goBtn = Array.from(d.querySelectorAll('#bulkDelBar button'))
.find(b => /bulkDeleteTeams\(\)/.test(b.getAttribute('onclick') || ''));
ok('bulk delete button appears in the bar', !!goBtn);
goBtn.click();
await new Promise(r => setTimeout(r, 400));
const post = calls.find(c => c.url.includes('bulk-delete') && c.method === 'POST');
ok('bulk delete POSTs once', !!post, JSON.stringify(calls.map(c => c.method + ' ' + c.url)));
if (post) {
const payload = JSON.parse(post.body);
ok('payload carries all 3 indices', JSON.stringify(payload.indices) === '[1,2,3]',
JSON.stringify(payload));
}
// Clear selection hides the bar again.
call('clearTeamSelection');
ok('clear empties selection', sel_set().size === 0);
ok('bar hidden after clear', bar.style.display === 'none', bar.style.display);
ok('checkboxes cleared too', boxes().every(b => !b.checked));
console.log(`\n ${pass} passed, ${fail} failed`);
process.exit(fail === 0 ? 0 : 1);
})();