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) => { return
{children}
; }, }, }; }); describe("Property 10: Alt Text for Images", () => { afterEach(() => { mockReducedMotion = false; }); it("should have non-empty alt attributes for all company logo images", () => { /** * **Validates: Requirements 6.2** * * Property: For any company logo image in the CompanyLogos component, * it should have a non-empty alt attribute describing the company. * This ensures accessibility for screen readers and assistive technologies. */ 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 have descriptive alt text that includes company name", () => { /** * **Validates: Requirements 6.2** * * Property: For any company logo image, the alt text should be descriptive * and include identifying information about the company. This ensures * meaningful accessibility information is provided. */ 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 alt text contains "logo" or is descriptive return Array.from(logoImages).every((img) => { const alt = img.getAttribute("alt"); if (!alt) return false; const altLower = alt.toLowerCase(); // Alt text should contain "logo" or be at least 5 characters (descriptive) return altLower.includes("logo") || alt.trim().length >= 5; }); }, ), { numRuns: 100 }, ); }); it("should maintain alt text consistency across multiple renders", () => { /** * **Validates: Requirements 6.2** * * Property: For any number of component renders, the alt text for each * company logo should remain consistent. This ensures stable accessibility * information across re-renders. */ fc.assert( fc.property( fc.integer({ min: 2, max: 5 }), // Test 2-5 renders fc.boolean(), // Random reduced motion preference (numRenders, shouldReduce) => { mockReducedMotion = shouldReduce; let firstRenderAltTexts: string[] = []; for (let i = 0; i < numRenders; i++) { const { container, unmount } = render(); const logoImages = container.querySelectorAll("img"); const currentAltTexts = Array.from(logoImages).map( (img) => img.getAttribute("alt") || "", ); if (i === 0) { firstRenderAltTexts = currentAltTexts; } else { // Compare with first render if (currentAltTexts.length !== firstRenderAltTexts.length) { unmount(); return false; } // Check that alt texts match (accounting for duplicates in animated mode) const expectedCount = shouldReduce ? 5 : 10; if (currentAltTexts.length !== expectedCount) { unmount(); return false; } } unmount(); } return true; }, ), { numRuns: 100 }, ); }); it("should have unique alt text for each distinct company", () => { /** * **Validates: Requirements 6.2** * * Property: For any set of company logos, each distinct company should have * its own unique alt text. This ensures each company is properly identified * for accessibility purposes. */ fc.assert( fc.property(fc.constant(null), () => { mockReducedMotion = true; // Use static mode to get unique logos const { container } = render(); const logoImages = container.querySelectorAll("img"); const altTexts = Array.from(logoImages).map( (img) => img.getAttribute("alt") || "", ); // In static mode, we should have 5 unique alt texts const uniqueAltTexts = new Set(altTexts); // Should have 5 unique alt texts (one per company) return uniqueAltTexts.size === 5 && altTexts.length === 5; }), { numRuns: 100 }, ); }); it("should preserve alt text in both animated and static modes", () => { /** * **Validates: Requirements 6.2** * * Property: For any reduced motion preference, all company logo images * should maintain their alt text attributes. The animation mode should * not affect accessibility attributes. */ fc.assert( fc.property( fc.boolean(), // Random reduced motion preference (shouldReduce) => { mockReducedMotion = shouldReduce; const { container } = render(); const logoImages = container.querySelectorAll("img"); // Verify all images have alt attributes const allHaveAlt = Array.from(logoImages).every((img) => { const alt = img.getAttribute("alt"); return alt !== null && alt.trim().length > 0; }); // Verify correct number of images based on mode const expectedCount = shouldReduce ? 5 : 10; const hasCorrectCount = logoImages.length === expectedCount; return allHaveAlt && hasCorrectCount; }, ), { numRuns: 100 }, ); }); it("should not have empty or whitespace-only alt text", () => { /** * **Validates: Requirements 6.2** * * Property: For any company logo image, the alt text should not be empty * or consist only of whitespace. This ensures meaningful accessibility * information is always provided. */ fc.assert( fc.property( fc.boolean(), // Random reduced motion preference (shouldReduce) => { mockReducedMotion = shouldReduce; const { container } = render(); const logoImages = container.querySelectorAll("img"); // Check that no image has empty or whitespace-only alt text return Array.from(logoImages).every((img) => { const alt = img.getAttribute("alt"); // Alt must exist and not be empty after trimming if (!alt || alt.trim().length === 0) { return false; } // Alt should not be just whitespace return alt !== alt.trim() ? false : true; }); }, ), { numRuns: 100 }, ); }); it("should have alt text that matches the expected company names", () => { /** * **Validates: Requirements 6.2** * * Property: For any company logo image, the alt text should correspond to * one of the expected company names (Apolo Energia, Julia Beauty Lashes, * Innovo, TeraByteShop, Pichau). This ensures correct identification. */ fc.assert( fc.property(fc.constant(null), () => { mockReducedMotion = true; // Use static mode for clarity const { container } = render(); const logoImages = container.querySelectorAll("img"); const altTexts = Array.from(logoImages).map( (img) => img.getAttribute("alt") || "", ); // Expected company names in alt text const expectedCompanies = [ "Apolo Energia", "Julia Beauty Lashes", "Innovo", "TeraByteShop", "Pichau", ]; // Check that each alt text contains one of the expected company names return altTexts.every((alt) => expectedCompanies.some((company) => alt.includes(company)), ); }), { numRuns: 100 }, ); }); it("should maintain alt text when images fail to load", () => { /** * **Validates: Requirements 6.2** * * Property: For any company logo image that fails to load, the alt text * should still be present in the DOM. This ensures accessibility is * maintained even when images are unavailable. */ fc.assert( fc.property( fc.boolean(), // Random reduced motion preference (shouldReduce) => { mockReducedMotion = shouldReduce; const { container } = render(); const logoImages = container.querySelectorAll("img"); // Verify all images have alt text before any error handling const allHaveAltBeforeError = Array.from(logoImages).every((img) => { const alt = img.getAttribute("alt"); return alt !== null && alt.trim().length > 0; }); // The alt attribute should be present regardless of load state return allHaveAltBeforeError; }, ), { numRuns: 100 }, ); }); });