feat(cases): prova certa por serviço, páginas internas com trabalho no topo
Diagnóstico sem case que não cabe; desktop com o GCC Installer como projeto próprio; IoT com o Twins Lab e a nota da Apolo. Mosaico de pôsteres em /servicos e /cases, faixa baixa em uma coluna no currículo, privacidade e laboratório; Laboratório como lista compacta; SEV começando por catálogo, pedido no WhatsApp e painel; Joinville e SC nos títulos; imagens do LCP com fetchPriority e sizes reais; pré-carregamento só no hover; 404 com Open Graph próprio.
This commit is contained in:
1 parent
7b633f0f4a
commit
de6af74a0c
37 files changed
+1062
-254
No files matched your search
@@ -0,0 +1,69 @@
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { coverSizes } from "@/app/cases/_components/CaseCover";
|
||||
import { getCaseMedia } from "@/app/cases/_components/case-media";
|
||||
import { getShowcaseAssets } from "@/lib/project-showcase";
|
||||
import { caseStudies } from "@/lib/site-content";
|
||||
import { ServiceHeroCover } from "./ServiceHeroCover";
|
||||
|
||||
/**
|
||||
* Capa de destaque do serviço: sem case não mostra nada; com case, é a imagem que abre a página (carga na hora,
|
||||
* prioridade alta e a largura real da coluna) e o link só pré-carrega o case quando a pessoa chega nele.
|
||||
*/
|
||||
|
||||
jest.mock("@/app/cases/_components/case-media", () => ({
|
||||
getCaseMedia: jest.fn(),
|
||||
}));
|
||||
jest.mock("next/link", () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
children,
|
||||
prefetch,
|
||||
...props
|
||||
}: { children?: React.ReactNode; prefetch?: boolean | null } & Record<
|
||||
string,
|
||||
unknown
|
||||
>) => (
|
||||
<a data-prefetch={String(prefetch)} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
}));
|
||||
|
||||
const mockedGetCaseMedia = jest.mocked(getCaseMedia);
|
||||
const sev = caseStudies.filter(({ id }) => id === "sev-modaintima")[0];
|
||||
|
||||
afterEach(() => {
|
||||
mockedGetCaseMedia.mockReset();
|
||||
});
|
||||
|
||||
describe("ServiceHeroCover", () => {
|
||||
it("path: no case -> renders nothing", () => {
|
||||
const { container } = render(<ServiceHeroCover />);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("path: case with a capture -> a link to the case around a cover that loads first", () => {
|
||||
mockedGetCaseMedia.mockImplementation(getShowcaseAssets);
|
||||
render(<ServiceHeroCover caseStudy={sev} />);
|
||||
|
||||
const link = screen.getByRole("link", {
|
||||
name: new RegExp(`^Ver o case ${sev.title}`),
|
||||
});
|
||||
expect(link).toHaveAttribute("href", `/cases/${sev.id}`);
|
||||
const image = screen.getByAltText(`Captura de tela do site ${sev.title}`);
|
||||
expect(image).toHaveAttribute("loading", "eager");
|
||||
expect(image).toHaveAttribute("fetchpriority", "high");
|
||||
expect(image).toHaveAttribute("sizes", coverSizes.hero);
|
||||
});
|
||||
|
||||
it("path: link -> pre-fetches the case only after the pointer reaches it", () => {
|
||||
mockedGetCaseMedia.mockImplementation(getShowcaseAssets);
|
||||
render(<ServiceHeroCover caseStudy={sev} />);
|
||||
|
||||
const link = screen.getByRole("link");
|
||||
expect(link).toHaveAttribute("data-prefetch", "false");
|
||||
fireEvent.mouseEnter(link);
|
||||
expect(link).toHaveAttribute("data-prefetch", "null");
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import Link from "next/link";
|
||||
import { CaseCover } from "@/app/cases/_components/CaseCover";
|
||||
import { CaseCover, coverSizes } from "@/app/cases/_components/CaseCover";
|
||||
import HoverPrefetchLink from "@/app/components/HoverPrefetchLink";
|
||||
import type { CaseStudyContent } from "@/lib/site-content";
|
||||
|
||||
/** Capa do case de destaque ao lado do título do serviço. Sem case com captura, não mostra nada. */
|
||||
@@ -11,15 +11,18 @@ export function ServiceHeroCover({
|
||||
if (!caseStudy) return null;
|
||||
|
||||
return (
|
||||
<Link href={`/cases/${caseStudy.id}`} className="block min-w-0">
|
||||
<HoverPrefetchLink
|
||||
href={`/cases/${caseStudy.id}`}
|
||||
className="block min-w-0"
|
||||
>
|
||||
<span className="sr-only">Ver o case {caseStudy.title}</span>
|
||||
<CaseCover
|
||||
id={caseStudy.id}
|
||||
title={caseStudy.title}
|
||||
demoUrl={caseStudy.demoUrl}
|
||||
sizes="(min-width: 1024px) 520px, 100vw"
|
||||
sizes={coverSizes.hero}
|
||||
priority
|
||||
/>
|
||||
</Link>
|
||||
</HoverPrefetchLink>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import Link from "next/link";
|
||||
import { CaseCoverLink } from "@/app/cases/_components/CaseCoverLink";
|
||||
import { secondaryButtonLight, textLink } from "@/app/components/button-styles";
|
||||
import HoverPrefetchLink from "@/app/components/HoverPrefetchLink";
|
||||
import { type CaseStudyContent, isClientCase } from "@/lib/site-content";
|
||||
|
||||
function ServiceCaseCard({
|
||||
@@ -27,9 +27,12 @@ function ServiceCaseCard({
|
||||
<p className="ds-kicker mt-sp-5">Relação com este serviço</p>
|
||||
<p className="ds-copy !mt-sp-2">{relevance ?? caseStudy.summary}</p>
|
||||
<div className="mt-sp-6 flex flex-wrap items-center gap-sp-3">
|
||||
<Link href={`/cases/${caseStudy.id}`} className={secondaryButtonLight}>
|
||||
<HoverPrefetchLink
|
||||
href={`/cases/${caseStudy.id}`}
|
||||
className={secondaryButtonLight}
|
||||
>
|
||||
Ler case: {caseStudy.title}
|
||||
</Link>
|
||||
</HoverPrefetchLink>
|
||||
{caseStudy.demoUrl ? (
|
||||
<a
|
||||
href={caseStudy.demoUrl}
|
||||
|
||||
Reference in new issue
Block a user