fix: create queue list with an id prop so setup no longer throws (#8) (#9)

ComfyUI's $el splits its selector on "." only and passes the rest to
document.createElement, so $el("div#queue-list.mr-queue-list") threw
InvalidCharacterError during setup and the Resolver sidebar tab was never
registered (#4, #8). Pass the id as a prop instead.

Add a test that every $el selector under web/ starts with a plain tag name.
This commit is contained in:
Alexander Puzynia
2026-11-09 20:57:44 +01:00
committed by GitHub
parent 948b5bae4b
commit 498e9cd47e
2 changed files with 50 additions and 1 deletions
+49
View File
@@ -0,0 +1,49 @@
import assert from 'node:assert/strict';
import { readdir, readFile } from 'node:fs/promises';
import { extname, relative, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import test from 'node:test';
const WEB_ROOT = fileURLToPath(new URL('../web/', import.meta.url));
// ComfyUI's $el only splits its first argument on "." and passes the first part to
// document.createElement, so "div#id" or "div[attr]" throws InvalidCharacterError.
// Ids and attributes must go in the props object instead.
const EL_CALL_PATTERN = /\$el\(\s*(["'`])(.*?)\1/g;
const TAG_NAME_PATTERN = /^[a-z][a-z0-9-]*$/i;
async function collectJavaScriptFiles(directory) {
const entries = await readdir(directory, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const entryPath = resolve(directory, entry.name);
if (entry.isDirectory()) {
files.push(...await collectJavaScriptFiles(entryPath));
} else if (entry.isFile() && extname(entry.name).toLowerCase() === '.js') {
files.push(entryPath);
}
}
return files;
}
test('$el selectors start with a plain tag name', async () => {
const invalid = [];
let calls = 0;
for (const file of await collectJavaScriptFiles(WEB_ROOT)) {
const source = await readFile(file, 'utf8');
for (const match of source.matchAll(EL_CALL_PATTERN)) {
calls += 1;
const tagName = match[2].split('.')[0];
if (!TAG_NAME_PATTERN.test(tagName)) {
const line = source.slice(0, match.index).split('\n').length;
invalid.push(`${relative(WEB_ROOT, file)}:${line}: ${match[0]}`);
}
}
}
assert.ok(calls > 0, 'expected to find $el calls under web/');
assert.deepEqual(invalid, [], 'pass ids and attributes as $el props, not in the selector');
});
+1 -1
View File
@@ -125,7 +125,7 @@ export const queueMethods = {
]);
// Scrollable list
this.queueList = $el("div#queue-list.mr-queue-list");
this.queueList = $el("div.mr-queue-list", { id: "queue-list" });
this.queueList.addEventListener('click', (event) => {
const button = event.target instanceof Element
? event.target.closest('.mr-queue-remove')