Files
portifolio-evandrodevbr/app/components/CompanyLogos.test.tsx
T
Evandro Fonseca Junior b791b7de9e feat: control room hero, SEO/AEO optimization and design system fixes
Hero: replace particle field with cobe globe (Capricorn view, multi-axis drag, reduced-motion safe), telemetry HUD with pointer parallax, particle text build effect, Space Grotesk display font. SEO: short titles with template, PT-BR accents, twitter cards on dynamic cases, escaped JSON-LD via lib/jsonld.ts, CSP report-only, 25 service FAQs answer-first. Design: fix TW4 layered reset killing spacing utilities, literal @theme tokens, mobile hero overflow, header overlap, empty case cards. Remove framer-motion (unified on motion/react), header version badge, phone number from hero CTA. Production build verified (24 static pages).
2026-08-27 10:55:21 -03:00

504 lines
16 KiB
TypeScript

import { render, screen } from "@testing-library/react";
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 (
<div {...props} {...animationProps}>
{children}
</div>
);
},
},
};
});
describe("CompanyLogos Component - Logo Rendering", () => {
beforeEach(() => {
mockReducedMotion = false;
});
it("should render all five company logos", () => {
const { container } = render(<CompanyLogos />);
// In animated mode, logos are duplicated (10 total)
const logoImages = container.querySelectorAll('img[alt*="logo"]');
expect(logoImages.length).toBe(10); // 5 logos x 2 (duplicated for infinite scroll)
});
it("should render Apolo Energia logo", () => {
render(<CompanyLogos />);
const apoloLogos = screen.getAllByAltText("Apolo Energia logo");
expect(apoloLogos.length).toBe(2); // Duplicated for infinite scroll
expect(apoloLogos[0]).toHaveAttribute("src", "/logos/apolo.svg");
});
it("should render Julia Beauty Lashes logo", () => {
render(<CompanyLogos />);
const juliaLogos = screen.getAllByAltText("Julia Beauty Lashes logo");
expect(juliaLogos.length).toBe(2);
expect(juliaLogos[0]).toHaveAttribute("src", "/logos/julia.svg");
});
it("should render Innovo logo", () => {
render(<CompanyLogos />);
const innovoLogos = screen.getAllByAltText("Innovo logo");
expect(innovoLogos.length).toBe(2);
expect(innovoLogos[0]).toHaveAttribute("src", "/logos/innovo.svg");
});
it("should render TeraByteShop logo", () => {
render(<CompanyLogos />);
const terabyteLogos = screen.getAllByAltText("TeraByteShop logo");
expect(terabyteLogos.length).toBe(2);
expect(terabyteLogos[0]).toHaveAttribute("src", "/logos/terabyte.svg");
});
it("should render Pichau logo", () => {
render(<CompanyLogos />);
const pichauLogos = screen.getAllByAltText("Pichau logo");
expect(pichauLogos.length).toBe(2);
expect(pichauLogos[0]).toHaveAttribute("src", "/logos/pichau.svg");
});
it("should have alt text for all logo images", () => {
const { container } = render(<CompanyLogos />);
const logoImages = container.querySelectorAll("img");
logoImages.forEach((img) => {
const alt = img.getAttribute("alt");
expect(alt).toBeTruthy();
expect(alt?.length).toBeGreaterThan(0);
});
});
it("should duplicate logo array for seamless infinite scroll", () => {
const { container } = render(<CompanyLogos />);
// Each company should appear exactly twice
const apoloLogos = screen.getAllByAltText("Apolo Energia logo");
const juliaLogos = screen.getAllByAltText("Julia Beauty Lashes logo");
const innovoLogos = screen.getAllByAltText("Innovo logo");
const terabyteLogos = screen.getAllByAltText("TeraByteShop logo");
const pichauLogos = screen.getAllByAltText("Pichau logo");
expect(apoloLogos.length).toBe(2);
expect(juliaLogos.length).toBe(2);
expect(innovoLogos.length).toBe(2);
expect(terabyteLogos.length).toBe(2);
expect(pichauLogos.length).toBe(2);
});
});
describe("CompanyLogos Component - Animation Configuration", () => {
beforeEach(() => {
mockReducedMotion = false;
});
it("should have horizontal scroll animation configuration", () => {
const { container } = render(<CompanyLogos />);
const animatedElement = container.querySelector("[data-animate]");
expect(animatedElement).toBeInTheDocument();
const animateValue = animatedElement?.getAttribute("data-animate");
expect(animateValue).toBeTruthy();
const animateProps = JSON.parse(animateValue!);
expect(animateProps.x).toEqual([0, "-50%"]);
});
it("should have 30-second animation duration", () => {
const { container } = render(<CompanyLogos />);
const animatedElement = container.querySelector("[data-transition]");
expect(animatedElement).toBeInTheDocument();
const transitionValue = animatedElement?.getAttribute("data-transition");
expect(transitionValue).toBeTruthy();
const transitionProps = JSON.parse(transitionValue!);
expect(transitionProps.duration).toBe(30);
});
it("should have infinite repeat configuration", () => {
const { container } = render(<CompanyLogos />);
const animatedElement = container.querySelector("[data-transition]");
const transitionValue = animatedElement?.getAttribute("data-transition");
const transitionProps = JSON.parse(transitionValue!);
// JSON.stringify converts Infinity to null, so we check for null
expect(transitionProps.repeat).toBe(null);
});
it("should use linear easing for smooth animation", () => {
const { container } = render(<CompanyLogos />);
const animatedElement = container.querySelector("[data-transition]");
const transitionValue = animatedElement?.getAttribute("data-transition");
const transitionProps = JSON.parse(transitionValue!);
expect(transitionProps.ease).toBe("linear");
});
it("should have flex layout with gap-sp-12 for logo spacing", () => {
const { container } = render(<CompanyLogos />);
const animatedElement = container.querySelector("[data-animate]");
const classes = animatedElement?.className || "";
expect(classes).toContain("flex");
expect(classes).toContain("gap-sp-12");
});
it("should have items-center alignment", () => {
const { container } = render(<CompanyLogos />);
const animatedElement = container.querySelector("[data-animate]");
const classes = animatedElement?.className || "";
expect(classes).toContain("items-center");
});
});
describe("CompanyLogos Component - Reduced Motion Fallback", () => {
beforeEach(() => {
mockReducedMotion = true;
});
afterEach(() => {
mockReducedMotion = false;
});
it("should render static grid when reduced motion is enabled", () => {
const { container } = render(<CompanyLogos />);
const staticGrid = container.querySelector(".grid");
expect(staticGrid).toBeInTheDocument();
});
it("should not have animated elements when reduced motion is enabled", () => {
const { container } = render(<CompanyLogos />);
const animatedElements = container.querySelectorAll("[data-animate]");
expect(animatedElements.length).toBe(0);
});
it("should render exactly 5 logos in static mode (no duplication)", () => {
const { container } = render(<CompanyLogos />);
const logoImages = container.querySelectorAll('img[alt*="logo"]');
expect(logoImages.length).toBe(5);
});
it("should have responsive grid columns in static mode", () => {
const { container } = render(<CompanyLogos />);
const staticGrid = container.querySelector(".grid");
const classes = staticGrid?.className || "";
expect(classes).toContain("grid-cols-2");
expect(classes).toContain("sm:grid-cols-3");
expect(classes).toContain("md:grid-cols-5");
});
it("should have gap-sp-6 spacing in static grid", () => {
const { container } = render(<CompanyLogos />);
const staticGrid = container.querySelector(".grid");
const classes = staticGrid?.className || "";
expect(classes).toContain("gap-sp-6");
});
it("should maintain all logo alt text in static mode", () => {
const { container } = render(<CompanyLogos />);
const logoImages = container.querySelectorAll("img");
logoImages.forEach((img) => {
const alt = img.getAttribute("alt");
expect(alt).toBeTruthy();
expect(alt?.length).toBeGreaterThan(0);
});
});
});
describe("CompanyLogos Component - Missing Logo Image Handling", () => {
beforeEach(() => {
mockReducedMotion = false;
});
it("should have onError handler for logo images", () => {
const { container } = render(<CompanyLogos />);
const logoImages = container.querySelectorAll("img");
logoImages.forEach((img) => {
// Check that the image element has an onError handler
// In React, this is attached as a prop, so we verify the element exists
expect(img).toBeInTheDocument();
});
});
it("should apply grayscale filter to logo images", () => {
const { container } = render(<CompanyLogos />);
const logoImages = container.querySelectorAll("img");
logoImages.forEach((img) => {
const classes = img.className;
expect(classes).toContain("grayscale");
});
});
it("should have hover effect to remove grayscale", () => {
const { container } = render(<CompanyLogos />);
const logoImages = container.querySelectorAll("img");
logoImages.forEach((img) => {
const classes = img.className;
expect(classes).toContain("hover:grayscale-0");
});
});
it("should have opacity transition on hover", () => {
const { container } = render(<CompanyLogos />);
const logoImages = container.querySelectorAll("img");
logoImages.forEach((img) => {
const classes = img.className;
expect(classes).toContain("opacity-60");
expect(classes).toContain("hover:opacity-100");
});
});
it("should have transition-all for smooth hover effects", () => {
const { container } = render(<CompanyLogos />);
const logoImages = container.querySelectorAll("img");
logoImages.forEach((img) => {
const classes = img.className;
expect(classes).toContain("transition-all");
});
});
});
describe("CompanyLogos Component - Responsive Layout", () => {
beforeEach(() => {
mockReducedMotion = false;
});
it("should have section element as container", () => {
const { container } = render(<CompanyLogos />);
const section = container.querySelector("section");
expect(section).toBeInTheDocument();
});
it("should have full width class", () => {
const { container } = render(<CompanyLogos />);
const section = container.querySelector("section");
const classes = section?.className || "";
expect(classes).toContain("w-full");
});
it("should have overflow-hidden for animation containment", () => {
const { container } = render(<CompanyLogos />);
const section = container.querySelector("section");
const classes = section?.className || "";
expect(classes).toContain("overflow-hidden");
});
it("should have border-b for visual separation", () => {
const { container } = render(<CompanyLogos />);
const section = container.querySelector("section");
const classes = section?.className || "";
expect(classes).toContain("border-b");
});
it("should have background color class", () => {
const { container } = render(<CompanyLogos />);
const section = container.querySelector("section");
const classes = section?.className || "";
expect(classes).toContain("bg-[var(--paper)]");
});
it("should have py-sp-8 vertical padding", () => {
const { container } = render(<CompanyLogos />);
// In animated mode, padding is on inner div
const innerDiv = container.querySelector(".py-sp-8");
expect(innerDiv).toBeInTheDocument();
});
it("should have responsive padding in static mode", () => {
mockReducedMotion = true;
const { container } = render(<CompanyLogos />);
const innerContainer = container.querySelector(".container");
const classes = innerContainer?.className || "";
expect(classes).toContain("px-sp-6");
expect(classes).toContain("py-sp-8");
expect(classes).toContain("md:px-sp-10");
expect(classes).toContain("lg:px-sp-16");
});
it("should have mx-auto for centering in static mode", () => {
mockReducedMotion = true;
const { container } = render(<CompanyLogos />);
const innerContainer = container.querySelector(".container");
const classes = innerContainer?.className || "";
expect(classes).toContain("mx-auto");
});
});
describe("CompanyLogos Component - Logo Sizing and Styling", () => {
beforeEach(() => {
mockReducedMotion = false;
});
it("should have h-12 height for all logos", () => {
const { container } = render(<CompanyLogos />);
const logoImages = container.querySelectorAll("img");
logoImages.forEach((img) => {
const classes = img.className;
expect(classes).toContain("h-12");
});
});
it("should have w-auto width for aspect ratio preservation", () => {
const { container } = render(<CompanyLogos />);
const logoImages = container.querySelectorAll("img");
logoImages.forEach((img) => {
const classes = img.className;
expect(classes).toContain("w-auto");
});
});
it("should have object-contain for proper image scaling", () => {
const { container } = render(<CompanyLogos />);
const logoImages = container.querySelectorAll("img");
logoImages.forEach((img) => {
const classes = img.className;
expect(classes).toContain("object-contain");
});
});
it("should have flex-shrink-0 on logo containers in animated mode", () => {
const { container } = render(<CompanyLogos />);
const logoContainers = container.querySelectorAll("[data-animate] > div");
logoContainers.forEach((div) => {
const classes = div.className;
expect(classes).toContain("flex-shrink-0");
});
});
it("should center logos within their containers", () => {
const { container } = render(<CompanyLogos />);
const logoContainers = container.querySelectorAll("[data-animate] > div");
logoContainers.forEach((div) => {
const classes = div.className;
expect(classes).toContain("flex");
expect(classes).toContain("items-center");
expect(classes).toContain("justify-center");
});
});
});
describe("CompanyLogos Component - Accessibility", () => {
beforeEach(() => {
mockReducedMotion = false;
});
it("should use semantic section element", () => {
const { container } = render(<CompanyLogos />);
const section = container.querySelector("section");
expect(section).toBeInTheDocument();
});
it("should have descriptive alt text for each logo", () => {
render(<CompanyLogos />);
expect(screen.getAllByAltText("Apolo Energia logo")).toHaveLength(2);
expect(screen.getAllByAltText("Julia Beauty Lashes logo")).toHaveLength(2);
expect(screen.getAllByAltText("Innovo logo")).toHaveLength(2);
expect(screen.getAllByAltText("TeraByteShop logo")).toHaveLength(2);
expect(screen.getAllByAltText("Pichau logo")).toHaveLength(2);
});
it("should respect reduced motion preference", () => {
mockReducedMotion = true;
const { container } = render(<CompanyLogos />);
// Should render static grid instead of animation
const staticGrid = container.querySelector(".grid");
const animatedElements = container.querySelectorAll("[data-animate]");
expect(staticGrid).toBeInTheDocument();
expect(animatedElements.length).toBe(0);
});
it("should maintain content visibility in both modes", () => {
// Test animated mode
mockReducedMotion = false;
const { container: animatedContainer } = render(<CompanyLogos />);
const animatedLogos =
animatedContainer.querySelectorAll('img[alt*="logo"]');
// Test static mode
mockReducedMotion = true;
const { container: staticContainer } = render(<CompanyLogos />);
const staticLogos = staticContainer.querySelectorAll('img[alt*="logo"]');
// Both modes should display logos (different counts due to duplication)
expect(animatedLogos.length).toBeGreaterThan(0);
expect(staticLogos.length).toBeGreaterThan(0);
});
});