Merge pull request #10067 from kiansaeidnia/fix/web-change-selection

fix(web): reset batch selection when changing apps
This commit is contained in:
Dean authored and GitHub committed 2026-10-26 06:14:10 +13:00
commit 63fa26aa68
4 files changed
+151 -5

No files matched your search

+1 -1
View File
@@ -36,4 +36,4 @@ jobs:
node --check demo/web/output-directory.mjs
node --check demo/web/worker.mjs
- name: Test browser converter helpers
run: node --test demo/web/tests/batch.test.mjs demo/web/tests/output-directory.test.mjs
run: node --test demo/web/tests/app.test.mjs demo/web/tests/batch.test.mjs demo/web/tests/output-directory.test.mjs
+7 -3
View File
@@ -263,12 +263,16 @@ async function selectInputs(inputs) {
$("choose").onclick = () => { $("file").value = ""; $("file").click(); };
$("change").onclick = () => {
if (working) return;
worker?.terminate();
worker = null;
if (downloadUrl) { URL.revokeObjectURL(downloadUrl); downloadUrl = null; }
file = null;
summary = null;
selectedInputs = [];
summaries = [];
inspectionComplete = false;
$("file").value = "";
$("folder").value = "";
for (const id of ["selection", "warnings", "error", "done", "download"]) $(id).hidden = true;
$("app-list").replaceChildren();
for (const id of ["selection", "warnings", "error", "done", "download", "app-list"]) $(id).hidden = true;
$("acknowledge").checked = false;
$("drop").hidden = false;
update();
+140
View File
@@ -0,0 +1,140 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { setImmediate } from "node:timers/promises";
import { test } from "node:test";
class Element {
children = [];
listeners = new Map();
value = "";
checked = false;
disabled = false;
textContent = "";
constructor(hidden = false) { this.hidden = hidden; }
addEventListener(type, listener) { this.listeners.set(type, listener); }
removeAttribute(name) { delete this[name]; }
append(...children) { this.children.push(...children); }
replaceChildren(...children) { this.children = children; }
}
async function setup(t, name) {
const html = readFileSync(new URL("../index.html", import.meta.url), "utf8");
const elements = new Map([...html.matchAll(/<[^>]+\bid="([^"]+)"[^>]*>/gu)].map(match => [
match[1], new Element(/\shidden(?:\s|>)/u.test(match[0]))
]));
const get = id => {
assert.ok(elements.has(id), `Missing element: ${id}`);
return elements.get(id);
};
const options = new Map(["disk", "zip", "memory"].map(value => [`[value="${value}"]`, new Element()]));
get("save").querySelector = selector => options.get(selector);
get("folder").webkitdirectory = true;
const workers = [];
class Worker {
messages = [];
terminated = false;
constructor() { workers.push(this); }
postMessage(message) { this.messages.push(message); }
terminate() { this.terminated = true; }
}
const globals = {
document: {
getElementById: get,
createElement: () => new Element(),
querySelector: selector => {
assert.equal(selector, 'input[name="target"]:checked');
return { value: "linux" };
}
},
window: { isSecureContext: true, Worker, WebAssembly, ReadableStream, WritableStream, crypto: { subtle: {} } },
Worker,
fetch: async () => ({
ok: true,
json: async () => ({ tag: "test-release", platforms: { linux: { expandedBytes: 1024 }, windows: { expandedBytes: 1024 } } })
})
};
for (const [key, value] of Object.entries(globals)) {
const original = Object.getOwnPropertyDescriptor(globalThis, key);
Object.defineProperty(globalThis, key, { value, configurable: true, writable: true });
t.after(() => {
if (original) Object.defineProperty(globalThis, key, original);
else delete globalThis[key];
});
}
await import(new URL(`../app.mjs?${name}`, import.meta.url));
await setImmediate();
return { get, workers };
}
async function inspect(worker, count) {
for (let index = 0; index < count; index++) {
await worker.onmessage({ data: {
type: "inspected", index,
summary: { files: 1, totalBytes: 1024, modules: 0, selectedBytes: 1024, windowsSelectedBytes: 1024, warnings: ["Test warning"] }
} });
}
await worker.onmessage({ data: { type: "inspection-complete" } });
}
for (const kind of ["zip", "batch", "folder"]) {
test(`Change resets an inspected ${kind} and permits a new conversion`, async t => {
const { get, workers } = await setup(t, kind);
assert.equal(get("convert").disabled, true);
if (kind === "folder") {
get("folder").files = [{ name: "eboot.bin", webkitRelativePath: "app/eboot.bin" }];
get("folder").onchange();
} else {
get("file").files = kind === "batch" ? [{ name: "first.zip" }, { name: "second.zip" }] : [{ name: "first.zip" }];
get("file").onchange();
}
const first = workers[0];
assert.equal(first.messages[0].type, "inspect");
assert.equal(first.messages[0].inputs.length, kind === "batch" ? 2 : 1);
get("change").onclick();
assert.equal(first.terminated, false);
await inspect(first, first.messages[0].inputs.length);
assert.equal(get("selection").hidden, false);
assert.equal(get("drop").hidden, true);
assert.equal(get("warnings").hidden, false);
assert.equal(get("app-list").children.length, first.messages[0].inputs.length);
assert.equal(get("app-list").hidden, kind !== "batch");
get("acknowledge").checked = true;
get("acknowledge").listeners.get("change")();
assert.equal(get("convert").disabled, false);
get("change").onclick();
assert.equal(first.terminated, true);
assert.equal(get("file").value, "");
assert.equal(get("folder").value, "");
assert.equal(get("selection").hidden, true);
assert.equal(get("warnings").hidden, true);
assert.equal(get("app-list").hidden, true);
assert.equal(get("app-list").children.length, 0);
assert.equal(get("drop").hidden, false);
assert.equal(get("acknowledge").checked, false);
assert.equal(get("convert").disabled, true);
get("acknowledge").checked = true;
get("acknowledge").listeners.get("change")();
assert.equal(get("convert").disabled, true);
get("change").onclick();
get("file").files = [{ name: "replacement.zip" }];
get("file").onchange();
assert.equal(workers.length, 2);
const replacement = workers[1];
assert.deepEqual(replacement.messages[0].inputs.map(input => input.name), ["replacement.zip"]);
assert.equal(get("convert").disabled, true);
await inspect(replacement, 1);
assert.equal(get("archive-name").textContent, "replacement.zip");
assert.equal(get("app-list").children.length, 1);
assert.equal(get("app-list").hidden, true);
assert.equal(get("convert").disabled, true);
get("acknowledge").checked = true;
get("acknowledge").listeners.get("change")();
assert.equal(get("convert").disabled, false);
await get("convert").onclick();
assert.equal(replacement.messages.at(-1).type, "convert");
assert.equal(replacement.terminated, false);
});
}
+3 -1
View File
@@ -26,7 +26,9 @@ def main(argv=None):
if not archive.is_file():
parser.error(f"archive module does not exist: {archive}")
try:
subprocess.run(["node", "--test", str(ROOT / "demo/web/tests/output-directory.test.mjs")], check=True)
subprocess.run(["node", "--test", *(str(ROOT / "demo/web/tests" / name) for name in (
"app.test.mjs", "batch.test.mjs", "output-directory.test.mjs"
))], check=True)
with tempfile.TemporaryDirectory(prefix="anyps5-web-") as directory:
fixture = Path(directory)
(fixture / "main.elf").write_bytes(main_fixture())