From 822ba4d478f212372f29d43d8a29743d60c3f5e5 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:39:38 +0000 Subject: [PATCH] Refactor DeleteModal to use shared getDTagValueFromTags Replaced the local `extractDTagValue` function in `js/ui/components/DeleteModal.js` with the shared `getDTagValueFromTags` utility from `js/nostr/nip71.js` to reduce code duplication and improve maintainability. Added `tests/ui/components/DeleteModal.test.mjs` with manual DOM mocks to verify the refactor and ensure no regressions. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- js/ui/components/DeleteModal.js | 18 +--- tests/ui/components/DeleteModal.test.mjs | 129 +++++++++++++++++++++++ 2 files changed, 131 insertions(+), 16 deletions(-) create mode 100644 tests/ui/components/DeleteModal.test.mjs diff --git a/js/ui/components/DeleteModal.js b/js/ui/components/DeleteModal.js index 80ade25f..7c01ab01 100644 --- a/js/ui/components/DeleteModal.js +++ b/js/ui/components/DeleteModal.js @@ -1,20 +1,6 @@ +import { getDTagValueFromTags } from "../../nostr/index.js"; import { createModalAccessibility } from "./modalAccessibility.js"; -function extractDTagValue(tags) { - if (!Array.isArray(tags)) { - return ""; - } - for (const entry of tags) { - if (!Array.isArray(entry) || entry.length < 2) { - continue; - } - if (entry[0] === "d" && typeof entry[1] === "string") { - return entry[1]; - } - } - return ""; -} - export class DeleteModal { constructor({ removeTrackingScripts, @@ -232,7 +218,7 @@ export class DeleteModal { video && typeof video.videoRootId === "string" && video.videoRootId.trim().length ? video.videoRootId.trim() : ""; - const dTagValue = extractDTagValue(video?.tags); + const dTagValue = getDTagValueFromTags(video?.tags); if (this.subtitle) { const subtitleParts = []; diff --git a/tests/ui/components/DeleteModal.test.mjs b/tests/ui/components/DeleteModal.test.mjs new file mode 100644 index 00000000..605e4959 --- /dev/null +++ b/tests/ui/components/DeleteModal.test.mjs @@ -0,0 +1,129 @@ +import { describe, it, beforeEach } from "node:test"; +import assert from "node:assert"; + +// Manual DOM Mocks +class MockElement { + constructor(tagName) { + this.tagName = tagName; + this.children = []; + this.textContent = ""; + this.dataset = {}; + this.classList = { + add: () => {}, + remove: () => {}, + contains: () => false, + }; + this.style = {}; + } + querySelector() { + return null; + } + querySelectorAll() { + return []; + } + appendChild(child) { + this.children.push(child); + } + replaceChildren() { + this.children = []; + } + getAttribute() { + return null; + } + setAttribute() {} + get ownerDocument() { + return globalThis.document; + } + get nodeType() { + return 1; + } // ELEMENT_NODE + addEventListener() {} + removeEventListener() {} +} + +globalThis.EventTarget = class EventTarget { + addEventListener() {} + removeEventListener() {} + dispatchEvent() {} +}; +globalThis.CustomEvent = class CustomEvent {}; +globalThis.DOMParser = class DOMParser { + parseFromString() { + return { body: { children: [] } }; + } +}; +globalThis.document = { + getElementById: () => null, + createElement: (tag) => new MockElement(tag), + body: new MockElement("BODY"), + activeElement: null, + addEventListener: () => {}, + removeEventListener: () => {}, + contains: () => false, +}; + +// Dynamic import to ensure mocks are ready before module execution +const { DeleteModal } = await import("../../../js/ui/components/DeleteModal.js"); + +describe("DeleteModal", () => { + let modal; + let mockModalElement; + let subtitle; + let metadataList; + + beforeEach(() => { + // Setup mock elements + mockModalElement = new MockElement("DIV"); + subtitle = new MockElement("DIV"); + subtitle.id = "deleteModalSubtitle"; + metadataList = new MockElement("DL"); + metadataList.id = "deleteModalMetadata"; + + // Mock querySelector for cacheElements + mockModalElement.querySelector = (selector) => { + if (selector === "#deleteModalSubtitle") return subtitle; + if (selector === "#deleteModalMetadata") return metadataList; + return new MockElement("DIV"); // Return generic element for others + }; + + modal = new DeleteModal({ container: new MockElement("DIV") }); + modal.cacheElements(mockModalElement); + }); + + it("should extract d-tag and display it in subtitle", () => { + const video = { + tags: [["d", "test-d-tag"]], + }; + modal.setVideo(video); + + // Check if subtitle text content contains the d-tag + assert.match(subtitle.textContent, /d=test-d-tag/); + }); + + it("should extract d-tag and display it in metadata", () => { + const video = { + tags: [["d", "test-d-tag"]], + }; + modal.setVideo(video); + + // Metadata list populates children + // We can check if any child (or grandchild) has the text + const hasDTag = metadataList.children.some((child) => { + // child is wrapper, wrapper has dt/dd + return child.children.some((grandchild) => + grandchild.textContent.includes("test-d-tag"), + ); + }); + + assert.ok(hasDTag, "Metadata should contain d-tag"); + }); + + it("should handle missing d-tag", () => { + const video = { + tags: [["t", "hashtag"]], + }; + modal.setVideo(video); + + assert.ok(!subtitle.textContent.includes("d=")); + }); +});