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 },
);
});
});