Unify Settings Layouts and Add Coverage
This commit is contained in:
@@ -38,12 +38,22 @@ function browser({onlyLoader = false, withLoader = false, role = 'user', usernam
|
||||
setInterval: (fn, ms) => timeout(fn, ms, true), clearInterval: id => timers.delete(id),
|
||||
requestAnimationFrame: fn => timeout(fn, -1), cancelAnimationFrame: id => timers.delete(id),
|
||||
location: {origin: 'https://sak.local', replace: path => redirects.push(path)}};
|
||||
const context = vm.createContext({window, document: {getElementById(id) {
|
||||
return nodes[id] ||= {textContent: '', value: '', checked: false, dataset: {}, classList: {toggle() {}},
|
||||
setAttribute(k, v) { this[k] = v; },
|
||||
getBoundingClientRect: () => ({width: 100, height: 100}),
|
||||
addEventListener(k, fn) { this[k] = fn; }};
|
||||
}}, Terminal, FitAddon: {FitAddon: class {
|
||||
class Element {
|
||||
constructor(tagName = 'div') {
|
||||
this.tagName = tagName.toUpperCase(); this.children = []; this._text = '';
|
||||
this.value = ''; this.checked = false; this.dataset = {}; this.classList = {toggle() {}};
|
||||
}
|
||||
get textContent() { return this._text + this.children.map(child => child.textContent).join(''); }
|
||||
set textContent(value) { this.children.forEach(child => { child.parentNode = null; }); this.children = []; this._text = String(value); }
|
||||
appendChild(child) { this.children.push(child); child.parentNode = this; return child; }
|
||||
setAttribute(k, v) { this[k] = v; }
|
||||
getBoundingClientRect() { return {width: 100, height: 100}; }
|
||||
addEventListener(k, fn) { this[k] = fn; }
|
||||
}
|
||||
const context = vm.createContext({window, document: {
|
||||
createElement: tag => new Element(tag),
|
||||
getElementById(id) { return nodes[id] ||= new Element(); }
|
||||
}, Terminal, FitAddon: {FitAddon: class {
|
||||
constructor() { this.measurements = []; this.calls = 0; fits.push(this); }
|
||||
proposeDimensions() { ++this.calls; return this.measurements.length ? this.measurements.shift() : {cols: 80, rows: 24}; }
|
||||
}},
|
||||
@@ -830,7 +840,8 @@ async function test(name, fn) { await fn(); ++passed; console.log('PASS JS:', na
|
||||
});
|
||||
const slotKey = index => ({index,type:'ssh-ed25519',fingerprint:'SHA256:' + String.fromCharCode(97 + index).repeat(43)});
|
||||
function assertKeySlots(b, indices) {
|
||||
assert.equal(b.nodes['account-keys-list'].textContent, indices.map(index => `${index}: ssh-ed25519 ${slotKey(index).fingerprint}`).join('\n'));
|
||||
assert.deepEqual(b.nodes['account-keys-list'].children.map(node => [node.tagName, node.textContent]),
|
||||
indices.flatMap(index => [['DT', `${index}: `], ['DD', `ssh-ed25519 ${slotKey(index).fingerprint}`]]));
|
||||
assert.equal(b.nodes['account-key-index'].value, String(indices[0]));
|
||||
for(let index=0;index<3;++index) {
|
||||
const option=b.nodes['key-option-'+index], present=indices.includes(index);
|
||||
@@ -927,6 +938,20 @@ async function test(name, fn) { await fn(); ++passed; console.log('PASS JS:', na
|
||||
const s=await accountsBrowser(); s.nodes['account-public-key'].value='ssh-ed25519 AAAA'; let warning; s.window.confirm=m=>{warning=m;return true;}; s.queues[accountPath].push(failure(401)); s.click('account-key-add'); await tick();
|
||||
assert.match(warning,/ALL.*web\/SSH.*401.*NOT proof/); assert.deepEqual(s.redirects,['/login']); assert.ok(s.sockets.every(s=>s.closed)); assert.doesNotMatch(s.nodes['account-operation-detail'].textContent,/completed/);
|
||||
});
|
||||
await test('Account definition rows and key rows clear as DOM children and fence late lists', async () => {
|
||||
const b = await keyBrowser(), list = b.nodes['accounts-list'], keys = b.nodes['account-keys-list'];
|
||||
assert.deepEqual(list.children.map(n => [n.tagName, n.textContent]),
|
||||
[['DT', 'alice'], ['DD', 'admin (you)'], ['DT', 'carol'], ['DD', 'user']]);
|
||||
assert.ok(keys.children.length > 0);
|
||||
assert.ok([...list.children, ...keys.children].every(n => n.children.length === 0));
|
||||
const old = [...list.children, ...keys.children], d = deferred();
|
||||
b.queues['/api/settings/accounts'].push(d.promise); b.click('refresh-accounts'); await tick();
|
||||
b.click('settings-serial'); await tick();
|
||||
assert.equal(list.children.length, 0); assert.equal(keys.children.length, 0);
|
||||
assert.ok(old.every(n => n.parentNode === null));
|
||||
d.resolve(json({users: [{username: 'late', role: 'user', user_id: 9, auth_generation: 1}]})); await tick();
|
||||
assert.equal(list.children.length, 0); assert.equal(keys.children.length, 0);
|
||||
});
|
||||
await test('Accounts list is admin-only, secret-free schema and navigation preserves both sockets', async () => {
|
||||
const u = await connected(); u.click('settings-accounts'); await tick();
|
||||
assert.ok(!u.calls.some(c => c.url === '/api/settings/accounts'));
|
||||
|
||||
Reference in New Issue
Block a user