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).
147 lines
3.9 KiB
TypeScript
147 lines
3.9 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,
|
|
year: number,
|
|
period = `${year} - Present`,
|
|
): PortfolioExperience {
|
|
return {
|
|
id: `experience-${index}`,
|
|
title: `Experience ${index}`,
|
|
company: "Company",
|
|
period,
|
|
location: "Remote",
|
|
type: "Software",
|
|
description: "Description",
|
|
achievements: [],
|
|
technologies: [],
|
|
featured: true,
|
|
};
|
|
}
|
|
|
|
describe("Experience chronological ordering", () => {
|
|
it("sorts featured experiences by the first year in descending order", () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.array(fc.integer({ min: 1990, max: 2035 }), {
|
|
minLength: 1,
|
|
maxLength: 8,
|
|
}),
|
|
(years) => {
|
|
const experiences = years.map((year, index) =>
|
|
makeExperience(index, year),
|
|
);
|
|
const { container, unmount } = render(
|
|
<Experience experiences={experiences} />,
|
|
);
|
|
const renderedYears = Array.from(
|
|
container.querySelectorAll("article .ds-kicker"),
|
|
).map((element) => Number.parseInt(element.textContent ?? "0", 10));
|
|
const expectedExperiences = [...experiences].sort(
|
|
(a, b) =>
|
|
Number.parseInt(b.period, 10) - Number.parseInt(a.period, 10),
|
|
);
|
|
const expectedYears = expectedExperiences.map((experience) =>
|
|
Number.parseInt(experience.period, 10),
|
|
);
|
|
const renderedTitles = Array.from(
|
|
container.querySelectorAll("article h3"),
|
|
).map((element) => element.textContent);
|
|
|
|
expect(renderedYears).toEqual(expectedYears);
|
|
expect(renderedTitles).toEqual(
|
|
expectedExperiences.map((experience) => experience.title),
|
|
);
|
|
unmount();
|
|
},
|
|
),
|
|
{ numRuns: 25 },
|
|
);
|
|
});
|
|
|
|
it("sorts a period range by its first year", () => {
|
|
const experiences = [
|
|
makeExperience(1, 2022, "2022 - 2024"),
|
|
makeExperience(2, 2024, "2024 - Present"),
|
|
makeExperience(3, 2021, "2021 - 2023"),
|
|
];
|
|
const { container } = render(<Experience experiences={experiences} />);
|
|
|
|
expect(
|
|
Array.from(container.querySelectorAll("article .ds-kicker")).map(
|
|
(element) => element.textContent,
|
|
),
|
|
).toEqual(["2024 - Present", "2022 - 2024", "2021 - 2023"]);
|
|
});
|
|
});
|