import { cleanup, fireEvent, render, screen, waitFor, } from "@testing-library/react"; import { afterEach, describe, expect, it } from "vitest"; import Preview, { type PreviewProps } from "./Preview"; import type { Settings } from "../types"; import { readFileSync } from "node:fs"; import postcss from "postcss"; const HOVER_CLASS_PATTERN = /\bb3-wvs-swatch-(scale|lift|bounce|jiggle|wobble|flip)\b/; function buildProps(rawSettings: Settings): PreviewProps { return { rawSettings, showLabels: true, showLabelsColors: false, enableTooltips: false, layoutPaddingTop: 0, layoutPaddingRight: 0, layoutPaddingBottom: 0, layoutPaddingLeft: 0, labelFontSize: 14, labelTextColor: "", mode: "colors", selectedColor: "#46a5e5", swatchWidth: 32, swatchHeight: 32, swatchGap: 8, selectedIndex: 0, onSelectIndex: () => {}, }; } function getRadios(container: HTMLElement): HTMLElement[] { return Array.from(container.querySelectorAll('[role="radio"]')); } afterEach(() => { cleanup(); document.body.innerHTML = ""; }); describe("admin Live Preview swatch hover animation", () => { it("drives the unsaved animation type onto the real preview swatches", async () => { const { container, rerender } = render( , ); await waitFor(() => expect(getRadios(container).length).toBeGreaterThan(0)); for (const radio of getRadios(container)) { expect(radio.className).toMatch(/\bb3-wvs-swatch-bounce\b/); } rerender(); await waitFor(() => { for (const radio of getRadios(container)) { expect(radio.className).toMatch(/\bb3-wvs-swatch-jiggle\b/); expect(radio.className).not.toMatch(/\bb3-wvs-swatch-bounce\b/); } }); }); it("removes every hover animation when Animate on Hover is off", async () => { const { container, rerender } = render( , ); await waitFor(() => expect(getRadios(container).length).toBeGreaterThan(0)); for (const radio of getRadios(container)) { expect(radio.className).toMatch(/\bb3-wvs-swatch-lift\b/); expect(radio.closest(".b3-wvs-swatch-hover-target")).not.toBeNull(); } rerender( , ); await waitFor(() => { for (const radio of getRadios(container)) { expect(radio.className).not.toMatch(HOVER_CLASS_PATTERN); } }); }); it("attaches no hover class for 'none' or extension-only animation values", async () => { const { container, rerender } = render( , ); await waitFor(() => expect(getRadios(container).length).toBeGreaterThan(0)); for (const radio of getRadios(container)) { expect(radio.className).not.toMatch(HOVER_CLASS_PATTERN); } // A Pro-only value saved by the extension must not leak a free class. rerender( , ); await waitFor(() => { for (const radio of getRadios(container)) { expect(radio.className).not.toMatch(HOVER_CLASS_PATTERN); } }); }); it("drives unsaved tooltip animation + fade-in choices onto the preview tooltips", async () => { const { container, rerender } = render( , ); await waitFor(() => expect(getRadios(container).length).toBeGreaterThan(0)); // The Free tooltip wraps the swatch column; hover its anchor element. const anchor = getRadios(container)[0].closest( "[aria-describedby], [class~='bwvs:flex-col']", ); if (!anchor) throw new Error("Missing tooltip anchor"); fireEvent.pointerEnter(anchor, { pointerType: "mouse" }); const tooltip = await screen.findByRole("tooltip"); expect(tooltip.className).toContain("b3-wvs-animate-tooltip-flip"); expect(tooltip.className).not.toContain("b3-wvs-animate-tooltip-fade"); // Turning fade-in off in the draft removes the enter animation class. rerender( , ); await waitFor(() => { expect(screen.getByRole("tooltip").className).not.toContain( "b3-wvs-animate-tooltip-", ); }); }); }); describe("admin Live Preview draft layout and appearance settings", () => { it("matches the theme width safeguard to rendered button swatches", async () => { const { container } = render(
, ); await waitFor(() => expect(getRadios(container).length).toBeGreaterThan(0)); const matching = [] as string[]; postcss .parse(readFileSync("src/admin/index.css", "utf8")) .walkRules((rule) => { if ( rule.selector.includes("button.") && rule.nodes.some( (node) => node.type === "decl" && node.prop === "width" && node.value === "auto" && node.important, ) ) matching.push(rule.selector); }); expect(matching).toHaveLength(1); expect(container.querySelectorAll(matching[0]).length).toBeGreaterThan(0); }); it("renders the draft outline border width on the selected swatch", async () => { const { container, rerender } = render( , ); await waitFor(() => expect(getRadios(container)[0]?.style.boxShadow).toContain("0 0 0 1px"), ); rerender( , ); await waitFor(() => expect(getRadios(container)[0]?.style.boxShadow).toContain("0 0 0 6px"), ); }); it("renders draft grid columns", async () => { const { container, rerender } = render( , ); await waitFor(() => expect( container.querySelector("[class~='bwvs:grid']")?.style .gridTemplateColumns, ).toBe("repeat(1, max-content)"), ); rerender( , ); await waitFor(() => expect( container.querySelector("[class~='bwvs:grid']")?.style .gridTemplateColumns, ).toBe("repeat(4, max-content)"), ); }); it("renders draft flow alignment and independent gaps", async () => { const { container, rerender } = render( , ); const getFlow = () => container.querySelector("[class~='bwvs:flex-wrap']"); await waitFor(() => { expect(getFlow()?.style.justifyContent).toBe("start"); expect(getFlow()?.style.alignItems).toBe("end"); expect(getFlow()?.style.rowGap).toBe("0px"); expect(getFlow()?.style.columnGap).toBe("0px"); }); rerender( , ); await waitFor(() => { expect(getFlow()?.style.justifyContent).toBe("space-between"); expect(getFlow()?.style.alignItems).toBe("start"); expect(getFlow()?.style.rowGap).toBe("32px"); expect(getFlow()?.style.columnGap).toBe("24px"); }); }); it("renders draft number custom dimensions and typography", async () => { const { container } = render( , ); await waitFor(() => { const number = getRadios(container)[0]; expect(number?.style.width).toBe("60px"); expect(number?.style.height).toBe("24px"); expect(number?.style.fontFamily).toBe('"Georgia"'); expect(number?.style.fontWeight).toBe("700"); }); }); it("renders the draft image thumbnail inside image tooltips", async () => { const { container } = render( , ); await waitFor(() => expect(getRadios(container).length).toBeGreaterThan(0)); const anchor = getRadios(container)[0].closest( "[aria-describedby], [class~='bwvs:flex-col']", ); if (!anchor) throw new Error("Missing tooltip anchor"); fireEvent.pointerEnter(anchor, { pointerType: "mouse" }); const tooltip = await screen.findByRole("tooltip"); expect(tooltip.querySelector("img")?.getAttribute("src")).toMatch( /^data:image\/svg\+xml/, ); }); });