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.
96 lines
4.1 KiB
TypeScript
96 lines
4.1 KiB
TypeScript
import { render, screen } from "@testing-library/react";
|
|
import HeroWork from "./HeroWork";
|
|
|
|
/**
|
|
* O pôster do hero: pelo next/image com `sizes` do tamanho em que aparece, lazy e com prioridade baixa (o título é o
|
|
* LCP), no cartão largo e clicável da SEV para as telas abaixo de 1024 px. Em tela larga a coluna é da peça 3D.
|
|
*/
|
|
|
|
const original = (image: Element) =>
|
|
new URL(image.getAttribute("src") ?? "", "http://localhost").searchParams.get(
|
|
"url",
|
|
);
|
|
|
|
describe("HeroWork: pôsteres no tamanho certo", () => {
|
|
it("serves the SEV poster through the image optimizer, with a `sizes` that matches how it shows", () => {
|
|
render(<HeroWork />);
|
|
|
|
// o cartão do celular usa o pôster de celular da SEV (o título em letras grandes cabe na faixa de 72 px), na largura
|
|
// da coluna (a tela menos as duas margens: cerca de 342 px num celular de 390 px), e não 100vw
|
|
const small = screen
|
|
.getByTestId("hero-work-mobile")
|
|
.querySelector("img") as HTMLImageElement;
|
|
expect(original(small)).toBe("/videos/sev-modaintima/mobile.webp");
|
|
expect(small).toHaveAttribute(
|
|
"sizes",
|
|
"(min-width: 1024px) 1px, calc(100vw - 48px)",
|
|
);
|
|
expect(small).toHaveAttribute("width", "390");
|
|
expect(small).toHaveAttribute("height", "844");
|
|
});
|
|
|
|
it("shows the headline of the SEV poster in the 72px strip of the mobile card: the position moves the strip to the line 'Nova Coleção' instead of the top of the poster", () => {
|
|
render(<HeroWork />);
|
|
|
|
const small = screen
|
|
.getByTestId("hero-work-mobile")
|
|
.querySelector("img") as HTMLImageElement;
|
|
expect(small).toHaveClass("object-cover", "object-[50%_57%]");
|
|
expect(small).not.toHaveClass("object-top");
|
|
// a faixa continua pequena (o título da página segue como o maior elemento da dobra)
|
|
expect((small.parentElement as HTMLElement).style.height).toBe("72px");
|
|
});
|
|
|
|
it("never asks for a high priority: lazy, low fetch priority and no preload, so the headline stays the LCP", () => {
|
|
const { container } = render(<HeroWork />);
|
|
|
|
const images = container.querySelectorAll("img");
|
|
expect(images).toHaveLength(1);
|
|
for (const image of images) {
|
|
expect(image).toHaveAttribute("loading", "lazy");
|
|
expect(image).toHaveAttribute("fetchpriority", "low");
|
|
}
|
|
// sem <link rel="preload">: o next/image só o insere com `preload` (ou fetchPriority="high")
|
|
expect(document.head.querySelector('link[rel="preload"]')).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("HeroWork: cartão do celular", () => {
|
|
it("is the whole component: a wide card with the SEV poster, only below 1024px", () => {
|
|
const { container } = render(<HeroWork />);
|
|
|
|
const card = screen.getByTestId("hero-work-mobile");
|
|
expect(container.firstChild).toBe(card);
|
|
expect(container.childElementCount).toBe(1);
|
|
expect(card).toHaveClass("lg:hidden", "w-full");
|
|
// sem a moldura de celular de 65x160 px: a imagem é larga e ocupa a coluna inteira
|
|
expect(card.querySelector(".ds-phone")).toBeNull();
|
|
const imageBox = card.querySelector("img")?.parentElement as HTMLElement;
|
|
expect(imageBox).toHaveClass("w-full");
|
|
expect(imageBox.style.height).toBe("72px");
|
|
expect(card).toHaveTextContent("Site da SEV Moda Íntima");
|
|
expect(card).toHaveTextContent("Ver case");
|
|
});
|
|
|
|
it("is a link to the SEV case, with a touch target of at least 48px and its own focus ring", () => {
|
|
render(<HeroWork />);
|
|
|
|
const card = screen.getByRole("link", {
|
|
name: "Site da SEV Moda Íntima Ver case",
|
|
});
|
|
expect(card).toBe(screen.getByTestId("hero-work-mobile"));
|
|
expect(card).toHaveAttribute("href", "/cases/sev-modaintima");
|
|
expect(card).not.toHaveClass("pointer-events-none");
|
|
expect(card.className).toContain("focus-visible:outline-2");
|
|
expect(card.querySelector("span.min-h-12")).not.toBeNull();
|
|
// a imagem é decorativa: o texto do link já diz o que ela mostra
|
|
expect(card.querySelector("img")).toHaveAttribute("alt", "");
|
|
});
|
|
|
|
it("draws no other link: the card is the only one in HeroWork", () => {
|
|
render(<HeroWork />);
|
|
|
|
expect(screen.getAllByRole("link")).toHaveLength(1);
|
|
});
|
|
});
|