Files
portifolio-evandrodevbr/app/components/showcase/ProjectReel.paths.test.tsx
T
evandro bcdf0099c5
CI / audit (push) Failing after 5m27s
CI / e2e (push) Failing after 11m23s
CI / verify (push) Successful in 11m31s
CI / deploy (push) Successful in 1m19s
feat(cases): adiciona o case Julia Beauty com vídeo de rolagem
2026-10-24 04:50:58 -03:00

836 lines
25 KiB
TypeScript

import { act, fireEvent, render, screen, within } from "@testing-library/react";
import { renderToString } from "react-dom/server";
import { MOBILE_QUERY } from "@/lib/device";
import { getFeaturedProjects } from "@/lib/portfolio-data";
import { getShowcaseConfig } from "@/lib/project-showcase";
import { ProjectReel } from "./ProjectReel";
import { ShowcaseProvider } from "./ShowcaseProvider";
import { reelOffset } from "./showcase-math";
import { PINNED_QUERY } from "./useShowcaseMedia";
// o pacote da vitrine fixa e o da janela ampliada chegam sob demanda (React.lazy): aqui entram já, para o teste não esperar
jest.mock("./PinnedReelLazy", () => ({
PinnedReelLazy: jest.requireActual("./PinnedReel").default,
}));
jest.mock("./ProjectLightboxLazy", () => ({
ProjectLightboxLazy: jest.requireActual("./ProjectLightbox").ProjectLightbox,
}));
/**
* Testes de caminhos-base de ProjectReel (complemento de ProjectReel.test.tsx):
* - qual versão da vitrine entra (fixa só com largura e altura de desktop e sem movimento reduzido; lista em
* janela baixa; carrossel no celular; nada sem projetos) e a troca ao vivo quando a janela muda;
* - measure(): vitrine escondida por CSS, trilho sem rolagem;
* - a faixa de projetos que acompanha o ativo, a descarga de vídeos, o seek dos vídeos, o "prime" e a rolagem suave.
*/
let reducedMotion = false;
jest.mock("@/app/hooks/useReducedMotion", () => ({
useReducedMotion: () => reducedMotion,
}));
const HEADER = 60;
let containerHeight = 7000;
let pinHeight = 800;
let reelTop = HEADER;
let phoneWidth = 90;
/** Rolagem horizontal da faixa de projetos (tablet e celular). */
let stripScrollWidth = 0;
let stripClientWidth = 0;
let buttonOffsetLeft = 0;
let mediaNetworkState = 0;
type Listener = () => void;
const mediaState: Record<string, boolean> = {};
const mediaListeners = new Map<string, Set<Listener>>();
const originalMatchMedia = window.matchMedia;
function mockViewport({
phone = false,
pinned = !phone,
}: {
phone?: boolean;
pinned?: boolean;
} = {}) {
mediaState[MOBILE_QUERY] = phone;
mediaState[PINNED_QUERY] = pinned;
mediaListeners.clear();
window.matchMedia = jest.fn().mockImplementation((query: string) => ({
get matches() {
return Boolean(mediaState[query]);
},
media: query,
addEventListener: (_type: string, listener: Listener) => {
if (!mediaListeners.has(query)) mediaListeners.set(query, new Set());
mediaListeners.get(query)?.add(listener);
},
removeEventListener: (_type: string, listener: Listener) => {
mediaListeners.get(query)?.delete(listener);
},
}));
}
function changeMedia(query: string, matches: boolean) {
mediaState[query] = matches;
act(() => {
for (const listener of mediaListeners.get(query) ?? []) listener();
});
}
const projects = getFeaturedProjects(null);
const WEIGHTS = [
"sev-modaintima",
"lavacao-047",
"nutriscan",
"sc-plus",
"as-servicos",
"julia-beauty",
].map((id) => getShowcaseConfig(id)?.weight ?? 1);
const originalRect = Element.prototype.getBoundingClientRect;
const descriptors = {
offsetHeight: Object.getOwnPropertyDescriptor(
HTMLElement.prototype,
"offsetHeight",
),
offsetWidth: Object.getOwnPropertyDescriptor(
HTMLElement.prototype,
"offsetWidth",
),
offsetLeft: Object.getOwnPropertyDescriptor(
HTMLElement.prototype,
"offsetLeft",
),
scrollWidth: Object.getOwnPropertyDescriptor(
Element.prototype,
"scrollWidth",
),
clientWidth: Object.getOwnPropertyDescriptor(
Element.prototype,
"clientWidth",
),
networkState: Object.getOwnPropertyDescriptor(
HTMLMediaElement.prototype,
"networkState",
),
};
beforeAll(() => {
Object.defineProperty(HTMLElement.prototype, "offsetHeight", {
configurable: true,
get(this: HTMLElement) {
if (this.classList.contains("ds-reel")) return containerHeight;
if (this.classList.contains("ds-reel-pin")) return pinHeight;
return 0;
},
});
Object.defineProperty(HTMLElement.prototype, "offsetWidth", {
configurable: true,
get(this: HTMLElement) {
return this.classList.contains("ds-reel-phone") ? phoneWidth : 0;
},
});
Object.defineProperty(HTMLElement.prototype, "offsetLeft", {
configurable: true,
get(this: HTMLElement) {
return this.tagName === "BUTTON" ? buttonOffsetLeft : 0;
},
});
Object.defineProperty(Element.prototype, "scrollWidth", {
configurable: true,
get(this: Element) {
return this.matches(".ds-reel-index ol") ? stripScrollWidth : 0;
},
});
Object.defineProperty(Element.prototype, "clientWidth", {
configurable: true,
get(this: Element) {
return this.matches(".ds-reel-index ol") ? stripClientWidth : 0;
},
});
Object.defineProperty(HTMLMediaElement.prototype, "networkState", {
configurable: true,
get: () => mediaNetworkState,
});
HTMLDialogElement.prototype.showModal = function showModal(
this: HTMLDialogElement,
) {
this.setAttribute("open", "");
};
HTMLDialogElement.prototype.close = function close(this: HTMLDialogElement) {
this.removeAttribute("open");
this.dispatchEvent(new Event("close"));
};
Element.prototype.getBoundingClientRect = function rect(this: Element) {
if (this.classList.contains("ds-reel"))
return {
top: reelTop,
bottom: reelTop + containerHeight,
height: containerHeight,
left: 0,
right: 1000,
width: 1000,
x: 0,
y: reelTop,
} as DOMRect;
return originalRect.call(this);
};
window.scrollTo = jest.fn();
});
afterAll(() => {
Element.prototype.getBoundingClientRect = originalRect;
for (const [key, target] of [
["offsetHeight", HTMLElement.prototype],
["offsetWidth", HTMLElement.prototype],
["offsetLeft", HTMLElement.prototype],
["scrollWidth", Element.prototype],
["clientWidth", Element.prototype],
["networkState", HTMLMediaElement.prototype],
] as const) {
const original = descriptors[key];
if (original) Object.defineProperty(target, key, original);
else Reflect.deleteProperty(target, key);
}
});
beforeEach(() => {
jest.useFakeTimers();
reducedMotion = false;
containerHeight = 7000;
pinHeight = 800;
reelTop = HEADER;
phoneWidth = 90;
stripScrollWidth = 0;
stripClientWidth = 0;
buttonOffsetLeft = 0;
mediaNetworkState = 0;
mockViewport();
(window.scrollTo as jest.Mock).mockClear();
jest.spyOn(HTMLMediaElement.prototype, "play").mockResolvedValue();
jest.spyOn(HTMLMediaElement.prototype, "pause").mockImplementation(() => {});
jest.spyOn(HTMLMediaElement.prototype, "load").mockImplementation(() => {});
});
afterEach(() => {
jest.useRealTimers();
jest.restoreAllMocks();
window.matchMedia = originalMatchMedia;
Reflect.deleteProperty(navigator, "userAgent");
Object.defineProperty(navigator, "connection", {
configurable: true,
value: undefined,
});
});
function renderReel(list = projects) {
const view = render(
<ShowcaseProvider projects={list}>
<ProjectReel />
</ShowcaseProvider>,
);
act(() => {
jest.advanceTimersByTime(50);
});
return view;
}
function scrollTo(progress: number) {
const scrollable = containerHeight - pinHeight;
reelTop = HEADER - progress * scrollable;
act(() => {
window.dispatchEvent(new Event("scroll"));
jest.advanceTimersByTime(1500);
});
}
const reelEl = () => document.querySelector(".ds-reel");
const listEl = () =>
screen.queryByRole("region", { name: "Projetos de clientes" });
const activeLayer = () =>
document.querySelector('.ds-reel-layer[data-state="active"]') as HTMLElement;
const caption = () => document.querySelector(".ds-reel-caption") as HTMLElement;
describe("ProjectReel: qual versão entra", () => {
it("path: no projects in the showcase -> renders nothing at all", () => {
const { container } = renderReel([]);
expect(container).toBeEmptyDOMElement();
});
it("path: wide and tall window, motion allowed -> the pinned reel, not the list", () => {
renderReel();
expect(reelEl()).not.toBeNull();
expect(listEl()).toBeNull();
});
it("path: wide but short window (below 561 px of height) -> desktop list, nothing pinned", () => {
mockViewport({ phone: false, pinned: false });
renderReel();
expect(reelEl()).toBeNull();
const list = listEl() as HTMLElement;
expect(list).not.toBeNull();
expect(list.querySelectorAll("li[data-device='desktop']")).toHaveLength(6);
expect(list.querySelector(".ds-phone")).toBeNull();
expect(list).not.toHaveAttribute("data-pending");
});
it("path: phone -> carousel list with the phone version of every item", () => {
mockViewport({ phone: true });
renderReel();
expect(reelEl()).toBeNull();
expect(
(listEl() as HTMLElement).querySelectorAll("li[data-device='mobile']"),
).toHaveLength(6);
});
it("path: phone width and reduced motion at once -> still the list", () => {
mockViewport({ phone: true });
reducedMotion = true;
renderReel();
expect(reelEl()).toBeNull();
expect(listEl()).not.toBeNull();
});
it("path: server render -> the list marked as pending plus the placeholder, with no media", () => {
const html = renderToString(
<ShowcaseProvider projects={projects}>
<ProjectReel />
</ShowcaseProvider>,
);
expect(html).toContain("data-pending");
expect(html).toContain("ds-reel-placeholder");
expect(html).not.toContain("<video");
expect(html).not.toContain("<img");
expect(html).not.toContain('data-device="');
});
it("path: the window shrinks below the pinned size -> the reel gives way to the list, and back", () => {
renderReel();
expect(reelEl()).not.toBeNull();
changeMedia(PINNED_QUERY, false);
expect(reelEl()).toBeNull();
expect(listEl()).not.toBeNull();
changeMedia(PINNED_QUERY, true);
expect(reelEl()).not.toBeNull();
expect(listEl()).toBeNull();
});
it("path: the window crosses the phone breakpoint while the list shows -> the list switches versions", () => {
mockViewport({ phone: false, pinned: false });
renderReel();
expect(
(listEl() as HTMLElement).querySelectorAll("li[data-device='desktop']"),
).toHaveLength(6);
changeMedia(MOBILE_QUERY, true);
expect(
(listEl() as HTMLElement).querySelectorAll("li[data-device='mobile']"),
).toHaveLength(6);
});
});
describe("ProjectReel: measure()", () => {
it("path: the reel is hidden by CSS (height 0) -> nothing is measured or downloaded (and it is when visible)", () => {
const attachedSources = () =>
[...document.querySelectorAll(".ds-reel-layer video")].filter((video) =>
video.getAttribute("src"),
);
containerHeight = 0;
const hidden = renderReel();
scrollTo(0.5);
expect(attachedSources()).toHaveLength(0);
hidden.unmount();
containerHeight = 7000;
renderReel();
scrollTo(0.5);
expect(attachedSources().length).toBeGreaterThan(0);
});
it("path: the pinned scene is as tall as the track (nothing to scroll) -> stays on the first project", () => {
containerHeight = pinHeight;
renderReel();
scrollTo(0.9);
expect(activeLayer().querySelector(".ds-bframe-url")).toHaveTextContent(
"sevmodaintima.com.br",
);
expect(caption()).toHaveTextContent("SEV Moda Íntima");
});
it("path: scrolled to a later project -> that project becomes the active layer", () => {
renderReel();
scrollTo(reelOffset(WEIGHTS, 3, 0.5));
expect(
document.querySelector('.ds-reel-index [aria-current="true"]'),
).toHaveAttribute("aria-label", "SC Plus Aquicultura");
});
});
describe("ProjectReel: faixa de projetos que acompanha o ativo", () => {
it("path: strip wider than its box -> scrolls the active button into view (minus 16 px)", () => {
stripScrollWidth = 900;
stripClientWidth = 300;
buttonOffsetLeft = 140;
const scrollToStrip = jest.fn();
HTMLOListElement.prototype.scrollTo = scrollToStrip;
try {
renderReel();
expect(scrollToStrip).toHaveBeenCalledWith({
left: 124,
behavior: "smooth",
});
} finally {
Reflect.deleteProperty(HTMLOListElement.prototype, "scrollTo");
}
});
it("path: the active button sits near the start -> never scrolls to a negative position", () => {
stripScrollWidth = 900;
stripClientWidth = 300;
buttonOffsetLeft = 4;
const scrollToStrip = jest.fn();
HTMLOListElement.prototype.scrollTo = scrollToStrip;
try {
renderReel();
expect(scrollToStrip).toHaveBeenCalledWith({
left: 0,
behavior: "smooth",
});
} finally {
Reflect.deleteProperty(HTMLOListElement.prototype, "scrollTo");
}
});
it("path: strip fits in its box -> no scrolling", () => {
stripScrollWidth = 300;
stripClientWidth = 300;
const scrollToStrip = jest.fn();
HTMLOListElement.prototype.scrollTo = scrollToStrip;
try {
renderReel();
expect(scrollToStrip).not.toHaveBeenCalled();
} finally {
Reflect.deleteProperty(HTMLOListElement.prototype, "scrollTo");
}
});
it("path: the browser has no Element.scrollTo -> no error and no scrolling", () => {
stripScrollWidth = 900;
stripClientWidth = 300;
expect(() => renderReel()).not.toThrow();
});
});
describe("ProjectReel: vídeos", () => {
const videos = () => [
...document.querySelectorAll<HTMLVideoElement>(".ds-reel-layer video"),
];
function ready(
video: HTMLVideoElement,
{ readyState = 4, duration = 30, seeking = false } = {},
) {
Object.defineProperty(video, "readyState", {
configurable: true,
value: readyState,
});
Object.defineProperty(video, "duration", {
configurable: true,
value: duration,
});
Object.defineProperty(video, "seeking", {
configurable: true,
value: seeking,
});
let time = 0;
Object.defineProperty(video, "currentTime", {
configurable: true,
get: () => time,
set: (value: number) => {
time = value;
},
});
}
it("path: videos that hold a file reference but are not attached -> released with load()", () => {
mediaNetworkState = 1;
reelTop = 6000; // longe da vitrine: ninguém fica anexado
renderReel();
expect(HTMLMediaElement.prototype.load).toHaveBeenCalled();
});
it("path: videos that never loaded anything (network empty) -> nothing to release", () => {
mediaNetworkState = 0;
reelTop = 6000;
renderReel();
expect(HTMLMediaElement.prototype.load).not.toHaveBeenCalled();
});
it("path: the attached project keeps its file while the others are released", () => {
mediaNetworkState = 1;
renderReel();
scrollTo(0);
jest.mocked(HTMLMediaElement.prototype.load).mockClear();
act(() => {
jest.advanceTimersByTime(400);
});
const attached = videos().filter((video) => video.getAttribute("src"));
expect(attached.length).toBeGreaterThan(0);
// quem soltou foi pedido a recarregar; quem ficou com src não foi tocado
const loaded = jest.mocked(HTMLMediaElement.prototype.load).mock
.instances as unknown as HTMLMediaElement[];
for (const video of attached) expect(loaded).not.toContain(video);
});
it("path: a video that is still seeking -> its time is left alone", () => {
renderReel();
for (const video of videos()) ready(video, { seeking: true });
scrollTo(reelOffset(WEIGHTS, 0, 0.5));
expect(videos()[0].currentTime).toBe(0);
});
it("path: a video whose metadata has not loaded (readyState 0) or has no duration -> never sought", () => {
renderReel();
const [first, second] = videos();
ready(first, { readyState: 0 });
ready(second, { duration: 0 });
scrollTo(reelOffset(WEIGHTS, 0, 0.5));
expect(first.currentTime).toBe(0);
expect(second.currentTime).toBe(0);
});
it("path: a video that is ready and idle -> sought to the scroll position", () => {
renderReel();
for (const video of videos()) ready(video);
scrollTo(reelOffset(WEIGHTS, 0, 0.5));
expect(videos()[0].currentTime).toBeGreaterThan(0);
});
it("path: Chrome (anything but WebKit) -> the video never plays: playing downloads the whole file, scrubbing only asks for what is seen", async () => {
renderReel();
const video = videos()[0];
fireEvent(video, new Event("loadeddata"));
await act(async () => {
await Promise.resolve();
});
expect(HTMLMediaElement.prototype.play).not.toHaveBeenCalled();
expect(HTMLMediaElement.prototype.pause).not.toHaveBeenCalled();
expect(video.dataset.primed).toBeUndefined();
});
it.each([
[
"iPhone (Safari)",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1",
],
[
"iPhone (Chrome, also WebKit)",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/126.0 Mobile/15E148 Safari/604.1",
],
[
"Safari on the Mac",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15",
],
])(
"path: %s (WebKit) -> primed once (play then pause) even if loadeddata fires twice",
async (_name, userAgent) => {
Object.defineProperty(navigator, "userAgent", {
configurable: true,
value: userAgent,
});
renderReel();
const video = videos()[0];
fireEvent(video, new Event("loadeddata"));
fireEvent(video, new Event("loadeddata"));
await act(async () => {
await Promise.resolve();
});
expect(HTMLMediaElement.prototype.play).toHaveBeenCalledTimes(1);
expect(HTMLMediaElement.prototype.pause).toHaveBeenCalledTimes(1);
expect(video.dataset.primed).toBe("true");
},
);
it("path: priming is refused by the browser -> swallowed, no pause", async () => {
Object.defineProperty(navigator, "userAgent", {
configurable: true,
value:
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1",
});
jest
.mocked(HTMLMediaElement.prototype.play)
.mockRejectedValue(new DOMException("blocked", "NotAllowedError"));
renderReel();
fireEvent(videos()[0], new Event("loadeddata"));
await act(async () => {
await Promise.resolve();
});
expect(HTMLMediaElement.prototype.pause).not.toHaveBeenCalled();
});
it("path: metadata loads before any scroll work (reel hidden by CSS, nothing measured) -> sought to the start, no error", () => {
containerHeight = 0;
renderReel();
const video = videos()[0];
ready(video);
expect(() => fireEvent(video, new Event("loadedmetadata"))).not.toThrow();
expect(video.currentTime).toBe(0);
});
it("path: metadata loads when the scroll target is known but no frame ran yet (reel far away) -> uses the target", () => {
reelTop = 6000;
renderReel();
const video = videos()[0];
ready(video);
fireEvent(video, new Event("loadedmetadata"));
expect(video.currentTime).toBe(0);
});
it("path: metadata loads after the scroll already moved -> the video jumps to where the scroll is", () => {
renderReel();
scrollTo(reelOffset(WEIGHTS, 0, 0.5));
const video = videos()[0];
ready(video);
fireEvent(video, new Event("loadedmetadata"));
expect(video.currentTime).toBeGreaterThan(0);
});
});
describe("ProjectReel: estado das camadas, traços de capítulo e legenda", () => {
const states = (selector: string, attribute = "data-state") =>
[...document.querySelectorAll(selector)].map((element) =>
element.getAttribute(attribute),
);
it("path: a middle project in scene -> layers before it are 'before', the ones after are 'after'", () => {
renderReel();
scrollTo(reelOffset(WEIGHTS, 2, 0.5));
const layers = states(".ds-reel-layer");
expect(layers.length).toBeGreaterThan(3);
expect(layers).toEqual(
layers.map((_, i) => (i < 2 ? "before" : i === 2 ? "active" : "after")),
);
});
it("path: first project, first chapter -> the ticks read now, todo, todo", () => {
renderReel();
scrollTo(0);
expect(states(".ds-reel-ticks li")).toEqual(["now", "todo"]);
expect(caption().querySelector(".ds-reel-chapter-count")).toHaveTextContent(
"01/02",
);
});
it("path: end of the project -> the chapter ticks before the current one are 'done', the ones after 'todo'", () => {
renderReel();
scrollTo(reelOffset(WEIGHTS, 0, 0.95));
const ticks = states(".ds-reel-ticks li");
const current = ticks.indexOf("now");
expect(current).toBeGreaterThan(0);
expect(ticks).toEqual(
ticks.map((_, i) =>
i < current ? "done" : i === current ? "now" : "todo",
),
);
expect(ticks.filter((state) => state === "done")).toHaveLength(current);
});
it("path: project with a subtitle -> shown under the title", () => {
expect(projects[0].subtitle).toBeTruthy();
renderReel();
expect(caption().querySelector(".ds-reel-sub")).toHaveTextContent(
projects[0].subtitle,
);
});
it("path: project without a subtitle -> no empty subtitle line in the caption", () => {
renderReel([{ ...projects[0], subtitle: "" }, ...projects.slice(1)]);
expect(caption().querySelector(".ds-reel-sub")).toBeNull();
expect(caption().querySelector(".ds-reel-title")).toHaveTextContent(
projects[0].title,
);
});
});
describe("ProjectReel: rolagem suave até um projeto", () => {
function pressProject(name: string) {
fireEvent.click(
within(
screen.getByRole("navigation", { name: "Projetos da vitrine" }),
).getByRole("button", { name }),
);
}
it("path: pressing a project -> scrolls in animation frames and ends exactly on the target", () => {
renderReel();
pressProject("SC Plus Aquicultura");
act(() => {
jest.advanceTimersByTime(2000);
});
const calls = (window.scrollTo as jest.Mock).mock.calls;
expect(calls.length).toBeGreaterThan(2);
for (const [options] of calls) expect(options.behavior).toBe("instant");
const tops = calls.map(([options]) => options.top as number);
expect(tops).toEqual([...tops].sort((a, b) => a - b));
expect(tops[tops.length - 1]).toBeGreaterThan(tops[0]);
});
it("path: the wheel during the scroll -> the animation stops where it is", () => {
renderReel();
pressProject("SC Plus Aquicultura");
act(() => {
jest.advanceTimersByTime(100);
});
act(() => {
window.dispatchEvent(new Event("wheel"));
});
const stoppedAt = (window.scrollTo as jest.Mock).mock.calls.length;
act(() => {
jest.advanceTimersByTime(2000);
});
expect((window.scrollTo as jest.Mock).mock.calls).toHaveLength(stoppedAt);
});
it("path: touching the screen during the scroll -> the animation stops too", () => {
renderReel();
pressProject("SC Plus Aquicultura");
act(() => {
jest.advanceTimersByTime(100);
});
act(() => {
window.dispatchEvent(new Event("touchstart"));
});
const stoppedAt = (window.scrollTo as jest.Mock).mock.calls.length;
act(() => {
jest.advanceTimersByTime(2000);
});
expect((window.scrollTo as jest.Mock).mock.calls).toHaveLength(stoppedAt);
});
it("path: a second press while the first scroll runs -> the first is cancelled, only the new target is reached", () => {
renderReel();
pressProject("AS Serviços");
act(() => {
jest.advanceTimersByTime(100);
});
(window.scrollTo as jest.Mock).mockClear();
pressProject("Lavação 047");
act(() => {
jest.advanceTimersByTime(2000);
});
const tops = (window.scrollTo as jest.Mock).mock.calls.map(
([options]) => options.top as number,
);
const finalTop = tops[tops.length - 1];
// o destino da Lavação (2º projeto) fica bem antes do da AS Serviços (5º)
expect(finalTop).toBeLessThan(reelTopFor(4));
});
function reelTopFor(index: number) {
const scrollable = containerHeight - pinHeight;
return reelOffset(WEIGHTS, index, 0) * scrollable;
}
});
describe("ProjectReel: altura do cabeçalho descontada na rolagem até um projeto", () => {
function pressAndLand(name: string) {
(window.scrollTo as jest.Mock).mockClear();
const view = renderReel();
fireEvent.click(
within(
screen.getByRole("navigation", { name: "Projetos da vitrine" }),
).getByRole("button", { name }),
);
act(() => {
jest.advanceTimersByTime(2000);
});
const calls = (window.scrollTo as jest.Mock).mock.calls;
view.unmount();
return calls[calls.length - 1][0].top as number;
}
function setWidth(width: number) {
Object.defineProperty(window, "innerWidth", {
configurable: true,
value: width,
});
}
afterEach(() => {
document.documentElement.style.removeProperty("--site-header-height");
});
it("path: no --site-header-height -> 60 px; set later at the same window width -> still the cached value; a new width -> read again", () => {
setWidth(1111);
const withoutVariable = pressAndLand("SC Plus Aquicultura");
document.documentElement.style.setProperty("--site-header-height", "80px");
const sameWidth = pressAndLand("SC Plus Aquicultura");
expect(sameWidth).toBe(withoutVariable);
setWidth(1112);
const newWidth = pressAndLand("SC Plus Aquicultura");
// o cabeçalho de 80 px deixa 20 px a menos de rolagem do que o de 60 px
expect(newWidth).toBe(withoutVariable - 20);
});
});