import { describe, it, beforeEach } from "node:test"; import assert from "node:assert/strict"; // Mock MutationObserver before importing the component let mutationCallbacks = []; globalThis.MutationObserver = class { constructor(callback) { this._callback = callback; this._connected = false; mutationCallbacks.push(this); } observe() { this._connected = true; } disconnect() { this._connected = false; } _trigger() { if (this._connected) { this._callback(); } } }; await import("/js/components/context-menu.js"); describe("context-menu", () => { beforeEach(() => { document.body.innerHTML = ""; mutationCallbacks = []; }); function connectElement(element) { const container = document.createElement("div"); container.className = "page-visible"; container.appendChild(element); document.body.appendChild(container); } describe("ContextMenu - rendering", () => { it("should render context-menu-container", () => { const element = document.createElement("context-menu"); connectElement(element); const container = element.querySelector(".context-menu-container"); assert(container !== null); }); it("should render dialog element", () => { const element = document.createElement("context-menu"); connectElement(element); const dialog = element.querySelector(".context-menu"); assert(dialog !== null); assert.deepEqual(dialog.tagName, "DIALOG"); }); it("should preserve children in the dialog", () => { const element = document.createElement("context-menu"); element.innerHTML = "Menu Item"; connectElement(element); const child = element.querySelector(".context-menu .test-child"); assert(child !== null); assert.deepEqual(child.textContent, "Menu Item"); }); }); describe("ContextMenu - initial state", () => { it("should start with isOpen set to false", () => { const element = document.createElement("context-menu"); connectElement(element); assert.deepEqual(element.isOpen, false); }); it("should not have open class on container initially", () => { const element = document.createElement("context-menu"); connectElement(element); const container = element.querySelector(".context-menu-container"); assert(!container.classList.contains("open")); }); }); describe("ContextMenu - open method", () => { it("should set isOpen to true when open() is called", () => { const element = document.createElement("context-menu"); connectElement(element); element.open(100, 100); assert.deepEqual(element.isOpen, true); }); it("should add open class to container when open() is called", () => { const element = document.createElement("context-menu"); connectElement(element); element.open(100, 100); const container = element.querySelector(".context-menu-container"); assert(container.classList.contains("open")); }); it("should show the dialog when open() is called", () => { const element = document.createElement("context-menu"); connectElement(element); element.open(100, 100); const dialog = element.querySelector(".context-menu"); assert(dialog.open); }); }); describe("ContextMenu - close method", () => { it("should set isOpen to false when close() is called", async () => { const element = document.createElement("context-menu"); connectElement(element); element.open(100, 100); await element.close(); assert.deepEqual(element.isOpen, false); }); it("should remove open class from container when close() is called", async () => { const element = document.createElement("context-menu"); connectElement(element); element.open(100, 100); await element.close(); const container = element.querySelector(".context-menu-container"); assert(!container.classList.contains("open")); }); it("should close the dialog when close() is called", async () => { const element = document.createElement("context-menu"); connectElement(element); element.open(100, 100); await element.close(); const dialog = element.querySelector(".context-menu"); assert(!dialog.open); }); }); describe("ContextMenu - container click", () => { it("should close menu when container is clicked", async () => { const element = document.createElement("context-menu"); connectElement(element); element.open(100, 100); const container = element.querySelector(".context-menu-container"); container.click(); await Promise.resolve(); assert.deepEqual(element.isOpen, false); }); }); describe("ContextMenu - child updates", () => { it("should update dialog contents when children change", () => { const element = document.createElement("context-menu"); element.innerHTML = "Item A"; connectElement(element); const observer = mutationCallbacks[mutationCallbacks.length - 1]; const itemA = element.querySelector(".context-menu .item-a"); assert(itemA !== null, "Initial child should be in dialog"); const newChild = document.createElement("span"); newChild.className = "item-b"; newChild.textContent = "Item B"; element.appendChild(newChild); observer._trigger(); const itemB = element.querySelector(".context-menu .item-b"); assert(itemB !== null, "New child should be moved into dialog"); assert.deepEqual(itemB.textContent, "Item B"); }); it("should include all children when full set is provided", () => { const element = document.createElement("context-menu"); element.innerHTML = "Original"; connectElement(element); const observer = mutationCallbacks[mutationCallbacks.length - 1]; // Simulate lit-html re-rendering all children (old + new) const original = document.createElement("span"); original.className = "original"; original.textContent = "Original"; const added = document.createElement("span"); added.className = "added"; added.textContent = "Added"; element.appendChild(original); element.appendChild(added); observer._trigger(); const items = element.querySelectorAll(".context-menu span"); assert.deepEqual(items.length, 2); assert.deepEqual(items[0].textContent, "Original"); assert.deepEqual(items[1].textContent, "Added"); }); it("should replace dialog contents when children are replaced", () => { const element = document.createElement("context-menu"); element.innerHTML = "KeepRemove"; connectElement(element); const observer = mutationCallbacks[mutationCallbacks.length - 1]; const dialog = element.querySelector(".context-menu"); assert.deepEqual(dialog.querySelectorAll("span").length, 2); const replacement = document.createElement("span"); replacement.className = "only"; replacement.textContent = "Only"; element.appendChild(replacement); observer._trigger(); const spans = dialog.querySelectorAll("span"); assert.deepEqual(spans.length, 1); assert.deepEqual(spans[0].textContent, "Only"); }); }); describe("ContextMenu - reinitialization protection", () => { it("should not reinitialize when connectedCallback is called multiple times", () => { const element = document.createElement("context-menu"); element.innerHTML = "Original"; connectElement(element); element.connectedCallback(); const child = element.querySelector(".context-menu .test"); assert(child !== null); assert.deepEqual(child.textContent, "Original"); }); }); });