feat(cases): mídia no topo, títulos de busca curtos e CreativeWork
Case com mídia ao lado do título e play acessível; problema de cada cliente reescrito com base nos dados; serviços relacionados em 2 cards; FAQ antes do próximo passo e bloco 'Onde já apliquei isso'; Apolo citada em IoT e arquitetura. metaTitle até 43 caracteres e descrições até 155 com teste; JSON-LD CreativeWork; imagens de compartilhamento abaixo de 100 KB; 404 e robots corrigidos; server-only nos módulos com node:.
This commit is contained in:
1 parent
c9a0b80656
commit
e80f73d075
52 files changed
+2593
-439
No files matched your search
@@ -1,15 +1,50 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { notFound } from "next/navigation";
|
||||
import { ImageResponse } from "next/og";
|
||||
import { isValidElement } from "react";
|
||||
import { OG_SIZE, OgCard } from "@/lib/og-card";
|
||||
import {
|
||||
OG_IMAGE_MAX_BYTES,
|
||||
OG_SIZE,
|
||||
OgCard,
|
||||
readProjectOgImage,
|
||||
} from "@/lib/og-card";
|
||||
import { caseStudies } from "@/lib/site-content";
|
||||
import Image from "./opengraph-image";
|
||||
import Image, { generateImageMetadata } from "./opengraph-image";
|
||||
|
||||
/**
|
||||
* Testes de caminhos-base de Image (imagem de compartilhamento do case):
|
||||
* slug desconhecido, id da imagem diferente do case, case de cliente e projeto de laboratório.
|
||||
* slug desconhecido, id da imagem diferente do case, case com capa gravada (entregue como está, em JPEG,
|
||||
* abaixo de 150 KB), capa pesada demais (cartão simples) e projeto de laboratório.
|
||||
*/
|
||||
|
||||
/** O jsdom não tem `Response`; a rota roda no Node e devolve uma. Aqui basta guardar o corpo e os cabeçalhos. */
|
||||
class FakeResponse {
|
||||
constructor(
|
||||
readonly body: Uint8Array,
|
||||
readonly init: { headers: Record<string, string> },
|
||||
) {}
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
Object.assign(globalThis, { Response: FakeResponse });
|
||||
});
|
||||
|
||||
jest.mock("@/lib/og-card", () => {
|
||||
const actual = jest.requireActual("@/lib/og-card");
|
||||
return { ...actual, readProjectOgImage: jest.fn(actual.readProjectOgImage) };
|
||||
});
|
||||
|
||||
const mockedReadProjectOgImage = jest.mocked(readProjectOgImage);
|
||||
const actualReadProjectOgImage =
|
||||
jest.requireActual<typeof import("@/lib/og-card")>(
|
||||
"@/lib/og-card",
|
||||
).readProjectOgImage;
|
||||
|
||||
afterEach(() => {
|
||||
mockedReadProjectOgImage.mockImplementation(actualReadProjectOgImage);
|
||||
});
|
||||
|
||||
jest.mock("next/navigation", () => ({
|
||||
notFound: jest.fn(() => {
|
||||
throw new Error("NEXT_NOT_FOUND");
|
||||
@@ -47,11 +82,32 @@ function sentCard() {
|
||||
}
|
||||
|
||||
describe("opengraph-image Image", () => {
|
||||
it("path: client case with the matching image id -> card labelled as a client case", async () => {
|
||||
it("path: case with a recorded cover -> the cover itself, in JPEG, under 150 KB, and no generated card", async () => {
|
||||
const caseStudy = getCase("sev-modaintima");
|
||||
await renderImage(caseStudy.id, caseStudy.id);
|
||||
const response = (await renderImage(
|
||||
caseStudy.id,
|
||||
caseStudy.id,
|
||||
)) as unknown as FakeResponse;
|
||||
|
||||
expect(notFound).not.toHaveBeenCalled();
|
||||
expect(mockedImageResponse).not.toHaveBeenCalled();
|
||||
expect(response).toBeInstanceOf(FakeResponse);
|
||||
expect(response.init.headers["Content-Type"]).toBe("image/jpeg");
|
||||
const cover = readFileSync(
|
||||
join(process.cwd(), "public/videos/sev-modaintima/og.jpg"),
|
||||
);
|
||||
expect(Buffer.from(response.body).equals(cover)).toBe(true);
|
||||
expect(response.body.length).toBeLessThan(150_000);
|
||||
});
|
||||
|
||||
it("path: cover that weighs 150 KB or more -> the plain generated card, with no background", async () => {
|
||||
const caseStudy = getCase("sev-modaintima");
|
||||
mockedReadProjectOgImage.mockReturnValue(undefined);
|
||||
expect(OG_IMAGE_MAX_BYTES).toBe(150_000);
|
||||
|
||||
const response = await renderImage(caseStudy.id, caseStudy.id);
|
||||
|
||||
expect(response).not.toBeInstanceOf(FakeResponse);
|
||||
expect(mockedImageResponse).toHaveBeenCalledTimes(1);
|
||||
const { card, options } = sentCard();
|
||||
expect(card.type).toBe(OgCard);
|
||||
@@ -60,10 +116,31 @@ describe("opengraph-image Image", () => {
|
||||
title: caseStudy.title,
|
||||
description: caseStudy.summary,
|
||||
});
|
||||
expect(card.props.backgroundImage).toMatch(/^data:image\/jpeg;base64,/);
|
||||
expect(card.props.backgroundImage).toBeUndefined();
|
||||
expect(options).toEqual(OG_SIZE);
|
||||
});
|
||||
|
||||
it("path: image metadata -> JPEG for covers, PNG for generated cards, and every case listed without a slug", () => {
|
||||
const [sev] = generateImageMetadata({ params: { slug: "sev-modaintima" } });
|
||||
const [twins] = generateImageMetadata({ params: { slug: "twins-lab" } });
|
||||
expect(sev).toMatchObject({
|
||||
id: "sev-modaintima",
|
||||
contentType: "image/jpeg",
|
||||
size: OG_SIZE,
|
||||
});
|
||||
expect(twins).toMatchObject({
|
||||
id: "twins-lab",
|
||||
contentType: "image/png",
|
||||
size: OG_SIZE,
|
||||
});
|
||||
|
||||
const all = generateImageMetadata({ params: {} });
|
||||
expect(all.map(({ id }) => id)).toEqual(caseStudies.map(({ id }) => id));
|
||||
expect(
|
||||
all.filter(({ contentType }) => contentType === "image/jpeg"),
|
||||
).toHaveLength(5);
|
||||
});
|
||||
|
||||
it("path: unknown slug -> notFound() and no image", async () => {
|
||||
await expect(renderImage("nao-existe", "nao-existe")).rejects.toThrow(
|
||||
"NEXT_NOT_FOUND",
|
||||
|
||||
Reference in new issue
Block a user