Files
portifolio-evandrodevbr/app/components/Experience.headingHierarchy.pbt.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

133 lines
3.6 KiB
TypeScript

import { render } from "@testing-library/react";
import fc from "fast-check";
import type { HTMLAttributes, ReactNode } from "react";
import type { PortfolioExperience } from "@/lib/portfolio-data";
import Experience from "./Experience";
type MotionProps = HTMLAttributes<HTMLElement> & {
initial?: unknown;
whileInView?: unknown;
viewport?: unknown;
variants?: unknown;
whileHover?: unknown;
whileTap?: unknown;
transition?: unknown;
};
type SectionMockProps = {
children?: ReactNode;
title?: string;
eyebrow?: string;
description?: string;
disableChildrenAnimation?: boolean;
id?: string;
className?: string;
};
jest.mock("motion/react", () => {
const createMotionElement =
(Tag: "div" | "article") =>
({
children,
initial: _initial,
whileInView: _whileInView,
viewport: _viewport,
variants: _variants,
whileHover: _whileHover,
whileTap: _whileTap,
transition: _transition,
...props
}: MotionProps & { children?: ReactNode }) => (
<Tag {...props}>{children}</Tag>
);
return {
motion: {
div: createMotionElement("div"),
article: createMotionElement("article"),
},
useReducedMotion: () => false,
};
});
jest.mock("./Section", () => ({
Section: ({
children,
title,
eyebrow: _eyebrow,
description: _description,
disableChildrenAnimation: _disableChildrenAnimation,
...props
}: SectionMockProps) => (
<section {...props}>
<h2>{title}</h2>
{children}
</section>
),
}));
jest.mock("./animations/DrawLine", () => ({
DrawLine: () => <span data-testid="draw-line" />,
}));
function makeExperience(index: number): PortfolioExperience {
return {
id: `experience-${index}`,
title: `Experience ${index}`,
company: "Company",
period: `${2020 + index} - Present`,
location: "Remote",
type: index % 2 === 0 ? "Software" : "Hardware",
description: "Description",
achievements: [],
technologies: [],
featured: true,
};
}
describe("Experience heading hierarchy", () => {
it("renders one h2 section heading and h3 headings for featured cards", () => {
fc.assert(
fc.property(fc.integer({ min: 1, max: 8 }), (count) => {
const experiences = Array.from({ length: count }, (_, index) =>
makeExperience(index),
);
const { container, unmount } = render(
<Experience experiences={experiences} />,
);
const headings = Array.from(
container.querySelectorAll("h1, h2, h3, h4, h5, h6"),
);
expect(headings[0]?.tagName).toBe("H2");
expect(container.querySelector("section > h2")).toHaveTextContent(
"Experiência em engenharia de software e sistemas críticos",
);
expect(container.querySelectorAll("section > h2")).toHaveLength(1);
expect(container.querySelectorAll("article h3")).toHaveLength(count);
expect(
new Set(
Array.from(container.querySelectorAll("article h3")).map(
(heading) => heading.textContent,
),
),
).toEqual(new Set(experiences.map((experience) => experience.title)));
expect(headings.some((heading) => heading.tagName === "H4")).toBe(
false,
);
unmount();
}),
{ numRuns: 25 },
);
});
it("renders no card headings when no experiences are supplied", () => {
const { container } = render(<Experience />);
expect(container.querySelector("section > h2")).toBeInTheDocument();
expect(container.querySelectorAll("article h3")).toHaveLength(0);
});
});