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.
73 lines
3.0 KiB
TypeScript
73 lines
3.0 KiB
TypeScript
import { render } from "@testing-library/react";
|
|
import { SEO_IDS } from "@/lib/seo";
|
|
import { serviceOffers, siteUrl } from "@/lib/site-content";
|
|
import CinematicHome from "./CinematicHome";
|
|
|
|
/**
|
|
* Dados estruturados da home: um único script `application/ld+json` com as três ofertas (nós Service ligados à
|
|
* Person). O WebSite e a Person já saem do layout (app/layout.tsx), e repeti-los aqui duplicaria os dados. Sem
|
|
* ProfessionalService nem LocalBusiness: a política de docs/seo-geo-operations.md os exclui.
|
|
*/
|
|
|
|
jest.mock("../Hero", () => ({ __esModule: true, default: () => null }));
|
|
jest.mock("../Projects", () => ({ __esModule: true, default: () => null }));
|
|
jest.mock("../About", () => ({ __esModule: true, default: () => null }));
|
|
jest.mock("../WorkModalities", () => ({ WorkModalities: () => null }));
|
|
jest.mock("../HomeFaq", () => ({ __esModule: true, default: () => null }));
|
|
jest.mock("../Testimonials", () => ({ __esModule: true, default: () => null }));
|
|
jest.mock("../Contact", () => ({ __esModule: true, default: () => null }));
|
|
jest.mock("./CinematicMain", () => ({
|
|
__esModule: true,
|
|
default: ({ children }: { children: React.ReactNode }) => (
|
|
<main>{children}</main>
|
|
),
|
|
}));
|
|
|
|
function structuredData() {
|
|
const { container } = render(<CinematicHome projects={[]} />);
|
|
return Array.from(
|
|
container.querySelectorAll('script[type="application/ld+json"]'),
|
|
).map((script) => JSON.parse(script.textContent ?? ""));
|
|
}
|
|
|
|
describe("CinematicHome: dados estruturados", () => {
|
|
it("emits exactly one ld+json script, before the scenes", () => {
|
|
const { container } = render(<CinematicHome projects={[]} />);
|
|
|
|
const scripts = container.querySelectorAll(
|
|
'script[type="application/ld+json"]',
|
|
);
|
|
expect(scripts).toHaveLength(1);
|
|
expect(container.firstElementChild).toBe(scripts[0]);
|
|
});
|
|
|
|
it("describes only the five offers as Service nodes tied to the person, without repeating what the layout emits", () => {
|
|
const [data] = structuredData();
|
|
|
|
expect(data["@context"]).toBe("https://schema.org");
|
|
expect(data["@graph"]).toHaveLength(5);
|
|
for (const offer of data["@graph"]) {
|
|
expect(offer["@type"]).toBe("Service");
|
|
expect(offer.provider).toEqual({ "@id": SEO_IDS.person });
|
|
expect(
|
|
offer.areaServed.map(({ name }: { name: string }) => name),
|
|
).toEqual(["Garuva", "Joinville", "Santa Catarina", "Brasil"]);
|
|
}
|
|
expect(data["@graph"].map(({ name }: { name: string }) => name)).toEqual(
|
|
serviceOffers.map(({ name }) => name),
|
|
);
|
|
expect(data["@graph"].map(({ url }: { url: string }) => url)).toEqual(
|
|
serviceOffers.map(({ href }) => `${siteUrl}${href}`),
|
|
);
|
|
expect(JSON.stringify(data)).not.toMatch(/"@type":"(WebSite|Person)"/);
|
|
});
|
|
|
|
it("never emits ProfessionalService, LocalBusiness or an offer catalog (docs/seo-geo-operations.md)", () => {
|
|
const [data] = structuredData();
|
|
|
|
expect(JSON.stringify(data)).not.toMatch(
|
|
/ProfessionalService|LocalBusiness|OfferCatalog/,
|
|
);
|
|
});
|
|
});
|