feat: harden portfolio and add SEO foundations
This commit is contained in:
1 parent
b079afdd73
commit
62bcfbd8fa
121 files changed
+7769
-10469
No files matched your search
@@ -1,18 +1,37 @@
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import Hero from "./Hero";
|
||||
|
||||
jest.mock("next/dynamic", () => () => {
|
||||
const DynamicComponent = ({ variant }: { variant?: string }) => (
|
||||
<div data-testid="hero-scene" data-variant={variant} />
|
||||
);
|
||||
const DynamicComponent = () => <div data-testid="hero-scene" />;
|
||||
DynamicComponent.displayName = "MockHeroScene";
|
||||
return DynamicComponent;
|
||||
});
|
||||
|
||||
jest.mock("motion/react", () => {
|
||||
const React = require("react");
|
||||
const MotionStateContext = React.createContext({
|
||||
initial: undefined,
|
||||
animate: undefined,
|
||||
});
|
||||
|
||||
const resolveOpacity = (
|
||||
value: unknown,
|
||||
variants: Record<string, { opacity?: number }> | undefined,
|
||||
) => {
|
||||
const state: unknown =
|
||||
typeof value === "string" ? variants?.[value] : value;
|
||||
if (
|
||||
state &&
|
||||
typeof state === "object" &&
|
||||
"opacity" in state &&
|
||||
typeof state.opacity === "number"
|
||||
) {
|
||||
return state.opacity;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const motion = new Proxy(
|
||||
{},
|
||||
@@ -22,26 +41,47 @@ jest.mock("motion/react", () => {
|
||||
children,
|
||||
initial,
|
||||
animate,
|
||||
variants,
|
||||
transition: _transition,
|
||||
whileHover: _whileHover,
|
||||
whileTap: _whileTap,
|
||||
variants: _variants,
|
||||
transition: _transition,
|
||||
style,
|
||||
...props
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
initial?: unknown;
|
||||
animate?: unknown;
|
||||
variants?: Record<string, { opacity?: number }>;
|
||||
style?: Record<string, unknown>;
|
||||
} & Record<string, unknown>) => {
|
||||
const dataProps = {
|
||||
...(initial !== undefined
|
||||
? { "data-initial": JSON.stringify(initial) }
|
||||
: {}),
|
||||
...(animate !== undefined
|
||||
? { "data-animate": JSON.stringify(animate) }
|
||||
: {}),
|
||||
};
|
||||
const inherited = React.useContext(MotionStateContext);
|
||||
const effectiveInitial =
|
||||
initial === undefined ? inherited.initial : initial;
|
||||
const effectiveAnimate =
|
||||
animate === undefined ? inherited.animate : animate;
|
||||
const opacity = resolveOpacity(
|
||||
effectiveInitial === false ? effectiveAnimate : effectiveInitial,
|
||||
variants,
|
||||
);
|
||||
const element = React.createElement(
|
||||
tag,
|
||||
{
|
||||
...props,
|
||||
style: opacity === undefined ? style : { ...style, opacity },
|
||||
},
|
||||
children,
|
||||
);
|
||||
|
||||
return React.createElement(tag, { ...props, ...dataProps }, children);
|
||||
return React.createElement(
|
||||
MotionStateContext.Provider,
|
||||
{
|
||||
value: {
|
||||
initial: effectiveInitial,
|
||||
animate: effectiveAnimate,
|
||||
},
|
||||
},
|
||||
element,
|
||||
);
|
||||
};
|
||||
Component.displayName = `motion.${tag}`;
|
||||
return Component;
|
||||
@@ -96,46 +136,31 @@ function mockMatchMedia(matches: boolean) {
|
||||
describe("Hero reduced motion", () => {
|
||||
beforeEach(() => {
|
||||
mockMatchMedia(true);
|
||||
jest.spyOn(Math, "random").mockReturnValue(0);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("keeps essential hero content visible with reduced motion enabled", async () => {
|
||||
it("shows active copy and actions immediately without animation timers", () => {
|
||||
render(<Hero />);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByTestId("hero-scene")).not.toBeInTheDocument(),
|
||||
);
|
||||
|
||||
expect(
|
||||
const visibleContent = [
|
||||
screen.getByText(/Disponível para novas oportunidades/i),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("heading", {
|
||||
level: 1,
|
||||
name: /engenharia de software para sistemas/i,
|
||||
}),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("button", { name: /conversar sobre uma oportunidade/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
screen.getByRole("button", {
|
||||
name: /conversar sobre uma oportunidade/i,
|
||||
}),
|
||||
screen.getByRole("link", { name: /ver casos selecionados/i }),
|
||||
screen.getByRole("link", { name: /download cv/i }),
|
||||
];
|
||||
|
||||
it("keeps motion blocks declarative instead of requiring 3D", async () => {
|
||||
const { container } = render(<Hero />);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByTestId("hero-scene")).not.toBeInTheDocument(),
|
||||
);
|
||||
|
||||
const rootMotionBlock = container.querySelector(
|
||||
"div[data-initial][data-animate]",
|
||||
);
|
||||
expect(rootMotionBlock).toBeInTheDocument();
|
||||
expect(rootMotionBlock?.getAttribute("data-initial")).toContain("hidden");
|
||||
expect(rootMotionBlock?.getAttribute("data-animate")).toContain("visible");
|
||||
for (const element of visibleContent) {
|
||||
expect(element).toBeVisible();
|
||||
}
|
||||
expect(screen.queryByTestId("hero-scene")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user