Hero e movimento - cena 3D em tempo real com os blocos da marca (kit feito no Blender, 3d/blocos.py): marca, tela, integrações e dados; segue a rolagem, tem vida em repouso, reage ao cursor e, parada no topo, passa sozinha pelos estados com legenda - três níveis de aparelho (full, lite, static); sem WebGL fica o pôster - título novo: "Sites e sistemas sob medida para o jeito que a sua empresa trabalha." - faixa de clientes com o ramo, linha do primeiro passo, hero alinhado às seções - rótulos digitados, entrada dos blocos, cortina de blocos entre páginas e capa compartilhada entre a lista de cases e o case Ofertas - atendimento no WhatsApp com IA e nota fiscal para a reforma tributária (páginas de serviço, cartões da home, formulário e aviso com data de validade) - página /ia atualizada, com fontes - peças 3D em loop nos cartões e topos de página (3d/pecas.py) Design - hover por papel (bloco nos botões, barra nos links), rótulos sem caixa alta - um destino por rótulo: "Falar sobre meu projeto" leva ao formulário, "Chamar no WhatsApp" abre o WhatsApp - logo montado em blocos, 404 redesenhado, grades sem célula vazia - ícones do PWA e manifesto novos Specs em specs/, contexto de design em DESIGN.md e PRODUCT.md.
111 lines
3.7 KiB
TypeScript
111 lines
3.7 KiB
TypeScript
import { act, render } from "@testing-library/react";
|
|
import type { CSSProperties } from "react";
|
|
import HeroField, { DIVE_QUERY } from "./HeroField";
|
|
|
|
/*
|
|
* A altura do mergulho do hero vive num lugar só: `--hero-tail` (globals.css), que o CSS usa na altura do hero e na
|
|
* margem de Projetos, e que o HeroField lê para saber onde o progresso (--hero-p) acaba: quando Projetos começa a
|
|
* subir sobre o palco, `--hero-tail` telas antes do fim do hero. Estes testes conferem o comportamento (o progresso
|
|
* segue a variável), não o texto do CSS.
|
|
*/
|
|
|
|
jest.mock("./BlocosScene", () => ({
|
|
__esModule: true,
|
|
default: () => null,
|
|
prepareBlocos: () => Promise.resolve(),
|
|
}));
|
|
|
|
const win = window as unknown as Record<string, unknown>;
|
|
const original = {
|
|
matchMedia: window.matchMedia,
|
|
webgl2: win.WebGL2RenderingContext,
|
|
getContext: HTMLCanvasElement.prototype.getContext,
|
|
innerHeight: Object.getOwnPropertyDescriptor(window, "innerHeight"),
|
|
};
|
|
|
|
const SCREEN = 800;
|
|
|
|
beforeEach(() => {
|
|
window.matchMedia = jest.fn().mockImplementation((query: string) => ({
|
|
matches: query === DIVE_QUERY,
|
|
media: query,
|
|
addEventListener() {},
|
|
removeEventListener() {},
|
|
}));
|
|
Object.defineProperty(navigator, "hardwareConcurrency", {
|
|
configurable: true,
|
|
value: 8,
|
|
});
|
|
win.WebGL2RenderingContext = function WebGL2RenderingContext() {};
|
|
HTMLCanvasElement.prototype.getContext = (() => ({
|
|
getExtension: () => null,
|
|
})) as unknown as typeof HTMLCanvasElement.prototype.getContext;
|
|
Object.defineProperty(window, "innerHeight", {
|
|
configurable: true,
|
|
value: SCREEN,
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
window.matchMedia = original.matchMedia;
|
|
win.WebGL2RenderingContext = original.webgl2;
|
|
HTMLCanvasElement.prototype.getContext = original.getContext;
|
|
if (original.innerHeight)
|
|
Object.defineProperty(window, "innerHeight", original.innerHeight);
|
|
});
|
|
|
|
/** O hero com a altura (px) que o CSS lhe dá, rolado `scrolled` px; devolve o progresso do mergulho. */
|
|
function progressAt({
|
|
tail,
|
|
height,
|
|
scrolled,
|
|
}: {
|
|
tail?: number;
|
|
height: number;
|
|
scrolled: number;
|
|
}) {
|
|
const style =
|
|
tail === undefined ? undefined : ({ "--hero-tail": tail } as CSSProperties);
|
|
const { container, unmount } = render(
|
|
<section className="hero" style={style}>
|
|
<HeroField />
|
|
</section>,
|
|
);
|
|
const hero = container.querySelector(".hero") as HTMLElement;
|
|
hero.getBoundingClientRect = () =>
|
|
({ top: -scrolled, height, bottom: height - scrolled }) as DOMRect;
|
|
act(() => {
|
|
window.dispatchEvent(new Event("resize"));
|
|
});
|
|
// o rAF do jsdom corre em ~16 ms
|
|
return new Promise<number>((resolve) => {
|
|
setTimeout(() => {
|
|
const progress = Number(hero.style.getPropertyValue("--hero-p"));
|
|
unmount();
|
|
resolve(progress);
|
|
}, 60);
|
|
});
|
|
}
|
|
|
|
describe("hero dive: --hero-tail is the one source of its height", () => {
|
|
it("ends the dive where Projects starts to rise: --hero-tail screens before the end of the hero", async () => {
|
|
// hero de 2800 px com 2 telas de cauda: o mergulho tem 2800 - 2 * 800 = 1200 px
|
|
expect(
|
|
await progressAt({ tail: 2, height: 2800, scrolled: 600 }),
|
|
).toBeCloseTo(0.5);
|
|
expect(await progressAt({ tail: 2, height: 2800, scrolled: 1200 })).toBe(1);
|
|
});
|
|
|
|
it("follows the variable: the same hero with a shorter tail has a longer dive", async () => {
|
|
// 2800 - 1,5 * 800 = 1600 px de mergulho
|
|
expect(
|
|
await progressAt({ tail: 1.5, height: 2800, scrolled: 800 }),
|
|
).toBeCloseTo(0.5);
|
|
});
|
|
|
|
it("treats a hero without --hero-tail as stage only (the dive is the rest of the height)", async () => {
|
|
// 2000 - 1 * 800 = 1200 px de mergulho
|
|
expect(await progressAt({ height: 2000, scrolled: 600 })).toBeCloseTo(0.5);
|
|
});
|
|
});
|