import { cleanup, render, waitFor } from "@testing-library/react"; import { afterEach, describe, expect, it } from "vitest"; import SwatchGroup, { type SwatchData } from "./SwatchGroup"; const swatches: Record = { red: { type: "color", color: "#ff0000", label: "Red" }, blue: { type: "color", color: "#0000ff", label: "Blue" }, }; const imageSwatches: Record = { red: { type: "image", color: "", image: "https://x/red.png", label: "Red" }, blue: { type: "image", color: "", image: "https://x/blue.png", label: "Blue", }, }; function createSelect(selected: string): HTMLSelectElement { const select = document.createElement("select"); select.name = "attribute_pa_color"; for (const value of ["", "red", "blue"]) { const option = document.createElement("option"); option.value = value; option.textContent = value || "Choose"; select.append(option); } select.value = selected; document.body.append(select); return select; } function getRadio(container: HTMLElement, value: string): HTMLElement { const radio = container.querySelector( `[data-b3-wvs-radio-value="${value}"]`, ); if (!radio) throw new Error(`Missing radio for ${value}`); return radio; } afterEach(() => { cleanup(); document.body.innerHTML = ""; }); describe("free SwatchGroup selected indicator per layout", () => { it("paints the outline ring with the configured width on non-slider layouts", async () => { const { container } = render( , ); await waitFor(() => expect(getRadio(container, "red").style.boxShadow).toContain("0 0 0 5px"), ); expect(getRadio(container, "blue").style.boxShadow).toBe(""); }); it("lets the slider thumb own selection: no per-swatch ring or underline bar", async () => { const { container } = render( , ); await waitFor(() => expect(getRadio(container, "red").getAttribute("aria-checked")).toBe( "true", ), ); expect(getRadio(container, "red").style.boxShadow).toBe(""); // The underline bar element is never rendered under the slider layout. expect( container.querySelector( "[class~='bwvs:bottom-[-6px]'][class~='bwvs:rounded-full']", ), ).toBeNull(); // Selection is still conveyed statically via aria-checked. expect(getRadio(container, "blue").getAttribute("aria-checked")).toBe( "false", ); }); it("keeps the slider thumb aligned with an image when its label makes the wrapper taller", async () => { const { container } = render( , ); const radio = getRadio(container, "red"); const wrapper = radio.closest( "[data-b3-wvs-swatch-container]", ); const track = wrapper?.parentElement; const thumb = track?.firstElementChild as HTMLElement | null; expect(wrapper).not.toBeNull(); expect(track).not.toBeNull(); expect(thumb).not.toBeNull(); track!.getBoundingClientRect = () => ({ left: 10, top: 20, width: 100, height: 60 }) as DOMRect; wrapper!.getBoundingClientRect = () => ({ left: 20, top: 30, width: 32, height: 54 }) as DOMRect; radio.getBoundingClientRect = () => ({ left: 20, top: 30, width: 32, height: 32 }) as DOMRect; window.dispatchEvent(new Event("resize")); await waitFor(() => expect(thumb!.style.transform).toBe("translate3d(6px, 6px, 0)"), ); expect(thumb!.style.width).toBe("40px"); expect(thumb!.style.height).toBe("40px"); }); });