/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at https://mozilla.org/MPL/2.0/. */ import { render } from "@testing-library/react"; import { createReorderFlip, useReorderFlip, } from "content-src/lib/useReorderFlip.jsx"; // jsdom has no layout, so each tile's rect is stubbed and mutated between // syncs to simulate a reorder. The engine only reads left/top. function setRect(el, { left = 0, top = 0, width = 100, height = 100 }) { el.getBoundingClientRect = () => ({ left, top, right: left + width, bottom: top + height, width, height, x: left, y: top, toJSON() {}, }); } function makeContainer(ids) { const container = document.createElement("div"); for (const id of ids) { const el = document.createElement("div"); el.setAttribute("data-id", id); setRect(el, {}); container.appendChild(el); } return container; } describe("createReorderFlip", () => { let reduceMotion; beforeEach(() => { reduceMotion = false; window.matchMedia = jest.fn(() => ({ matches: reduceMotion })); }); it("is not animating before any slide", () => { const flip = createReorderFlip({ childSelector: "[data-id]" }); expect(flip.isAnimating()).toBe(false); }); it("no-ops without a container", () => { const flip = createReorderFlip({ childSelector: "[data-id]" }); expect(() => flip.sync(null, {})).not.toThrow(); expect(flip.isAnimating()).toBe(false); }); it("a reset pass establishes the baseline without animating", () => { const flip = createReorderFlip({ childSelector: "[data-id]" }); const container = makeContainer(["a", "b"]); flip.sync(container, { reset: true }); const [a] = container.children; expect(a.style.transition).toBe("none"); expect(flip.isAnimating()).toBe(false); }); it("animates only the tiles that moved since the baseline", () => { const flip = createReorderFlip({ childSelector: "[data-id]" }); const container = makeContainer(["a", "b"]); const [a, b] = container.children; flip.sync(container, { reset: true }); setRect(b, { left: 100 }); flip.sync(container, {}); expect(b.style.transition).toContain("transform"); // Released back to the natural position so the transition can play. expect(b.style.transform).toBe(""); expect(a.style.transition).toBe("none"); expect(flip.isAnimating()).toBe(true); }); it("leaves the skipped tile untransformed even when it moved", () => { const flip = createReorderFlip({ childSelector: "[data-id]", skipSelector: "[data-skip]", }); const container = makeContainer(["a", "b"]); const [, b] = container.children; b.setAttribute("data-skip", ""); flip.sync(container, { reset: true }); setRect(b, { left: 100 }); flip.sync(container, {}); expect(b.style.transition).toBe("none"); }); it("does not animate when disabled", () => { const flip = createReorderFlip({ childSelector: "[data-id]" }); const container = makeContainer(["a", "b"]); const [, b] = container.children; flip.sync(container, { reset: true }); setRect(b, { left: 100 }); flip.sync(container, { enabled: false }); expect(b.style.transition).toBe("none"); expect(flip.isAnimating()).toBe(false); }); it("does not animate under prefers-reduced-motion", () => { const flip = createReorderFlip({ childSelector: "[data-id]" }); const container = makeContainer(["a", "b"]); const [, b] = container.children; flip.sync(container, { reset: true }); setRect(b, { left: 100 }); reduceMotion = true; flip.sync(container, {}); expect(b.style.transition).toBe("none"); expect(flip.isAnimating()).toBe(false); }); }); describe("useReorderFlip", () => { // Mounts a real grid so the hook's ref attaches to a live container and its // layout effect actually calls sync() against the tiles. function Grid({ orderKey, resetKey, ids }) { const ref = useReorderFlip({ orderKey, resetKey, childSelector: "[data-id]", }); return (
{ids.map(id => (
))}
); } beforeEach(() => { window.matchMedia = jest.fn(() => ({ matches: false })); }); it("animates a tile when the order changes", () => { const ids = ["a", "b"]; const { container, rerender } = render( ); const [a, b] = container.firstChild.children; // Mount established the baseline; move a tile then change the order. setRect(b, { left: 100 }); rerender(); expect(b.style.transition).toContain("transform"); expect(a.style.transition).toBe("none"); }); it("does not animate on a drag start alone (only resetKey changed)", () => { const ids = ["a", "b"]; const { container, rerender } = render( ); const [, b] = container.firstChild.children; // A drag start bumps resetKey but not orderKey: refresh baseline, no slide. setRect(b, { left: 100 }); rerender(); expect(b.style.transition).toBe("none"); }); });