This commit is contained in:
2026-09-08 15:20:27 +02:00
parent 4b9e6c5651
commit 2eb9a9590e
15 changed files with 1023 additions and 127 deletions
+96
View File
@@ -0,0 +1,96 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import { PlaylistLibrary } from "../public/playlist-library.js";
// Minimal DOM elements let the view's actions run without a browser dependency.
class Element {
constructor() {
this.children = [];
this.dataset = {};
this.attributes = {};
this.listeners = {};
this.classes = new Set();
this.classList = { toggle: (name, active) => active ? this.classes.add(name) : this.classes.delete(name) };
}
append(...children) { this.children.push(...children); }
replaceChildren(...children) { this.children = children; }
setAttribute(name, value) { this.attributes[name] = value; }
addEventListener(name, callback) { this.listeners[name] = callback; }
fire(name, values = {}) { this.listeners[name]({ target: this, stopPropagation() {}, preventDefault() {}, ...values }); }
focus() {}
closest() { return null; }
}
class Fixture {
constructor() {
this.nodes = new Map();
this.commands = [];
this.language = "en";
globalThis.window = { RktTranslate: {
language: () => this.language,
t: (key, values = {}) => `${key} ${values.name || ""}`,
} };
globalThis.document = { createElement: () => new Element() };
this.view = new PlaylistLibrary({ querySelector: (selector) => this.node(selector) },
(name, data) => this.commands.push({ name, data }), duration => `${duration} seconds`);
}
node(selector) {
if (!this.nodes.has(selector)) this.nodes.set(selector, new Element());
return this.nodes.get(selector);
}
}
test("library tabs change only the visible panel", () => {
const fixture = new Fixture();
fixture.node("#saved-playlists-tab").fire("click");
assert.equal(fixture.node("#folders-panel").hidden, true);
assert.equal(fixture.node("#saved-playlists-panel").hidden, false);
assert.equal(fixture.node("#saved-playlists-tab").attributes["aria-selected"], "true");
fixture.node("#saved-playlists-tab").fire("keydown", { key: "ArrowLeft" });
assert.equal(fixture.node("#folders-panel").hidden, false);
assert.deepEqual(fixture.commands, []);
});
test("plus and play address the saved playlist UUID and never use item-add/play", () => {
const fixture = new Fixture();
fixture.view.render([{ id: "saved-id", name: "Music", count: 2, duration: 120 }]);
const row = fixture.node("#saved-playlists").children[0];
row.fire("click");
assert.deepEqual(fixture.commands, []);
assert.ok(row.classes.has("selected"));
row.children[2].children[1].fire("click");
row.children[2].children[0].fire("click");
assert.deepEqual(fixture.commands, [
{ name: "playlist-open", data: { id: "saved-id" } },
{ name: "playlist-play", data: { id: "saved-id" } },
]);
assert.equal(fixture.node("#saved-playlists-empty").hidden, true);
});
test("unchanged summaries reuse rows while metadata and language changes refresh them", () => {
const fixture = new Fixture();
const playlists = [{ id: "one", name: "Music", count: 0, duration: 0 }];
fixture.view.render(playlists);
const row = fixture.node("#saved-playlists").children[0];
assert.equal(row.children[2].children[0].disabled, true);
assert.equal(row.children[2].children[1].disabled, false);
fixture.view.render(playlists);
assert.equal(fixture.node("#saved-playlists").children[0], row);
fixture.language = "nl";
fixture.view.render(playlists);
assert.notEqual(fixture.node("#saved-playlists").children[0], row);
fixture.view.render([{ ...playlists[0], name: "Renamed" }]);
assert.equal(fixture.node("#saved-playlists").children[0].children[1].textContent, "Renamed");
fixture.view.render([]);
assert.equal(fixture.node("#saved-playlists-empty").hidden, false);
});
test("keyboard opening does not intercept a nested action button", () => {
const fixture = new Fixture();
fixture.view.render([{ id: "one", name: "Music", count: 1, duration: 60 }]);
const row = fixture.node("#saved-playlists").children[0];
row.fire("keydown", { key: "Enter", target: row.children[2].children[0] });
assert.deepEqual(fixture.commands, []);
row.fire("keydown", { key: "Enter" });
assert.deepEqual(fixture.commands, [{ name: "playlist-open", data: { id: "one" } }]);
});