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:.
103 lines
3.5 KiB
TypeScript
103 lines
3.5 KiB
TypeScript
import { statSync } from "node:fs";
|
|
import { render } from "@testing-library/react";
|
|
import { OG_IMAGE_MAX_BYTES, OgCard, readProjectOgImage } from "./og-card";
|
|
import { PROJECT_VIDEO_IDS } from "./project-showcase";
|
|
|
|
jest.mock("node:fs", () => {
|
|
const actual = jest.requireActual<typeof import("node:fs")>("node:fs");
|
|
return { ...actual, statSync: jest.fn(actual.statSync) };
|
|
});
|
|
|
|
const mockedStatSync = jest.mocked(statSync);
|
|
const actualStatSync =
|
|
jest.requireActual<typeof import("node:fs")>("node:fs").statSync;
|
|
|
|
afterEach(() => {
|
|
mockedStatSync.mockImplementation(actualStatSync);
|
|
});
|
|
|
|
/** Largura e altura de um JPEG, lidas do primeiro marcador de quadro (SOF). */
|
|
function jpegSize(image: Buffer) {
|
|
let offset = 2;
|
|
while (offset < image.length) {
|
|
const marker = image[offset + 1];
|
|
const length = image.readUInt16BE(offset + 2);
|
|
if (
|
|
marker >= 0xc0 &&
|
|
marker <= 0xcf &&
|
|
marker !== 0xc4 &&
|
|
marker !== 0xc8
|
|
) {
|
|
return {
|
|
height: image.readUInt16BE(offset + 5),
|
|
width: image.readUInt16BE(offset + 7),
|
|
};
|
|
}
|
|
offset += 2 + length;
|
|
}
|
|
throw new Error("JPEG sem marcador de quadro");
|
|
}
|
|
|
|
describe("readProjectOgImage", () => {
|
|
it("returns the recorded cover of a showcase project as it is, in JPEG", () => {
|
|
const image = readProjectOgImage("nutriscan");
|
|
expect(image?.subarray(0, 2).toString("hex")).toBe("ffd8");
|
|
expect(image?.length).toBeGreaterThan(5_000);
|
|
});
|
|
|
|
it("serves every recorded cover below 150 KB and in the 1200x630 card size", () => {
|
|
expect(OG_IMAGE_MAX_BYTES).toBe(150_000);
|
|
for (const id of PROJECT_VIDEO_IDS) {
|
|
const image = readProjectOgImage(id);
|
|
expect(image).toBeDefined();
|
|
expect(image?.length ?? Infinity).toBeLessThan(150_000);
|
|
expect(jpegSize(image as Buffer)).toEqual({ width: 1200, height: 630 });
|
|
}
|
|
});
|
|
|
|
it("returns undefined for a cover that weighs the limit or more, so the plain card is used", () => {
|
|
mockedStatSync.mockReturnValue({
|
|
size: OG_IMAGE_MAX_BYTES,
|
|
} as ReturnType<typeof statSync>);
|
|
expect(readProjectOgImage("nutriscan")).toBeUndefined();
|
|
|
|
mockedStatSync.mockReturnValue({
|
|
size: OG_IMAGE_MAX_BYTES - 1,
|
|
} as ReturnType<typeof statSync>);
|
|
expect(readProjectOgImage("nutriscan")).toBeDefined();
|
|
});
|
|
|
|
it("returns undefined for projects without a recorded cover", () => {
|
|
expect(readProjectOgImage("twins-lab")).toBeUndefined();
|
|
expect(readProjectOgImage("../../etc/passwd")).toBeUndefined();
|
|
});
|
|
|
|
it("returns undefined when the file is missing on disk", () => {
|
|
mockedStatSync.mockImplementation(() => {
|
|
throw new Error("ENOENT");
|
|
});
|
|
expect(readProjectOgImage("nutriscan")).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("OgCard", () => {
|
|
const props = { eyebrow: "Case", title: "Título", description: "Resumo." };
|
|
|
|
it("keeps the plain dark card when there is no background", () => {
|
|
const { container } = render(<OgCard {...props} />);
|
|
// só a marca
|
|
expect(container.querySelectorAll("img")).toHaveLength(1);
|
|
});
|
|
|
|
it("puts the project cover behind the text with a darkening gradient", () => {
|
|
const { container } = render(
|
|
<OgCard {...props} backgroundImage="data:image/jpeg;base64,AAAA" />,
|
|
);
|
|
const images = [...container.querySelectorAll("img")];
|
|
expect(images).toHaveLength(2);
|
|
expect(images[0]).toHaveAttribute("src", "data:image/jpeg;base64,AAAA");
|
|
expect(images[0]).toHaveAttribute("alt", "");
|
|
expect(container.innerHTML).toContain("linear-gradient");
|
|
});
|
|
});
|