import { render } from "@testing-library/react"; import fc from "fast-check"; import CompanyLogos from "./CompanyLogos"; // Mock motion/react with configurable useReducedMotion let mockReducedMotion = false; jest.mock("motion/react", () => { const actual = jest.requireActual("motion/react"); return { ...actual, useReducedMotion: () => mockReducedMotion, motion: { div: ({ children, animate, transition, ...props }: any) => { // Capture animation props for testing const animationProps = { "data-animate": JSON.stringify(animate), "data-transition": JSON.stringify(transition), }; return (
{children}
); }, }, }; }); describe("Property 4: Reduced Motion Respect", () => { afterEach(() => { mockReducedMotion = false; }); it("should render static grid layout when reduced motion is enabled", () => { /** * **Validates: Requirements 3.6, 6.3** * * Property: For any reduced motion preference, when useReducedMotion() returns true, * the CompanyLogos component should render a static grid layout without motion * animations. This ensures accessibility compliance with prefers-reduced-motion. */ fc.assert( fc.property( fc.boolean(), // Random reduced motion preference (shouldReduce) => { mockReducedMotion = shouldReduce; const { container } = render(); // Check for animated motion.div with animation props const animatedElements = container.querySelectorAll("[data-animate]"); // Check for static grid layout const staticGrid = container.querySelector(".grid"); if (shouldReduce) { // When reduced motion is enabled: // - Should NOT have animated elements // - Should have static grid layout return animatedElements.length === 0 && staticGrid !== null; } else { // When reduced motion is disabled: // - Should have animated elements // - Should NOT have static grid layout return animatedElements.length > 0 && staticGrid === null; } }, ), { numRuns: 100 }, ); }); it("should exclude horizontal scroll animation when reduced motion is enabled", () => { /** * **Validates: Requirements 3.6, 6.3** * * Property: For any component render with reduced motion enabled, the horizontal * scroll animation (x: [0, '-50%']) should be completely absent. This verifies * that motion animations are properly disabled. */ fc.assert( fc.property( fc.constant(null), // No random input needed () => { mockReducedMotion = true; const { container } = render(); // Check for animation props const animatedElements = container.querySelectorAll("[data-animate]"); let hasScrollAnimation = false; animatedElements.forEach((element) => { const animateValue = element.getAttribute("data-animate"); if (animateValue && animateValue !== "undefined") { try { const props = JSON.parse(animateValue); // Check for x animation (horizontal scroll) if (props.x !== undefined) { hasScrollAnimation = true; } } catch (e) { // Skip invalid JSON } } }); // Should not have scroll animation when reduced motion is enabled return !hasScrollAnimation; }, ), { numRuns: 100 }, ); }); it("should display all company logos in both animated and static modes", () => { /** * **Validates: Requirements 3.6, 6.3** * * Property: For any reduced motion preference, all company logos should be * displayed. The reduced motion preference should only affect the animation * style, not the content visibility. */ fc.assert( fc.property( fc.boolean(), // Random reduced motion preference (shouldReduce) => { mockReducedMotion = shouldReduce; const { container } = render(); // Find all logo images const logoImages = container.querySelectorAll('img[alt*="logo"]'); // Should have at least 5 logos (the original set) // In animated mode, we have duplicates, so it will be 10 // In static mode, we have exactly 5 if (shouldReduce) { return logoImages.length === 5; } else { return logoImages.length === 10; // Duplicated for infinite scroll } }, ), { numRuns: 100 }, ); }); it("should maintain accessibility attributes regardless of reduced motion preference", () => { /** * **Validates: Requirements 3.6, 6.3, 6.2** * * Property: For any reduced motion preference, all company logo images should * maintain proper alt text attributes. Accessibility should not be compromised * by the animation mode. */ fc.assert( fc.property( fc.boolean(), // Random reduced motion preference (shouldReduce) => { mockReducedMotion = shouldReduce; const { container } = render(); // Find all logo images const logoImages = container.querySelectorAll("img"); // Verify we found images if (logoImages.length === 0) { return false; } // Check that all images have non-empty alt text return Array.from(logoImages).every((img) => { const alt = img.getAttribute("alt"); return alt !== null && alt.trim().length > 0; }); }, ), { numRuns: 100 }, ); }); it("should consistently apply reduced motion across multiple renders", () => { /** * **Validates: Requirements 3.6, 6.3** * * Property: For any number of component renders with reduced motion enabled, * the static layout should be consistently rendered without animations. * This verifies that the reduced motion behavior is stable across re-renders. */ fc.assert( fc.property( fc.integer({ min: 1, max: 5 }), // Test 1-5 renders (numRenders) => { mockReducedMotion = true; let allRendersValid = true; for (let i = 0; i < numRenders; i++) { const { container, unmount } = render(); // Check for animated elements const animatedElements = container.querySelectorAll("[data-animate]"); // Check for static grid const staticGrid = container.querySelector(".grid"); // Should have no animations and should have static grid if (animatedElements.length > 0 || staticGrid === null) { allRendersValid = false; } unmount(); } return allRendersValid; }, ), { numRuns: 100 }, ); }); it("should use grid layout classes when reduced motion is enabled", () => { /** * **Validates: Requirements 3.6, 6.3** * * Property: When reduced motion is enabled, the component should use CSS grid * layout classes (grid, grid-cols-*) to display logos in a static arrangement. * This ensures proper visual presentation without animation. */ fc.assert( fc.property(fc.constant(null), () => { mockReducedMotion = true; const { container } = render(); // Check for grid layout classes const gridContainer = container.querySelector(".grid"); if (!gridContainer) { return false; } // Verify grid has responsive column classes const hasGridCols = gridContainer.className.includes("grid-cols-"); return hasGridCols; }), { numRuns: 100 }, ); }); it("should not have infinite repeat transition when reduced motion is enabled", () => { /** * **Validates: Requirements 3.6, 6.3** * * Property: When reduced motion is enabled, the component should not have * transition configurations with infinite repeat. This ensures no continuous * motion occurs when users prefer reduced motion. */ fc.assert( fc.property(fc.constant(null), () => { mockReducedMotion = true; const { container } = render(); // Check for transition props const elementsWithTransition = container.querySelectorAll("[data-transition]"); let hasInfiniteRepeat = false; elementsWithTransition.forEach((element) => { const transitionValue = element.getAttribute("data-transition"); if (transitionValue && transitionValue !== "undefined") { try { const props = JSON.parse(transitionValue); // Check for infinite repeat if (props.repeat === Infinity || props.repeat === "Infinity") { hasInfiniteRepeat = true; } } catch (e) { // Skip invalid JSON } } }); // Should not have infinite repeat when reduced motion is enabled return !hasInfiniteRepeat; }), { numRuns: 100 }, ); }); it("should switch between animated and static modes based on reduced motion preference", () => { /** * **Validates: Requirements 3.6, 6.3** * * Property: The component should render different layouts based on the reduced * motion preference. When the preference changes, the component should adapt * accordingly. This verifies the conditional rendering logic. */ fc.assert( fc.property( fc.array(fc.boolean(), { minLength: 2, maxLength: 5 }), // Sequence of preference changes (preferenceSequence) => { let allTransitionsValid = true; preferenceSequence.forEach((shouldReduce) => { mockReducedMotion = shouldReduce; const { container, unmount } = render(); const hasAnimations = container.querySelectorAll("[data-animate]").length > 0; const hasStaticGrid = container.querySelector(".grid") !== null; // Verify correct mode is rendered if (shouldReduce) { if (hasAnimations || !hasStaticGrid) { allTransitionsValid = false; } } else { if (!hasAnimations || hasStaticGrid) { allTransitionsValid = false; } } unmount(); }); return allTransitionsValid; }, ), { numRuns: 100 }, ); }); });