Files
RustProxy/tests/acl_picker_dom.cjs
T
2026-10-20 22:47:13 +02:00

73 lines
4.9 KiB
JavaScript

// Run with: node tests/acl_picker_dom.cjs. Tiny DOM stub for picker state/regression.
"use strict";
const assert = require("node:assert/strict");
const fs = require("node:fs");
const vm = require("node:vm");
const path = require("node:path");
class DomEvent {
constructor(type, options = {}) { this.type = type; this.bubbles = !!options.bubbles; this.target = null; }
}
class DomNode {
constructor(tag) { this.tag = tag; this.className = ""; this.children = []; this.parent = null; this.dataset = {}; this.handlers = new Map(); this.hidden = false; this.disabled = false; this.checked = false; this.value = ""; this.type = ""; this._text = ""; }
get textContent() { return this._text + this.children.map(c => c.textContent).join(""); }
set textContent(text) { this.replaceChildren(); this._text = String(text); }
append(...children) { for (const child of children) { child.parent = this; this.children.push(child); } }
replaceChildren(...children) { for (const child of this.children) child.parent = null; this.children = []; this._text = ""; this.append(...children); }
setAttribute() { }
focus() { }
addEventListener(type, handler) { if (!this.handlers.has(type)) this.handlers.set(type, []); this.handlers.get(type).push(handler); }
dispatchEvent(event) { if (!event.target) event.target = this; for (const handler of this.handlers.get(event.type) || []) handler(event); if (event.bubbles && this.parent) this.parent.dispatchEvent(event); }
click() { this.dispatchEvent(new DomEvent("click", {bubbles:true})); }
hasClass(name) { return this.className.split(/\s+/).includes(name); }
matches(selector) { return selector === 'input[type="checkbox"]' && this.tag === "input" && this.type === "checkbox"; }
all() { return this.children.flatMap(child => [child, ...child.all()]); }
querySelectorAll(selector) {
const descendants = this.all();
if (selector === '.acl-picker-option input[type="checkbox"]:checked') return descendants.filter(n => n.tag === "input" && n.type === "checkbox" && n.checked && n.parent?.hasClass("acl-picker-option"));
if (selector === '.acl-picker-option input[type="checkbox"]') return descendants.filter(n => n.tag === "input" && n.type === "checkbox" && n.parent?.hasClass("acl-picker-option"));
const value = selector.match(/^\.acl-picker-option input\[value="([^"]+)"\]$/);
if (value) return descendants.filter(n => n.tag === "input" && n.value === value[1] && n.parent?.hasClass("acl-picker-option"));
const cls = selector.match(/^\.([\w-]+)$/);
if (cls) return descendants.filter(n => n.hasClass(cls[1]));
throw Error("Unsupported test selector " + selector);
}
querySelector(selector) { return this.querySelectorAll(selector)[0] || null; }
}
const profiles = Array.from({length:10}, (_,i) => ({
id: `profile_${i}`, name: i === 0 ? "Office networks" : `Profile ${i}`,
acl: {default: "deny", rules:[{network:`10.${i}.0.0/16`,action:"allow"},{network:"192.0.2.0/24",action:"deny"}]},
country_access: {mode: i === 0 ? "block" : "off", countries: i === 0 ? ["RU"] : []}
}));
const context = {
window: {},
envelope: {config:{acl_profiles:profiles}},
el(tag, text, cls) { const node = new DomNode(tag); if (text !== undefined) node.textContent = text; if (cls) node.className = cls; return node; },
t: key => key,
Event: DomEvent,
};
vm.createContext(context);
vm.runInContext(fs.readFileSync(path.resolve(__dirname, "../src/admin/web/acl-picker.js"), "utf8"), context);
const picker = context.window.rustProxyAclPicker;
const root = picker.create(["profile_0", "profile_1"], "vh-acl-profile-choices");
assert.deepEqual(Array.from(picker.read(root)), ["profile_0", "profile_1"]);
assert.equal(root.querySelectorAll(".acl-picker-tag").length, 2);
assert.match(root.textContent, /10\.0\.0\.0\/16/);
assert.match(root.textContent, /RU/);
const search = root.querySelector(".acl-picker-search");
search.value = "Office"; search.dispatchEvent(new DomEvent("input", {bubbles:true}));
assert.equal(root.querySelectorAll(".acl-picker-option").filter(node => !node.hidden).length, 1);
search.value = ""; search.dispatchEvent(new DomEvent("input", {bubbles:true}));
root.querySelector(".acl-picker-remove").click();
assert.deepEqual(Array.from(picker.read(root)), ["profile_1"]);
for (const check of root.querySelectorAll('.acl-picker-option input[type="checkbox"]').slice(2,9)) {
check.checked = true;
check.dispatchEvent(new DomEvent("change", {bubbles:true}));
}
assert.equal(picker.count(root), 8);
assert.equal(root.querySelector('.acl-picker-option input[value="profile_9"]').disabled, true);
root.querySelector(".acl-picker-remove").click();
assert.equal(root.querySelector('.acl-picker-option input[value="profile_9"]').disabled, false);
const docker = picker.create([], "docker-acl-profile");
assert.deepEqual(Array.from(picker.read(docker)), []); // empty override inherits agent profiles
console.log("ACL picker DOM regression: PASS (choices, preview, search, badges, limit and inheritance)");