feat(story): reel scrollytelling scrubbed por scroll com palco WebGL 3D
This commit is contained in:
1 parent
52960313f0
commit
8be2ba572d
4 files changed
+595
-1
No files matched your search
@@ -0,0 +1,80 @@
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import ScrollStory from "./ScrollStory";
|
||||
|
||||
// gsap é carregado via import() dinâmico dentro do effect; o contrato testado
|
||||
// aqui é markup do reel (cenas/HUD/alvos), não a timeline — que é verificada
|
||||
// no browser via probe CDP. Mock leve mantém a suíte em jsdom.
|
||||
jest.mock("gsap", () => {
|
||||
const chain = { set: jest.fn(), fromTo: jest.fn(), to: jest.fn() };
|
||||
Object.assign(chain, {
|
||||
set: jest.fn(() => chain),
|
||||
fromTo: jest.fn(() => chain),
|
||||
to: jest.fn(() => chain),
|
||||
});
|
||||
return {
|
||||
gsap: {
|
||||
registerPlugin: jest.fn(),
|
||||
set: jest.fn(),
|
||||
timeline: jest.fn(() => chain),
|
||||
ticker: { add: jest.fn(), remove: jest.fn(), lagSmoothing: jest.fn() },
|
||||
utils: { toArray: jest.fn(() => []) },
|
||||
context: jest.fn((fn: () => void) => {
|
||||
fn();
|
||||
return { revert: jest.fn() };
|
||||
}),
|
||||
},
|
||||
};
|
||||
});
|
||||
jest.mock("gsap/ScrollTrigger", () => ({
|
||||
ScrollTrigger: { getAll: jest.fn(() => []) },
|
||||
}));
|
||||
|
||||
const props = { projectsCount: 5, experiencesCount: 6, skillsCount: 18 };
|
||||
|
||||
function mockMatchMedia(matches: boolean) {
|
||||
Object.defineProperty(window, "matchMedia", {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation((query: string) => ({
|
||||
matches,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
describe("ScrollStory", () => {
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
mockMatchMedia(false);
|
||||
});
|
||||
|
||||
it("renderiza fallback empilhado com conteúdo real com reduced motion", () => {
|
||||
mockMatchMedia(true);
|
||||
const { container } = render(<ScrollStory {...props} />);
|
||||
// sem palco scrub: capítulos legíveis no fluxo, números reais do portfólio
|
||||
expect(container.querySelector("[data-story]")).not.toBeInTheDocument();
|
||||
expect(container).toHaveTextContent("01 — Visão geral");
|
||||
expect(container).toHaveTextContent("04 — Próximo ciclo");
|
||||
expect(container).toHaveTextContent("5");
|
||||
expect(screen.getAllByText(/sistemas ativos no portfólio/)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("renderiza o reel com 4 cenas, HUD e índice quando o engine liga", async () => {
|
||||
mockMatchMedia(false);
|
||||
const { container } = render(<ScrollStory {...props} />);
|
||||
await waitFor(() =>
|
||||
expect(container.querySelector("[data-story]")).toBeInTheDocument(),
|
||||
);
|
||||
expect(container.querySelectorAll("[data-story-scene]")).toHaveLength(4);
|
||||
expect(container.querySelector("[data-story-index]")).toHaveTextContent("01");
|
||||
expect(container.querySelector("[data-story-bar]")).toBeInTheDocument();
|
||||
// count-up é alimentado pela timeline: markup carrega o alvo real
|
||||
const num = container.querySelector("[data-story-num]");
|
||||
expect(num?.getAttribute("data-story-num-to")).toBe("5");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user