feat(home): posicionamento com prova, menu sem recarregar e WhatsApp em todo o site
H1 'Sites e sistemas que trazem pedidos pelo WhatsApp e tiram sua operação da planilha.'; HeroWork maior com a SEV; menu Cases, Serviços, Como trabalho, Sobre e IA com next/link e pré-carregamento no hover; menu do celular em tela cheia; WhatsApp flutuante e no rodapé com a mensagem da rota; rodapé em todas as páginas; FAQ da home; /ia como oferta de revisão de app feito com IA; formulário com noscript, validação do mailto e plano B do pop-up; cartões sem foco indevido; sem a fonte Monument.
This commit is contained in:
1 parent
e80f73d075
commit
10f170abf4
56 files changed
+3099
-906
No files matched your search
@@ -0,0 +1,150 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { caseStudies, serviceHubLinks, servicePages } from "@/lib/site-content";
|
||||
import RootLayout, { metadata } from "./layout";
|
||||
|
||||
/**
|
||||
* Metadados do layout (título e sufixo, canônico) e o que ele monta em todas as páginas: o cabeçalho, o rodapé e o botão
|
||||
* flutuante do WhatsApp. Cabeçalho e botão recebem os nomes dos serviços e dos cases, lidos de lib/site-content.ts no servidor.
|
||||
*/
|
||||
|
||||
const mockHeaderProps = jest.fn();
|
||||
const mockFloatingProps = jest.fn();
|
||||
const mockFooterProps = jest.fn();
|
||||
|
||||
jest.mock("next/font/google", () => ({
|
||||
Geist_Mono: () => ({ variable: "font-mono-var" }),
|
||||
Space_Grotesk: () => ({ variable: "font-display-var" }),
|
||||
}));
|
||||
jest.mock("./globals.css", () => ({}));
|
||||
jest.mock("./components/cinematic/CinematicOverlays", () => ({
|
||||
__esModule: true,
|
||||
default: () => null,
|
||||
}));
|
||||
jest.mock("./components/GoogleAnalytics", () => ({
|
||||
__esModule: true,
|
||||
default: () => null,
|
||||
}));
|
||||
jest.mock("./components/Header", () => ({
|
||||
Header: (props: unknown) => {
|
||||
mockHeaderProps(props);
|
||||
return <header data-testid="header" />;
|
||||
},
|
||||
}));
|
||||
jest.mock("./components/Footer", () => ({
|
||||
__esModule: true,
|
||||
default: (props: unknown) => {
|
||||
mockFooterProps(props);
|
||||
return <footer data-testid="footer" />;
|
||||
},
|
||||
}));
|
||||
jest.mock("./components/FloatingWhatsApp", () => ({
|
||||
__esModule: true,
|
||||
default: (props: unknown) => {
|
||||
mockFloatingProps(props);
|
||||
return <div data-testid="floating-whatsapp" />;
|
||||
},
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("metadata", () => {
|
||||
it("ends every page title with ' | Evandro.dev.br' and gives the home its own default title", () => {
|
||||
expect(metadata.title).toEqual({
|
||||
default: "Sites e sistemas em Joinville e SC | Evandro.dev.br",
|
||||
template: "%s | Evandro.dev.br",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps the home title, suffix included, within 60 characters", () => {
|
||||
const title = metadata.title as { default: string };
|
||||
expect(title.default.length).toBeLessThanOrEqual(60);
|
||||
});
|
||||
|
||||
it("does not set a canonical in the layout: only pages that know their own address do", () => {
|
||||
// o 404 e as páginas sem canônico próprio não podem herdar o "/" da home
|
||||
expect(metadata.alternates).toBeUndefined();
|
||||
});
|
||||
|
||||
it("describes the offer without inventing numbers or promises", () => {
|
||||
expect(metadata.description).toMatch(/WhatsApp/);
|
||||
expect(metadata.description).not.toMatch(/R\$|garantia|\d+ dias/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("what the layout mounts", () => {
|
||||
function markup() {
|
||||
return renderToStaticMarkup(
|
||||
<RootLayout>
|
||||
<main>conteúdo</main>
|
||||
</RootLayout>,
|
||||
);
|
||||
}
|
||||
|
||||
it("mounts the floating WhatsApp button for the whole site, outside the home", () => {
|
||||
const html = markup();
|
||||
|
||||
expect(html).toContain('data-testid="floating-whatsapp"');
|
||||
expect(html.indexOf("conteúdo")).toBeLessThan(
|
||||
html.indexOf('data-testid="floating-whatsapp"'),
|
||||
);
|
||||
});
|
||||
|
||||
it("mounts the footer once for the whole site, after the page content, with the service links", () => {
|
||||
const html = markup();
|
||||
|
||||
expect(html.match(/data-testid="footer"/g)).toHaveLength(1);
|
||||
expect(html.indexOf("conteúdo")).toBeLessThan(
|
||||
html.indexOf('data-testid="footer"'),
|
||||
);
|
||||
expect(mockFooterProps).toHaveBeenCalledWith({
|
||||
serviceLinks: serviceHubLinks,
|
||||
});
|
||||
expect(serviceHubLinks.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("gives the header and the button the service and case names, and only names", () => {
|
||||
markup();
|
||||
|
||||
const expected = {
|
||||
services: Object.fromEntries(
|
||||
servicePages.map((service) => [service.slug, service.navLabel]),
|
||||
),
|
||||
cases: Object.fromEntries(
|
||||
caseStudies.map((caseStudy) => [caseStudy.id, caseStudy.title]),
|
||||
),
|
||||
};
|
||||
expect(mockFloatingProps).toHaveBeenCalledWith({ contexts: expected });
|
||||
expect(mockHeaderProps).toHaveBeenCalledWith({
|
||||
whatsAppContexts: expected,
|
||||
});
|
||||
expect(Object.keys(expected.services).length).toBeGreaterThan(0);
|
||||
expect(Object.keys(expected.cases).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("hides the JavaScript-only parts of the page when scripts are off", () => {
|
||||
expect(markup()).toContain(".js-only{display:none!important}");
|
||||
});
|
||||
});
|
||||
|
||||
describe("fonts", () => {
|
||||
it("no longer loads the unused Monument Extended font", () => {
|
||||
const source = readFileSync(join(__dirname, "layout.tsx"), "utf8");
|
||||
|
||||
expect(source).not.toMatch(/Monument|next\/font\/local/i);
|
||||
expect(markupClassName()).not.toMatch(/monument/i);
|
||||
});
|
||||
|
||||
function markupClassName() {
|
||||
return (
|
||||
renderToStaticMarkup(
|
||||
<RootLayout>
|
||||
<main />
|
||||
</RootLayout>,
|
||||
).match(/<html[^>]*class="([^"]*)"/)?.[1] ?? ""
|
||||
);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user