fix(cases): um só convite ao WhatsApp no fim do case e testes alinhados
This commit is contained in:
1 parent
2fd8b063d9
commit
d356c364e0
7 files changed
+124
-197
No files matched your search
@@ -45,12 +45,6 @@ afterEach(() => {
|
|||||||
mockedReadProjectOgImage.mockImplementation(actualReadProjectOgImage);
|
mockedReadProjectOgImage.mockImplementation(actualReadProjectOgImage);
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock("next/navigation", () => ({
|
|
||||||
notFound: jest.fn(() => {
|
|
||||||
throw new Error("NEXT_NOT_FOUND");
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock("next/og", () => ({ ImageResponse: jest.fn() }));
|
jest.mock("next/og", () => ({ ImageResponse: jest.fn() }));
|
||||||
|
|
||||||
const mockedImageResponse = jest.mocked(ImageResponse);
|
const mockedImageResponse = jest.mocked(ImageResponse);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import { setNavigation } from "@/__mocks__/next/navigation";
|
||||||
import { caseStudies } from "@/lib/site-content";
|
import { caseStudies } from "@/lib/site-content";
|
||||||
import CasePage from "./page";
|
import CasePage from "./page";
|
||||||
|
|
||||||
@@ -7,11 +8,9 @@ import CasePage from "./page";
|
|||||||
* O teste principal cobre a lista ausente (`undefined`) e a lista preenchida; aqui, a lista vazia (`[]`).
|
* O teste principal cobre a lista ausente (`undefined`) e a lista preenchida; aqui, a lista vazia (`[]`).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// usePathname: o HoverPrefetchLink (trilha, cartões) lê o caminho para saber se está na home
|
beforeEach(() => {
|
||||||
jest.mock("next/navigation", () => ({
|
setNavigation({ pathname: "/cases" });
|
||||||
notFound: jest.fn(),
|
});
|
||||||
usePathname: () => "/cases",
|
|
||||||
}));
|
|
||||||
|
|
||||||
function getCase(id: string) {
|
function getCase(id: string) {
|
||||||
const caseStudy = caseStudies.find((item) => item.id === id);
|
const caseStudy = caseStudies.find((item) => item.id === id);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
|
import { setNavigation } from "@/__mocks__/next/navigation";
|
||||||
import { caseStudies, getRelatedServices } from "@/lib/site-content";
|
import { caseStudies, getRelatedServices } from "@/lib/site-content";
|
||||||
import { getCaseMedia } from "../_components/case-media";
|
import { getCaseMedia } from "../_components/case-media";
|
||||||
import CasePage, { generateMetadata } from "./page";
|
import CasePage, { generateMetadata } from "./page";
|
||||||
@@ -10,13 +11,9 @@ import CasePage, { generateMetadata } from "./page";
|
|||||||
* demonstração sem captura, resultado vazio e case sem serviço relacionado.
|
* demonstração sem captura, resultado vazio e case sem serviço relacionado.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// usePathname: o HoverPrefetchLink (trilha, cartões) lê o caminho para saber se está na home
|
beforeEach(() => {
|
||||||
jest.mock("next/navigation", () => ({
|
setNavigation({ pathname: "/cases" });
|
||||||
notFound: jest.fn(() => {
|
});
|
||||||
throw new Error("NEXT_NOT_FOUND");
|
|
||||||
}),
|
|
||||||
usePathname: () => "/cases",
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock("../_components/case-media", () => {
|
jest.mock("../_components/case-media", () => {
|
||||||
const actual = jest.requireActual("../_components/case-media");
|
const actual = jest.requireActual("../_components/case-media");
|
||||||
@@ -127,12 +124,12 @@ describe("CasePage: caminhos que o teste principal não cobre", () => {
|
|||||||
expect(screen.getByText(/Projeto de laboratório/)).toBeVisible();
|
expect(screen.getByText(/Projeto de laboratório/)).toBeVisible();
|
||||||
expect(screen.queryByRole("link", { name: /^Ver o serviço:/ })).toBeNull();
|
expect(screen.queryByRole("link", { name: /^Ver o serviço:/ })).toBeNull();
|
||||||
expect(screen.queryByText("Serviços ligados a este projeto")).toBeNull();
|
expect(screen.queryByText("Serviços ligados a este projeto")).toBeNull();
|
||||||
// o NutriScan tem "Como ficou": o botão de contato vem depois dele e de novo no fecho, com o mesmo rótulo
|
// o NutriScan tem "Como ficou", mas o botão de contato é um só, o do fecho (sem dois seguidos)
|
||||||
const contacts = screen.getAllByRole("link", {
|
const contacts = screen.getAllByRole("link", {
|
||||||
name: "Falar sobre meu projeto",
|
name: "Falar sobre meu projeto",
|
||||||
});
|
});
|
||||||
expect(contacts).toHaveLength(2);
|
expect(contacts).toHaveLength(1);
|
||||||
for (const contact of contacts) expect(contact).toBeVisible();
|
expect(contacts[0]).toBeVisible();
|
||||||
expect(
|
expect(
|
||||||
screen.getByRole("link", { name: "Ver outros cases" }),
|
screen.getByRole("link", { name: "Ver outros cases" }),
|
||||||
).toHaveAttribute("href", "/cases");
|
).toHaveAttribute("href", "/cases");
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { fireEvent, render, screen, within } from "@testing-library/react";
|
import { fireEvent, render, screen, within } from "@testing-library/react";
|
||||||
|
import { setNavigation } from "@/__mocks__/next/navigation";
|
||||||
import {
|
import {
|
||||||
buttonLabels,
|
buttonLabels,
|
||||||
caseStudies,
|
caseStudies,
|
||||||
@@ -10,11 +11,9 @@ import { similarProjectSubject } from "@/lib/whatsapp";
|
|||||||
import CasesPage from "../page";
|
import CasesPage from "../page";
|
||||||
import CasePage, { generateMetadata, generateStaticParams } from "./page";
|
import CasePage, { generateMetadata, generateStaticParams } from "./page";
|
||||||
|
|
||||||
// usePathname: o HoverPrefetchLink (trilha, cartões) lê o caminho para saber se está na home
|
beforeEach(() => {
|
||||||
jest.mock("next/navigation", () => ({
|
setNavigation({ pathname: "/cases" });
|
||||||
notFound: jest.fn(),
|
});
|
||||||
usePathname: () => "/cases",
|
|
||||||
}));
|
|
||||||
|
|
||||||
const clientCaseIds = [
|
const clientCaseIds = [
|
||||||
"sev-modaintima",
|
"sev-modaintima",
|
||||||
@@ -63,12 +62,9 @@ function readJsonLdEntities(container: HTMLElement): Record<string, unknown>[] {
|
|||||||
/**
|
/**
|
||||||
* Nenhum link para /#contact nem para o GitHub; o contato é o WhatsApp com o contexto do case, com o mesmo rótulo
|
* Nenhum link para /#contact nem para o GitHub; o contato é o WhatsApp com o contexto do case, com o mesmo rótulo
|
||||||
* ("Falar sobre meu projeto") no case de cliente e no de laboratório. A mensagem é a mesma frase nos dois, a do cabeçalho:
|
* ("Falar sobre meu projeto") no case de cliente e no de laboratório. A mensagem é a mesma frase nos dois, a do cabeçalho:
|
||||||
* `similarProjectSubject(título)`. Com "Como ficou" na página, o botão aparece duas vezes: logo depois dele e no fecho.
|
* `similarProjectSubject(título)`. Um botão só por página (o do fecho): sem dois "Falar sobre meu projeto" seguidos.
|
||||||
*/
|
*/
|
||||||
function expectContactAndNoDeadLinks(
|
function expectContactAndNoDeadLinks(caseStudy: { id: string; title: string }) {
|
||||||
caseStudy: { id: string; title: string },
|
|
||||||
expectedButtons = 1,
|
|
||||||
) {
|
|
||||||
const hrefs = screen
|
const hrefs = screen
|
||||||
.getAllByRole("link")
|
.getAllByRole("link")
|
||||||
.map((link) => link.getAttribute("href") ?? "");
|
.map((link) => link.getAttribute("href") ?? "");
|
||||||
@@ -76,7 +72,7 @@ function expectContactAndNoDeadLinks(
|
|||||||
expect(hrefs.some((href) => /github/i.test(href))).toBe(false);
|
expect(hrefs.some((href) => /github/i.test(href))).toBe(false);
|
||||||
|
|
||||||
const ctas = screen.getAllByRole("link", { name: buttonLabels.contact });
|
const ctas = screen.getAllByRole("link", { name: buttonLabels.contact });
|
||||||
expect(ctas).toHaveLength(expectedButtons);
|
expect(ctas).toHaveLength(1);
|
||||||
for (const cta of ctas) {
|
for (const cta of ctas) {
|
||||||
const destination = new URL(cta.getAttribute("href") ?? "");
|
const destination = new URL(cta.getAttribute("href") ?? "");
|
||||||
expect(destination.origin).toBe("https://wa.me");
|
expect(destination.origin).toBe("https://wa.me");
|
||||||
@@ -171,11 +167,17 @@ it("renders a client case in the sales order, with media, contact and collapsed
|
|||||||
screen.getByRole("link", { name: `Abrir o site ${caseStudy.title}` }),
|
screen.getByRole("link", { name: `Abrir o site ${caseStudy.title}` }),
|
||||||
).toHaveAttribute("href", caseStudy.demoUrl);
|
).toHaveAttribute("href", caseStudy.demoUrl);
|
||||||
|
|
||||||
// o botão de contato se repete logo depois de "Como ficou", dentro da seção das capacidades
|
// um botão de contato só, no fecho: as capacidades (logo depois de "Como ficou") não repetem o botão
|
||||||
expectContactAndNoDeadLinks(caseStudy, 2);
|
expectContactAndNoDeadLinks(caseStudy);
|
||||||
const capabilitiesSection = sectionHeadings[4].closest("section");
|
const capabilitiesSection = sectionHeadings[4].closest("section");
|
||||||
expect(capabilitiesSection).toContainElement(
|
expect(
|
||||||
within(capabilitiesSection as HTMLElement).getByRole("link", {
|
within(capabilitiesSection as HTMLElement).queryByRole("link", {
|
||||||
|
name: buttonLabels.contact,
|
||||||
|
}),
|
||||||
|
).toBeNull();
|
||||||
|
const closingSection = sectionHeadings[5].closest("section");
|
||||||
|
expect(closingSection).toContainElement(
|
||||||
|
within(closingSection as HTMLElement).getByRole("link", {
|
||||||
name: buttonLabels.contact,
|
name: buttonLabels.contact,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -184,14 +186,6 @@ it("renders a client case in the sales order, with media, contact and collapsed
|
|||||||
within(capabilitiesSection as HTMLElement).getByText(capability),
|
within(capabilitiesSection as HTMLElement).getByText(capability),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
}
|
}
|
||||||
expect(
|
|
||||||
isBefore(
|
|
||||||
sectionHeadings[3],
|
|
||||||
within(capabilitiesSection as HTMLElement).getByRole("link", {
|
|
||||||
name: buttonLabels.contact,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
).toBe(true);
|
|
||||||
|
|
||||||
// detalhes técnicos: recolhidos, junto do conteúdo (logo depois do que foi feito), não numa faixa à parte
|
// detalhes técnicos: recolhidos, junto do conteúdo (logo depois do que foi feito), não numa faixa à parte
|
||||||
const details = container.querySelector("details");
|
const details = container.querySelector("details");
|
||||||
@@ -293,8 +287,8 @@ it("renders the two new client cases from the portfolio data", async () => {
|
|||||||
expect(
|
expect(
|
||||||
screen.getByRole("link", { name: `Abrir o site ${caseStudy.title}` }),
|
screen.getByRole("link", { name: `Abrir o site ${caseStudy.title}` }),
|
||||||
).toHaveAttribute("href", caseStudy.demoUrl);
|
).toHaveAttribute("href", caseStudy.demoUrl);
|
||||||
// com "Como ficou" na página, o contato aparece logo depois dele e de novo no fecho
|
// um botão de contato só, no fecho, mesmo com "Como ficou" na página
|
||||||
expectContactAndNoDeadLinks(caseStudy, 2);
|
expectContactAndNoDeadLinks(caseStudy);
|
||||||
unmount();
|
unmount();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -336,9 +330,8 @@ it("turns the contact of a laboratory case into the 'Fale sobre o seu caso' invi
|
|||||||
expect(
|
expect(
|
||||||
screen.getByRole("heading", { level: 2, name: "Fale sobre o seu caso" }),
|
screen.getByRole("heading", { level: 2, name: "Fale sobre o seu caso" }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
// a repetição do botão só vem junto de "Como ficou"
|
// um botão só, com ou sem "Como ficou" (NutriScan tem)
|
||||||
const hasMedia = screen.queryByRole("heading", { name: "Como ficou" });
|
expectContactAndNoDeadLinks(getCase(id));
|
||||||
expectContactAndNoDeadLinks(getCase(id), hasMedia ? 2 : 1);
|
|
||||||
// o laboratório não tem "o que o cliente passou a poder fazer"
|
// o laboratório não tem "o que o cliente passou a poder fazer"
|
||||||
expect(
|
expect(
|
||||||
screen.queryByRole("heading", {
|
screen.queryByRole("heading", {
|
||||||
@@ -349,7 +342,7 @@ it("turns the contact of a laboratory case into the 'Fale sobre o seu caso' invi
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("writes what the client can do now right after 'Como ficou', then repeats the contact button, for every client case", async () => {
|
it("writes what the client can do now right after 'Como ficou', with no contact button of its own, for every client case", async () => {
|
||||||
for (const id of clientCaseIds) {
|
for (const id of clientCaseIds) {
|
||||||
const caseStudy = getCase(id);
|
const caseStudy = getCase(id);
|
||||||
const { unmount } = await renderCase(id);
|
const { unmount } = await renderCase(id);
|
||||||
@@ -371,11 +364,24 @@ it("writes what the client can do now right after 'Como ficou', then repeats the
|
|||||||
expect(items.length).toBeLessThanOrEqual(3);
|
expect(items.length).toBeLessThanOrEqual(3);
|
||||||
// sem número inventado
|
// sem número inventado
|
||||||
expect(items.join(" ")).not.toMatch(/\d/);
|
expect(items.join(" ")).not.toMatch(/\d/);
|
||||||
const button = within(capabilitiesSection as HTMLElement).getByRole(
|
// o único botão de contato é o do fecho, depois da lista: não há dois "Falar sobre meu projeto" seguidos
|
||||||
"link",
|
expect(
|
||||||
{ name: buttonLabels.contact },
|
within(capabilitiesSection as HTMLElement).queryByRole("link", {
|
||||||
);
|
name: buttonLabels.contact,
|
||||||
expect(isBefore(media, button)).toBe(true);
|
}),
|
||||||
|
).toBeNull();
|
||||||
|
const closingSection = screen
|
||||||
|
.getByRole("heading", {
|
||||||
|
level: 2,
|
||||||
|
name: "Quer um projeto parecido com este?",
|
||||||
|
})
|
||||||
|
.closest("section");
|
||||||
|
expect(capabilitiesSection?.nextElementSibling).toBe(closingSection);
|
||||||
|
expect(
|
||||||
|
within(closingSection as HTMLElement).getAllByRole("link", {
|
||||||
|
name: buttonLabels.contact,
|
||||||
|
}),
|
||||||
|
).toHaveLength(1);
|
||||||
unmount();
|
unmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,16 +469,16 @@ it("shows at most two related services, as cards under the contact button", asyn
|
|||||||
expect(
|
expect(
|
||||||
screen.queryByRole("link", { name: /^Ver serviço relacionado:/ }),
|
screen.queryByRole("link", { name: /^Ver serviço relacionado:/ }),
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
// o botão do fecho (o último) vem antes dos cartões
|
// o botão do fecho (o único) vem antes dos cartões
|
||||||
const contacts = screen.getAllByRole("link", { name: buttonLabels.contact });
|
const contact = screen.getByRole("link", { name: buttonLabels.contact });
|
||||||
expect(isBefore(contacts[contacts.length - 1], cards[0])).toBe(true);
|
expect(isBefore(contact, cards[0])).toBe(true);
|
||||||
unmount();
|
unmount();
|
||||||
|
|
||||||
// sc-plus tem dois; a SEV e a AS Serviços, um cada (a SEV só aparece na página de sistemas)
|
// sc-plus e a SEV têm dois (a SEV liga à oferta de site e à de sistema); a AS Serviços, um
|
||||||
for (const [id, expected] of [
|
for (const [id, expected] of [
|
||||||
["sc-plus", 2],
|
["sc-plus", 2],
|
||||||
["as-servicos", 1],
|
["as-servicos", 1],
|
||||||
["sev-modaintima", 1],
|
["sev-modaintima", 2],
|
||||||
] as const) {
|
] as const) {
|
||||||
const { unmount: unmountCase } = await renderCase(id);
|
const { unmount: unmountCase } = await renderCase(id);
|
||||||
const links = screen.getAllByRole("link", { name: /^Ver o serviço:/ });
|
const links = screen.getAllByRole("link", { name: /^Ver o serviço:/ });
|
||||||
@@ -482,11 +488,25 @@ it("shows at most two related services, as cards under the contact button", asyn
|
|||||||
.slice(0, 2)
|
.slice(0, 2)
|
||||||
.map(({ slug }) => `/servicos/${slug}`),
|
.map(({ slug }) => `/servicos/${slug}`),
|
||||||
);
|
);
|
||||||
expectContactAndNoDeadLinks(getCase(id), 2);
|
expectContactAndNoDeadLinks(getCase(id));
|
||||||
unmountCase();
|
unmountCase();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("links the SEV case to the site offer and to the system offer, each by the name of the offer", async () => {
|
||||||
|
await renderCase("sev-modaintima");
|
||||||
|
|
||||||
|
const links = screen.getAllByRole("link", { name: /^Ver o serviço:/ });
|
||||||
|
expect(links.map((link) => link.getAttribute("href"))).toEqual([
|
||||||
|
"/servicos/criacao-de-sites",
|
||||||
|
"/servicos/engenharia-de-software",
|
||||||
|
]);
|
||||||
|
expect(links.map((link) => link.textContent)).toEqual([
|
||||||
|
"Ver o serviço: Site que traz pedidos pelo WhatsApp",
|
||||||
|
"Ver o serviço: Sistema para tirar a operação da planilha",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it("writes the CreativeWork of a client case with creator, client and keywords", async () => {
|
it("writes the CreativeWork of a client case with creator, client and keywords", async () => {
|
||||||
const caseStudy = getCase("lavacao-047");
|
const caseStudy = getCase("lavacao-047");
|
||||||
const { container } = await renderCase(caseStudy.id);
|
const { container } = await renderCase(caseStudy.id);
|
||||||
@@ -652,10 +672,15 @@ it("lists client cases with a cover first, then the laboratory, and keeps the sc
|
|||||||
expect(isBefore(clientHeading, labHeading)).toBe(true);
|
expect(isBefore(clientHeading, labHeading)).toBe(true);
|
||||||
expect(screen.getByText(/Projetos pessoais e de estudo/)).toBeVisible();
|
expect(screen.getByText(/Projetos pessoais e de estudo/)).toBeVisible();
|
||||||
|
|
||||||
// cases de cliente, com a SEV por último: capa (pôster) em moldura de navegador e botão "Ver case" em contorno
|
// cases de cliente, na ordem da vitrine (SEV primeiro): capa (pôster) em moldura de navegador e botão "Ver case" em contorno
|
||||||
const listedIds = clientCasesForListing.map(({ id }) => id);
|
const listedIds = clientCasesForListing.map(({ id }) => id);
|
||||||
expect([...listedIds].sort()).toEqual([...clientCaseIds].sort());
|
expect([...listedIds].sort()).toEqual([...clientCaseIds].sort());
|
||||||
expect(listedIds[listedIds.length - 1]).toBe("sev-modaintima");
|
expect(listedIds).toEqual([
|
||||||
|
"sev-modaintima",
|
||||||
|
"lavacao-047",
|
||||||
|
"sc-plus",
|
||||||
|
"as-servicos",
|
||||||
|
]);
|
||||||
const clientLinks = listedIds.map((id) => {
|
const clientLinks = listedIds.map((id) => {
|
||||||
const card = container
|
const card = container
|
||||||
.querySelector(`a[href="/cases/${id}"]`)
|
.querySelector(`a[href="/cases/${id}"]`)
|
||||||
|
|||||||
@@ -1,46 +1,26 @@
|
|||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
import { getShowcaseAssets } from "@/lib/project-showcase";
|
import { setNavigation } from "@/__mocks__/next/navigation";
|
||||||
import { buttonLabels } from "@/lib/site-content";
|
|
||||||
import { similarProjectSubject } from "@/lib/whatsapp";
|
|
||||||
import { CaseCapabilitiesSection } from "./CaseCapabilitiesSection";
|
import { CaseCapabilitiesSection } from "./CaseCapabilitiesSection";
|
||||||
import { getCaseMedia } from "./case-media";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Testes de caminhos-base de CaseCapabilitiesSection: com itens e "Como ficou" (lista e botão), só com "Como ficou"
|
* Testes de caminhos-base de CaseCapabilitiesSection: com itens (a lista sob o título do cliente ou do laboratório, sem
|
||||||
* (só o botão), só com itens (só a lista, sem repetir o botão que o fecho da página já traz) e sem nada (não aparece).
|
* botão de contato: o fecho do case vem logo depois e é o único convite, para não haver dois "Falar sobre meu projeto"
|
||||||
* A existência das capturas em disco é simulada em getCaseMedia.
|
* seguidos) e sem itens (a seção não aparece, tenha o case site no ar ou captura ou não).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
jest.mock("next/navigation", () => ({ usePathname: () => "/cases" }));
|
beforeEach(() => {
|
||||||
jest.mock("./case-media", () => ({ getCaseMedia: jest.fn() }));
|
setNavigation({ pathname: "/cases" });
|
||||||
|
});
|
||||||
const mockedGetCaseMedia = jest.mocked(getCaseMedia);
|
|
||||||
|
|
||||||
const capabilities = [
|
const capabilities = [
|
||||||
"O dono muda os preços sem pedir a ninguém.",
|
"O dono muda os preços sem pedir a ninguém.",
|
||||||
"O cliente pede pelo WhatsApp com a mensagem pronta.",
|
"O cliente pede pelo WhatsApp com a mensagem pronta.",
|
||||||
];
|
];
|
||||||
const withSite = {
|
|
||||||
id: "projeto-teste",
|
|
||||||
title: "Projeto Teste",
|
|
||||||
demoUrl: "https://exemplo.com.br",
|
|
||||||
};
|
|
||||||
|
|
||||||
function messageOf(link: HTMLElement) {
|
|
||||||
return new URL(link.getAttribute("href") ?? "").searchParams.get("text");
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockedGetCaseMedia.mockReturnValue(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("CaseCapabilitiesSection", () => {
|
describe("CaseCapabilitiesSection", () => {
|
||||||
it("path: items and a site on the air -> the list under the client title, then the contact button", () => {
|
it("path: items -> the list under the client title, with no contact button of its own", () => {
|
||||||
render(
|
const { container } = render(
|
||||||
<CaseCapabilitiesSection
|
<CaseCapabilitiesSection caseStudy={{ capabilities }} isClient />,
|
||||||
caseStudy={{ ...withSite, capabilities }}
|
|
||||||
isClient
|
|
||||||
/>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@@ -52,25 +32,14 @@ describe("CaseCapabilitiesSection", () => {
|
|||||||
for (const item of capabilities) {
|
for (const item of capabilities) {
|
||||||
expect(screen.getByText(item)).toBeVisible();
|
expect(screen.getByText(item)).toBeVisible();
|
||||||
}
|
}
|
||||||
const button = screen.getByRole("link", { name: buttonLabels.contact });
|
// o botão "Falar sobre meu projeto" é do fecho do case (CaseContactSection), não desta seção
|
||||||
expect(button).toHaveAttribute("target", "_blank");
|
expect(screen.queryByRole("link")).toBeNull();
|
||||||
expect(button.className).toContain("bg-[var(--slash)]");
|
expect(container.querySelectorAll("a")).toHaveLength(0);
|
||||||
expect(messageOf(button)).toContain(
|
|
||||||
`Quero conversar sobre ${similarProjectSubject(withSite.title)}.`,
|
|
||||||
);
|
|
||||||
// o botão vem depois da lista
|
|
||||||
expect(
|
|
||||||
screen.getByText(capabilities[1]).compareDocumentPosition(button) &
|
|
||||||
Node.DOCUMENT_POSITION_FOLLOWING,
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("path: a laboratory case with items -> the title does not talk about a client", () => {
|
it("path: a laboratory case with items -> the title does not talk about a client", () => {
|
||||||
render(
|
render(
|
||||||
<CaseCapabilitiesSection
|
<CaseCapabilitiesSection caseStudy={{ capabilities }} isClient={false} />,
|
||||||
caseStudy={{ ...withSite, capabilities }}
|
|
||||||
isClient={false}
|
|
||||||
/>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@@ -82,55 +51,17 @@ describe("CaseCapabilitiesSection", () => {
|
|||||||
expect(screen.queryByText(/o cliente passou a poder fazer/i)).toBeNull();
|
expect(screen.queryByText(/o cliente passou a poder fazer/i)).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("path: no items but a capture on disk (no site) -> only the contact button, under a heading that only screen readers get", () => {
|
it("path: no items -> renders nothing, with no button on its own even when the case has a capture or a site", () => {
|
||||||
mockedGetCaseMedia.mockImplementation((id, device) =>
|
|
||||||
id === "projeto-teste" ? getShowcaseAssets(id, device) : undefined,
|
|
||||||
);
|
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<CaseCapabilitiesSection
|
<CaseCapabilitiesSection caseStudy={{}} isClient />,
|
||||||
caseStudy={{
|
|
||||||
id: "projeto-teste",
|
|
||||||
title: "Projeto Teste",
|
|
||||||
demoUrl: null,
|
|
||||||
}}
|
|
||||||
isClient
|
|
||||||
/>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(container.querySelector("ul")).toBeNull();
|
expect(container).toBeEmptyDOMElement();
|
||||||
expect(
|
|
||||||
screen.getByRole("link", { name: buttonLabels.contact }),
|
|
||||||
).toBeVisible();
|
|
||||||
expect(screen.getByRole("heading", { level: 2 })).toHaveClass("sr-only");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("path: items but no 'Como ficou' -> the list stays and the button is not repeated", () => {
|
it("path: an empty list of items -> renders nothing", () => {
|
||||||
render(
|
|
||||||
<CaseCapabilitiesSection
|
|
||||||
caseStudy={{
|
|
||||||
id: "projeto-teste",
|
|
||||||
title: "Projeto Teste",
|
|
||||||
demoUrl: null,
|
|
||||||
capabilities,
|
|
||||||
}}
|
|
||||||
isClient
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByText(capabilities[0])).toBeVisible();
|
|
||||||
expect(screen.queryByRole("link")).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("path: no items and no 'Como ficou' -> renders nothing", () => {
|
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<CaseCapabilitiesSection
|
<CaseCapabilitiesSection caseStudy={{ capabilities: [] }} isClient />,
|
||||||
caseStudy={{
|
|
||||||
id: "projeto-teste",
|
|
||||||
title: "Projeto Teste",
|
|
||||||
demoUrl: null,
|
|
||||||
}}
|
|
||||||
isClient
|
|
||||||
/>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(container).toBeEmptyDOMElement();
|
expect(container).toBeEmptyDOMElement();
|
||||||
|
|||||||
@@ -1,60 +1,33 @@
|
|||||||
import { primaryButton } from "@/app/components/button-styles";
|
import type { CaseStudyContent } from "@/lib/site-content";
|
||||||
import WhatsAppLink from "@/app/components/WhatsAppLink";
|
|
||||||
import { buttonLabels, type CaseStudyContent } from "@/lib/site-content";
|
|
||||||
import { similarProjectSubject } from "@/lib/whatsapp";
|
|
||||||
import { hasCaseMedia } from "./CaseMediaSection";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logo depois de "Como ficou": o que o cliente passou a poder fazer (2 ou 3 itens, sem número, só com o que as entregas já sustentam)
|
* Logo depois de "Como ficou": o que o cliente passou a poder fazer (2 ou 3 itens, sem número, só com o que as entregas já sustentam).
|
||||||
* e, de novo, o botão de contato, para quem acabou de ver o resultado não precisar rolar até o fim da página.
|
* Sem botão de contato: o fecho do case (CaseContactSection) vem logo depois e é o único convite ao WhatsApp, para não haver dois
|
||||||
* Sem itens, só o botão (e só quando há "Como ficou"); sem itens e sem mídia, a seção não aparece.
|
* "Falar sobre meu projeto" seguidos. Sem itens, a seção não aparece.
|
||||||
*/
|
*/
|
||||||
export function CaseCapabilitiesSection({
|
export function CaseCapabilitiesSection({
|
||||||
caseStudy,
|
caseStudy,
|
||||||
isClient,
|
isClient,
|
||||||
}: {
|
}: {
|
||||||
caseStudy: Pick<
|
caseStudy: Pick<CaseStudyContent, "capabilities">;
|
||||||
CaseStudyContent,
|
|
||||||
"id" | "title" | "demoUrl" | "capabilities"
|
|
||||||
>;
|
|
||||||
isClient: boolean;
|
isClient: boolean;
|
||||||
}) {
|
}) {
|
||||||
const capabilities = caseStudy.capabilities ?? [];
|
const capabilities = caseStudy.capabilities ?? [];
|
||||||
const showContact = hasCaseMedia(caseStudy);
|
if (capabilities.length === 0) return null;
|
||||||
if (capabilities.length === 0 && !showContact) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="ds-section" aria-labelledby="case-capabilities">
|
<section className="ds-section" aria-labelledby="case-capabilities">
|
||||||
<div className="ds-section-shell">
|
<div className="ds-section-shell">
|
||||||
{capabilities.length > 0 ? (
|
<h2 id="case-capabilities" className="ds-card-title !mt-0">
|
||||||
<>
|
{isClient
|
||||||
<h2 id="case-capabilities" className="ds-card-title !mt-0">
|
? "O que o cliente passou a poder fazer"
|
||||||
{isClient
|
: "O que dá para fazer com ele"}
|
||||||
? "O que o cliente passou a poder fazer"
|
</h2>
|
||||||
: "O que dá para fazer com ele"}
|
<ul className="ds-list max-w-[78ch] space-y-sp-3 pl-[1.15rem]">
|
||||||
</h2>
|
{capabilities.map((item) => (
|
||||||
<ul className="ds-list max-w-[78ch] space-y-sp-3 pl-[1.15rem]">
|
<li key={item}>{item}</li>
|
||||||
{capabilities.map((item) => (
|
))}
|
||||||
<li key={item}>{item}</li>
|
</ul>
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<h2 id="case-capabilities" className="sr-only">
|
|
||||||
Fale sobre o seu projeto
|
|
||||||
</h2>
|
|
||||||
)}
|
|
||||||
{showContact ? (
|
|
||||||
<div className="mt-sp-8">
|
|
||||||
<WhatsAppLink
|
|
||||||
service={similarProjectSubject(caseStudy.title)}
|
|
||||||
placement="case_after_media"
|
|
||||||
className={primaryButton}
|
|
||||||
>
|
|
||||||
{buttonLabels.contact}
|
|
||||||
</WhatsAppLink>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ describe("CasesPage: cases de cliente", () => {
|
|||||||
).toBeNull();
|
).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("path: contact -> 'Falar sobre meu projeto', and the list of clients opens with Lavação 047 and ends with the SEV", () => {
|
it("path: contact -> 'Falar sobre meu projeto', and the list of clients follows the showcase: the SEV first, then Lavação 047, SC Plus and AS Serviços", () => {
|
||||||
const { container } = render(<CasesPage />);
|
const { container } = render(<CasesPage />);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@@ -89,8 +89,16 @@ describe("CasesPage: cases de cliente", () => {
|
|||||||
expect(clientTitles).toEqual(
|
expect(clientTitles).toEqual(
|
||||||
clientCasesForListing.map(({ title }) => title),
|
clientCasesForListing.map(({ title }) => title),
|
||||||
);
|
);
|
||||||
expect(clientTitles[0]).toBe("Lavação 047");
|
expect(clientTitles).toEqual([
|
||||||
expect(clientTitles[clientTitles.length - 1]).toBe("SEV Moda Íntima");
|
"SEV Moda Íntima",
|
||||||
|
"Lavação 047",
|
||||||
|
"SC Plus Aquicultura",
|
||||||
|
"AS Serviços",
|
||||||
|
]);
|
||||||
|
// um botão de contato só na página: o do fecho
|
||||||
|
expect(
|
||||||
|
screen.getAllByRole("link", { name: buttonLabels.contact }),
|
||||||
|
).toHaveLength(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user