import { act, render, screen } from "@testing-library/react";
import { DrawLine } from "./animations/DrawLine";
import { LazyRevealImage } from "./animations/LazyRevealImage";
import { Reveal } from "./Reveal";
import { Section } from "./Section";
/**
* Entradas por rolagem sem biblioteca de animação: o componente só marca data-shown (ou data-loaded) quando o
* IntersectionObserver avisa e o CSS (globals.css) faz a transição. Aqui vale o contrato com o CSS: as classes,
* as variáveis e o momento em que as marcas aparecem.
*/
type Observer = {
callback: IntersectionObserverCallback;
observed: Element[];
};
let observers: Observer[] = [];
const original = global.IntersectionObserver;
beforeEach(() => {
observers = [];
Object.defineProperty(window, "innerHeight", {
configurable: true,
value: 800,
});
global.IntersectionObserver = class {
private readonly record: Observer;
constructor(callback: IntersectionObserverCallback) {
this.record = { callback, observed: [] };
observers.push(this.record);
}
observe(target: Element) {
this.record.observed.push(target);
}
unobserve() {}
disconnect() {}
takeRecords() {
return [];
}
} as unknown as typeof IntersectionObserver;
});
afterEach(() => {
global.IntersectionObserver = original;
});
/** Avisa que `target` entrou inteiro na janela. */
function reveal(target: Element) {
act(() => {
for (const observer of observers)
if (observer.observed.includes(target))
observer.callback(
[
{
isIntersecting: true,
intersectionRatio: 1,
intersectionRect: { height: 400 },
target,
} as unknown as IntersectionObserverEntry,
],
{} as IntersectionObserver,
);
});
}
describe("Reveal", () => {
it("renders its children and waits for the viewport to mark them as shown", () => {
const { container } = render(
Conteúdo
,
);
const wrapper = container.firstElementChild as HTMLElement;
expect(screen.getByText("Conteúdo")).toBeInTheDocument();
expect(wrapper).toHaveClass("ds-reveal");
expect(wrapper).not.toHaveAttribute("data-shown");
reveal(wrapper);
expect(wrapper).toHaveAttribute("data-shown");
});
it("hands the CSS its offset, duration and delay as custom properties", () => {
const { container } = render(
x
,
);
const wrapper = container.firstElementChild as HTMLElement;
expect(wrapper.style.getPropertyValue("--reveal-y")).toBe("40px");
expect(wrapper.style.getPropertyValue("--reveal-duration")).toBe("0.9s");
expect(wrapper.style.getPropertyValue("--reveal-delay")).toBe("0.12s");
expect(wrapper).toHaveClass("ds-reveal", "extra");
});
it("uses no inline opacity or transform: the hidden state lives in the CSS, so no script means visible content", () => {
const { container } = render(x);
const wrapper = container.firstElementChild as HTMLElement;
expect(wrapper.style.opacity).toBe("");
expect(wrapper.style.transform).toBe("");
expect(wrapper.style.clipPath).toBe("");
});
});
describe("DrawLine", () => {
it("draws once the line is mostly on screen, and takes its delay from the prop", () => {
const { container } = render();
const line = container.firstElementChild as HTMLElement;
expect(line).toHaveClass("ds-draw-line", "x");
expect(line.style.getPropertyValue("--reveal-delay")).toBe("0.16s");
expect(line).not.toHaveAttribute("data-shown");
reveal(line);
expect(line).toHaveAttribute("data-shown");
});
});
describe("LazyRevealImage", () => {
it("shows the picture (data-loaded) only after it has loaded", async () => {
const { container } = render(
,
);
const wrapper = container.firstElementChild as HTMLElement;
const image = screen.getByAltText("Foto");
expect(wrapper).toHaveClass("ds-lazy-reveal", "h-40");
expect(wrapper).not.toHaveAttribute("data-loaded");
// o next/image só repassa o onLoad depois de decodificar a imagem (uma microtarefa)
await act(async () => {
image.dispatchEvent(new Event("load"));
});
expect(wrapper).toHaveAttribute("data-loaded");
});
it("does not wait forever for an image that was already complete before the page hydrated", async () => {
const complete = jest
.spyOn(HTMLImageElement.prototype, "complete", "get")
.mockReturnValue(true);
const width = jest
.spyOn(HTMLImageElement.prototype, "naturalWidth", "get")
.mockReturnValue(640);
let view: ReturnType | undefined;
await act(async () => {
view = render();
});
expect(view?.container.firstElementChild).toHaveAttribute("data-loaded");
complete.mockRestore();
width.mockRestore();
});
});
describe("Section", () => {
it("renders the heading, the description and the content, and marks only the header as it enters", () => {
const { container } = render(
lateral}
>
Corpo
,
);
const header = container.querySelector(".ds-section-header") as HTMLElement;
expect(container.querySelector("section#sobre")).toBeInTheDocument();
expect(
screen.getByRole("heading", { level: 2, name: "Quem faz" }),
).toBeInTheDocument();
expect(screen.getByText("Texto de apoio")).toHaveClass(
"ds-section-description",
"ds-reveal",
);
expect(screen.getByText("Sobre")).toHaveClass("ds-eyebrow", "ds-type");
// um