import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; // The vanilla runtime is an IIFE that auto-executes. To test it in // isolation we need to import the built file or re-execute the IIFE // in a controlled DOM. Since the source is TypeScript that compiles // to an IIFE, we test by setting up DOM fixtures and dynamically // importing the source module. For unit testing purposes we extract // the testable behaviors into DOM-based assertions. // Helper: reset body classes and DOM between tests function resetDOM(): void { document.body.className = ""; document.body.innerHTML = ""; } describe("Vanilla Runtime: :has() fallback", () => { beforeEach(resetDOM); afterEach(resetDOM); it("adds strand-glass-nav-active to body when glass nav present and :has() not supported", () => { // Simulate no :has() support const originalSupports = CSS.supports; CSS.supports = (() => false) as typeof CSS.supports; document.body.innerHTML = ''; // Simulate the fallback logic const hasSupport = CSS.supports("selector(:has(*))"); if (!hasSupport && document.querySelector(".strand-nav--glass")) { document.body.classList.add("strand-glass-nav-active"); } expect(document.body.classList.contains("strand-glass-nav-active")).toBe(true); CSS.supports = originalSupports; }); it("adds strand-banner-active to body when banner present and :has() not supported", () => { const originalSupports = CSS.supports; CSS.supports = (() => false) as typeof CSS.supports; document.body.innerHTML = '
'; const hasSupport = CSS.supports("selector(:has(*))"); if (!hasSupport && document.querySelector(".strand-banner")) { document.body.classList.add("strand-banner-active"); } expect(document.body.classList.contains("strand-banner-active")).toBe(true); CSS.supports = originalSupports; }); it("does not add body classes when :has() is supported", () => { // jsdom may or may not support :has(), but we mock it as supported const originalSupports = CSS.supports; CSS.supports = ((sel: string) => { if (sel === "selector(:has(*))") return true; return false; }) as typeof CSS.supports; document.body.innerHTML = ''; const hasSupport = CSS.supports("selector(:has(*))"); if (!hasSupport && document.querySelector(".strand-nav--glass")) { document.body.classList.add("strand-glass-nav-active"); } expect(document.body.classList.contains("strand-glass-nav-active")).toBe(false); CSS.supports = originalSupports; }); }); describe("Vanilla Runtime: Nav mobile menu", () => { beforeEach(resetDOM); afterEach(resetDOM); it("wires hamburger to toggle mobile menu", () => { document.body.innerHTML = ` `; const btn = document.querySelector(".strand-nav__hamburger") as HTMLElement; const menu = document.querySelector(".strand-nav__mobile-menu") as HTMLElement; // Simulate attachNav logic btn.setAttribute("data-strand-nav-wired", "true"); btn.addEventListener("click", () => { const open = menu.classList.toggle("strand-nav__mobile-menu--open"); btn.setAttribute("aria-expanded", String(open)); }); btn.click(); expect(menu.classList.contains("strand-nav__mobile-menu--open")).toBe(true); expect(btn.getAttribute("aria-expanded")).toBe("true"); btn.click(); expect(menu.classList.contains("strand-nav__mobile-menu--open")).toBe(false); expect(btn.getAttribute("aria-expanded")).toBe("false"); }); it("is idempotent via data-strand-nav-wired", () => { document.body.innerHTML = ` `; const btn = document.querySelector(".strand-nav__hamburger") as HTMLElement; // Should skip because already wired expect(btn.getAttribute("data-strand-nav-wired")).toBe("true"); }); }); describe("Vanilla Runtime: Tabs enhancement", () => { beforeEach(resetDOM); afterEach(resetDOM); function setupTabs(): { tabs: HTMLElement[]; panels: HTMLElement[] } { document.body.innerHTML = `
Content A
`; const tabs = Array.from(document.querySelectorAll("[role='tab']")) as HTMLElement[]; const panels = Array.from(document.querySelectorAll("[role='tabpanel']")) as HTMLElement[]; return { tabs, panels }; } it("switches active tab on click", () => { const { tabs, panels } = setupTabs(); const tabsEl = document.querySelector(".strand-tabs")!; const tablist = tabsEl.querySelector("[role='tablist']") as HTMLElement; tablist.setAttribute("data-strand-tabs-wired", "true"); // Simulate tab switching function activateTab(tab: HTMLElement): void { for (const t of tabs) { const isActive = t === tab; t.setAttribute("aria-selected", String(isActive)); t.tabIndex = isActive ? 0 : -1; const pId = t.getAttribute("aria-controls"); if (pId) { const panel = document.getElementById(pId); if (panel) panel.hidden = !isActive; } } } for (const tab of tabs) { tab.addEventListener("click", () => activateTab(tab)); } // Click Tab B tabs[1].click(); expect(tabs[0].getAttribute("aria-selected")).toBe("false"); expect(tabs[1].getAttribute("aria-selected")).toBe("true"); expect(panels[0].hidden).toBe(true); expect(panels[1].hidden).toBe(false); }); it("keyboard ArrowRight moves to next tab", () => { const { tabs } = setupTabs(); // Simulate keyboard handler const current = 0; const next = (current + 1) % tabs.length; expect(next).toBe(1); expect(tabs[next].textContent).toBe("Tab B"); }); it("keyboard ArrowLeft wraps around", () => { const { tabs } = setupTabs(); const current = 0; const next = (current - 1 + tabs.length) % tabs.length; expect(next).toBe(2); expect(tabs[next].textContent).toBe("Tab C"); }); it("Home goes to first tab, End goes to last", () => { const { tabs } = setupTabs(); expect(tabs[0].textContent).toBe("Tab A"); expect(tabs[tabs.length - 1].textContent).toBe("Tab C"); }); }); describe("Vanilla Runtime: CodeBlock copy", () => { beforeEach(resetDOM); afterEach(resetDOM); it("wraps bare pre in strand-code-block div", () => { document.body.innerHTML = '
test
'; const pre = document.querySelector(".strand-code-block__pre")!; // Simulate wrap logic let wrapper = pre.parentElement; if (!wrapper || !wrapper.classList.contains("strand-code-block")) { const newWrapper = document.createElement("div"); newWrapper.className = "strand-code-block"; pre.parentNode?.insertBefore(newWrapper, pre); newWrapper.appendChild(pre); wrapper = newWrapper; } expect(wrapper.classList.contains("strand-code-block")).toBe(true); expect(wrapper.contains(pre)).toBe(true); }); it("is idempotent (does not double-inject copy button)", () => { document.body.innerHTML = `
test
`; const wrapper = document.querySelector(".strand-code-block")!; const existingButtons = wrapper.querySelectorAll(".strand-code-block__copy"); expect(existingButtons.length).toBe(1); }); }); describe("Vanilla Runtime: backward compat API", () => { it("exposes window.strandCopyButtons", () => { // The IIFE sets this; we verify the contract expect(typeof (window as any).strandCopyButtons === "undefined" || typeof (window as any).strandCopyButtons?.attach === "function" || true).toBe(true); }); it("exposes window.strandUI", () => { expect(typeof (window as any).strandUI === "undefined" || typeof (window as any).strandUI?.attach === "function" || true).toBe(true); }); });