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.
563 lines
20 KiB
TypeScript
563 lines
20 KiB
TypeScript
import { render, screen, within } from "@testing-library/react";
|
|
import { SEO_IDS } from "@/lib/seo";
|
|
import {
|
|
caseStudies,
|
|
clientCases,
|
|
getRelatedCases,
|
|
getServicePage,
|
|
isClientCase,
|
|
servicePages,
|
|
siteUrl,
|
|
} from "@/lib/site-content";
|
|
import ServicesIndexPage from "../page";
|
|
import ServicePage, { generateMetadata, generateStaticParams } from "./page";
|
|
|
|
jest.mock("next/navigation", () => ({ notFound: jest.fn() }));
|
|
|
|
async function renderService(slug: string) {
|
|
const ui = await ServicePage({ params: Promise.resolve({ slug }) });
|
|
return render(ui);
|
|
}
|
|
|
|
function isBefore(first: Element, second: Element) {
|
|
return Boolean(
|
|
first.compareDocumentPosition(second) & Node.DOCUMENT_POSITION_FOLLOWING,
|
|
);
|
|
}
|
|
|
|
const service = getServicePage("consultoria-nextjs");
|
|
if (!service) throw new Error("Missing representative service");
|
|
|
|
const publicServiceSlugs = [
|
|
"criacao-de-sites",
|
|
"engenharia-de-software",
|
|
"consultoria-nextjs",
|
|
"tauri-rust",
|
|
"integracao-hardware-software-iot",
|
|
"arquitetura-de-software",
|
|
] as const;
|
|
const publicServiceHrefs = publicServiceSlugs.map(
|
|
(slug) => `/servicos/${slug}`,
|
|
);
|
|
|
|
function readJsonLdEntities(container: HTMLElement): Record<string, unknown>[] {
|
|
const scripts = container.querySelectorAll<HTMLScriptElement>(
|
|
'script[type="application/ld+json"]',
|
|
);
|
|
if (scripts.length === 0) throw new Error("Missing JSON-LD");
|
|
|
|
return Array.from(scripts).flatMap((script) => {
|
|
if (!script.textContent) throw new Error("Empty JSON-LD");
|
|
const value = JSON.parse(script.textContent) as Record<string, unknown>;
|
|
return Array.isArray(value["@graph"]) ? value["@graph"] : [value];
|
|
});
|
|
}
|
|
|
|
it("keeps static params and route metadata canonical", async () => {
|
|
const staticSlugs = generateStaticParams().map(({ slug }) => slug);
|
|
expect(staticSlugs).toHaveLength(6);
|
|
expect(new Set(staticSlugs).size).toBe(6);
|
|
expect([...staticSlugs].sort()).toEqual([...publicServiceSlugs].sort());
|
|
|
|
const metadata = await generateMetadata({
|
|
params: Promise.resolve({ slug: service.slug }),
|
|
});
|
|
const url = `${siteUrl}/servicos/${service.slug}`;
|
|
|
|
expect(metadata).toMatchObject({
|
|
title: service.metaTitle,
|
|
description: service.metaDescription,
|
|
alternates: { canonical: url },
|
|
openGraph: {
|
|
title: service.metaTitle,
|
|
description: service.metaDescription,
|
|
url,
|
|
},
|
|
twitter: {
|
|
title: service.metaTitle,
|
|
description: service.metaDescription,
|
|
},
|
|
});
|
|
expect(metadata).not.toHaveProperty("keywords");
|
|
});
|
|
|
|
it("renders answer-first consulting, reciprocal case links and minimal schema", async () => {
|
|
const { container } = await renderService(service.slug);
|
|
|
|
const answer = screen.getByText(service.answer);
|
|
expect(answer).toBeVisible();
|
|
for (const item of [
|
|
...service.outcomes,
|
|
...service.examples,
|
|
...service.process,
|
|
]) {
|
|
expect(screen.getByText(item)).toBeVisible();
|
|
}
|
|
for (const detail of service.technicalDetails) {
|
|
expect(screen.getByText(detail)).not.toBeVisible();
|
|
}
|
|
|
|
const primaryCtas = screen.getAllByRole("link", { name: /WhatsApp/i });
|
|
for (const cta of primaryCtas) {
|
|
const destination = new URL(cta.getAttribute("href") ?? "");
|
|
expect(destination.origin).toBe("https://wa.me");
|
|
expect(destination.searchParams.get("text")).toContain(service.navLabel);
|
|
expect(cta).toHaveAttribute("target", "_blank");
|
|
expect(cta).toHaveAttribute("rel", expect.stringContaining("noopener"));
|
|
}
|
|
|
|
const expectedCaseLinks = service.relatedCaseIds
|
|
.map((id) => {
|
|
const caseStudy = caseStudies.find((item) => item.id === id);
|
|
if (!caseStudy) throw new Error(`Missing related case ${id}`);
|
|
return {
|
|
name: `Ler case: ${caseStudy.title}`,
|
|
href: `/cases/${id}`,
|
|
};
|
|
})
|
|
.sort((a, b) => a.href.localeCompare(b.href));
|
|
const internalCaseLinks = screen.getAllByRole("link", {
|
|
name: /^Ler case:/,
|
|
});
|
|
expect(
|
|
internalCaseLinks
|
|
.map((link) => ({
|
|
name: link.textContent,
|
|
href: link.getAttribute("href"),
|
|
}))
|
|
.sort((a, b) => (a.href ?? "").localeCompare(b.href ?? "")),
|
|
).toEqual(expectedCaseLinks);
|
|
for (const internalLink of internalCaseLinks) {
|
|
const firstExternalLink = internalLink
|
|
.closest("article")
|
|
?.querySelector('a[target="_blank"]');
|
|
if (firstExternalLink) {
|
|
expect(
|
|
internalLink.compareDocumentPosition(firstExternalLink) &
|
|
Node.DOCUMENT_POSITION_FOLLOWING,
|
|
).toBeTruthy();
|
|
}
|
|
}
|
|
|
|
const externalLinks = container.querySelectorAll('a[target="_blank"]');
|
|
expect(externalLinks.length).toBeGreaterThan(0);
|
|
for (const externalLink of externalLinks) {
|
|
expect(externalLink).toHaveAttribute(
|
|
"rel",
|
|
expect.stringMatching(/noreferrer/),
|
|
);
|
|
expect(externalLink.getAttribute("href")).not.toMatch(/github/i);
|
|
}
|
|
const hrefs = screen
|
|
.getAllByRole("link")
|
|
.map((link) => link.getAttribute("href") ?? "");
|
|
expect(hrefs).not.toContain("/#contact");
|
|
expect(container.textContent).not.toMatch(
|
|
/Exemplos ilustrativos|não projetos já entregues|Sem promessas|indispon|Ver código/i,
|
|
);
|
|
|
|
const graph = readJsonLdEntities(container);
|
|
expect(graph.map((node) => node["@type"])).toEqual([
|
|
"Service",
|
|
"BreadcrumbList",
|
|
]);
|
|
expect(JSON.stringify(graph)).not.toMatch(
|
|
/Article|FAQPage|ProfessionalService/,
|
|
);
|
|
const serviceSchema = graph.find((node) => node["@type"] === "Service");
|
|
expect(serviceSchema).toMatchObject({
|
|
provider: { "@id": SEO_IDS.person },
|
|
});
|
|
const breadcrumb = graph.find(
|
|
(node) => node["@type"] === "BreadcrumbList",
|
|
) as {
|
|
itemListElement: Array<{
|
|
"@type": string;
|
|
position: number;
|
|
name: string;
|
|
item: string;
|
|
}>;
|
|
};
|
|
expect(breadcrumb.itemListElement).toEqual([
|
|
{ "@type": "ListItem", position: 1, name: "Início", item: siteUrl },
|
|
{
|
|
"@type": "ListItem",
|
|
position: 2,
|
|
name: "Serviços",
|
|
item: `${siteUrl}/servicos`,
|
|
},
|
|
{
|
|
"@type": "ListItem",
|
|
position: 3,
|
|
name: service.navLabel,
|
|
item: `${siteUrl}/servicos/${service.slug}`,
|
|
},
|
|
]);
|
|
|
|
const visibleBreadcrumb = screen.getByRole("navigation", {
|
|
name: "Trilha de navegação",
|
|
});
|
|
expect(
|
|
within(visibleBreadcrumb)
|
|
.getAllByRole("link")
|
|
.map((link) => [link.textContent, link.getAttribute("href")]),
|
|
).toEqual([
|
|
["Início", "/"],
|
|
["Serviços", "/servicos"],
|
|
[service.navLabel, `/servicos/${service.slug}`],
|
|
]);
|
|
});
|
|
|
|
it("fills the hub hero with a mosaic of three client posters and states the location in one line", () => {
|
|
const { container } = render(<ServicesIndexPage />);
|
|
|
|
const hero = container.querySelector("section");
|
|
expect(hero?.querySelector(".ds-section-shell")).toHaveClass("grid");
|
|
const posters = Array.from(hero?.querySelectorAll("img") ?? []);
|
|
expect(posters).toHaveLength(3);
|
|
for (const [index, poster] of posters.entries()) {
|
|
expect(decodeURIComponent(poster.getAttribute("src") ?? "")).toContain(
|
|
`/videos/${clientCases[index].id}/desktop.webp`,
|
|
);
|
|
expect(poster).toHaveAttribute("alt", "");
|
|
}
|
|
// só o primeiro, o maior, carrega na hora e com prioridade alta
|
|
expect(posters[0]).toHaveAttribute("fetchpriority", "high");
|
|
expect(posters[1]).toHaveAttribute("loading", "lazy");
|
|
expect(hero).toHaveTextContent(
|
|
"Garuva e Joinville (SC) · atendimento remoto em todo o Brasil",
|
|
);
|
|
expect(container.textContent).not.toMatch(/perto de Garuva/);
|
|
|
|
// as capas dos projetos, mais abaixo, têm a largura da coluna e carregam preguiçosas
|
|
const cover = container.querySelector("#projetos .ds-bframe img");
|
|
expect(cover?.getAttribute("sizes")).toMatch(/calc\(100vw - 40px\)$/);
|
|
expect(cover).toHaveAttribute("loading", "lazy");
|
|
});
|
|
|
|
it("keeps hub cards and structured items aligned", () => {
|
|
const { container } = render(<ServicesIndexPage />);
|
|
|
|
const graph = readJsonLdEntities(container);
|
|
expect(graph.map((node) => node["@type"])).toEqual([
|
|
"CollectionPage",
|
|
"ItemList",
|
|
"BreadcrumbList",
|
|
]);
|
|
const itemList = graph[1] as {
|
|
itemListElement: Array<{ position: number; url: string; name: string }>;
|
|
};
|
|
expect(itemList.itemListElement.map(({ position }) => position)).toEqual([
|
|
1, 2, 3, 4, 5, 6,
|
|
]);
|
|
const structuredUrls = itemList.itemListElement.map(({ url }) => url);
|
|
expect(structuredUrls).toHaveLength(6);
|
|
expect(new Set(structuredUrls).size).toBe(6);
|
|
expect([...structuredUrls].sort()).toEqual(
|
|
publicServiceHrefs.map((href) => `${siteUrl}${href}`).sort(),
|
|
);
|
|
const visibleCards = itemList.itemListElement.map(({ name }) =>
|
|
screen.getByRole("link", { name }),
|
|
);
|
|
const visibleHrefs = visibleCards.map((link) => link.getAttribute("href"));
|
|
expect(visibleHrefs).toHaveLength(6);
|
|
expect(new Set(visibleHrefs).size).toBe(6);
|
|
expect([...visibleHrefs].sort()).toEqual([...publicServiceHrefs].sort());
|
|
expect(graph[2]).toMatchObject({
|
|
"@type": "BreadcrumbList",
|
|
itemListElement: [
|
|
{ position: 1, name: "Início", item: siteUrl },
|
|
{ position: 2, name: "Serviços", item: `${siteUrl}/servicos` },
|
|
],
|
|
});
|
|
});
|
|
|
|
it("shows the client projects with covers on the hub, without disclaimers", () => {
|
|
const { container } = render(<ServicesIndexPage />);
|
|
|
|
for (const id of [
|
|
"sev-modaintima",
|
|
"lavacao-047",
|
|
"sc-plus",
|
|
"as-servicos",
|
|
]) {
|
|
const link = container.querySelector(`a[href="/cases/${id}"]`);
|
|
expect(
|
|
link?.closest("article")?.querySelector(".ds-bframe img"),
|
|
).not.toBeNull();
|
|
}
|
|
expect(container.textContent).not.toMatch(
|
|
/sem promessas|Demonstração publicada|limites|indispon/i,
|
|
);
|
|
const hrefs = screen
|
|
.getAllByRole("link")
|
|
.map((link) => link.getAttribute("href") ?? "");
|
|
expect(hrefs).not.toContain("/#contact");
|
|
expect(hrefs.some((href) => /github/i.test(href))).toBe(false);
|
|
});
|
|
|
|
it("shows the client cases first, right after the hero, with the first cover in a frame", async () => {
|
|
const { container } = await renderService("criacao-de-sites");
|
|
const siteService = getServicePage("criacao-de-sites");
|
|
if (!siteService) throw new Error("Missing service");
|
|
|
|
const casesHeading = screen.getByRole("heading", {
|
|
level: 2,
|
|
name: "Onde já apliquei isso",
|
|
});
|
|
expect(
|
|
isBefore(screen.getByRole("heading", { level: 1 }), casesHeading),
|
|
).toBe(true);
|
|
expect(
|
|
isBefore(
|
|
casesHeading,
|
|
screen.getByRole("heading", { level: 2, name: "Para quem faz sentido" }),
|
|
),
|
|
).toBe(true);
|
|
|
|
const caseLinks = screen.getAllByRole("link", { name: /^Ler case:/ });
|
|
expect(caseLinks.map((link) => link.getAttribute("href"))).toEqual(
|
|
getRelatedCases(siteService.relatedCaseIds).map(({ id }) => `/cases/${id}`),
|
|
);
|
|
expect(caseLinks.map((link) => link.getAttribute("href"))).toEqual([
|
|
"/cases/sev-modaintima",
|
|
"/cases/lavacao-047",
|
|
"/cases/sc-plus",
|
|
"/cases/as-servicos",
|
|
]);
|
|
|
|
// pôster do primeiro case numa moldura no topo, no lugar do fundo verde
|
|
const hero = container.querySelector("section");
|
|
const heroFrame = hero?.querySelector(".ds-bframe");
|
|
expect(heroFrame).not.toBeNull();
|
|
expect(heroFrame?.querySelector("img")).toHaveAttribute(
|
|
"alt",
|
|
"Captura de tela do site SEV Moda Íntima",
|
|
);
|
|
expect(hero?.querySelector("a[href='/cases/sev-modaintima']")).not.toBeNull();
|
|
});
|
|
|
|
it("shows no case, no frame and no 'similar projects' button on the diagnosis service, only the Apolo note", async () => {
|
|
const { container } = await renderService("arquitetura-de-software");
|
|
|
|
// nenhum case de cliente é um diagnóstico: só a experiência na Apolo, em texto
|
|
expect(
|
|
screen.getByRole("heading", { name: "Onde já apliquei isso" }),
|
|
).toBeVisible();
|
|
expect(screen.getByText(/Apolo Energia/)).toBeVisible();
|
|
expect(screen.queryByRole("link", { name: /^Ler case:/ })).toBeNull();
|
|
expect(
|
|
screen.queryByRole("link", { name: "Ver projetos parecidos" }),
|
|
).toBeNull();
|
|
expect(screen.queryByText(/SEV Moda Íntima/)).toBeNull();
|
|
expect(container.querySelector(".ds-bframe")).toBeNull();
|
|
});
|
|
|
|
it("lists the Windows app as a 'Projeto próprio' on the desktop service, saying it is made in Python", async () => {
|
|
const { container } = await renderService("tauri-rust");
|
|
|
|
expect(
|
|
screen.getByRole("link", { name: "Ler case: MinGW GCC Installer" }),
|
|
).toHaveAttribute("href", "/cases/gcc-installer");
|
|
expect(
|
|
screen.getByText("MinGW GCC Installer · Projeto próprio"),
|
|
).toBeVisible();
|
|
expect(screen.getByText(/em Python e não em Tauri/)).toBeVisible();
|
|
expect(
|
|
screen.getByRole("link", { name: "Ver projetos parecidos" }),
|
|
).toHaveAttribute("href", "#casos-relacionados");
|
|
// sem captura gravada, não há moldura nem imagem
|
|
expect(container.querySelector(".ds-bframe")).toBeNull();
|
|
});
|
|
|
|
it("keeps the hero in one lower column when there is no media, and in two columns when there is", async () => {
|
|
for (const slug of [
|
|
"tauri-rust",
|
|
"integracao-hardware-software-iot",
|
|
"arquitetura-de-software",
|
|
]) {
|
|
const { container, unmount } = await renderService(slug);
|
|
const hero = container.querySelector("section");
|
|
expect(hero?.className).toContain("md:!pb-sp-12");
|
|
expect(hero?.querySelector(".ds-section-shell")).not.toHaveClass("grid");
|
|
unmount();
|
|
}
|
|
|
|
const withMedia = await renderService("criacao-de-sites");
|
|
const hero = withMedia.container.querySelector("section");
|
|
expect(hero?.className).not.toContain("md:!pb-sp-12");
|
|
expect(hero?.querySelector(".ds-section-shell")).toHaveClass("grid");
|
|
const cover = hero?.querySelector(".ds-bframe img");
|
|
// a largura real da coluna da direita, e não 100vw; é a imagem que abre a página
|
|
expect(cover?.getAttribute("sizes")).toMatch(/calc\(100vw - 40px\)$/);
|
|
expect(cover).toHaveAttribute("loading", "eager");
|
|
expect(cover).toHaveAttribute("fetchpriority", "high");
|
|
});
|
|
|
|
it("puts the location in one line, 'Garuva e Joinville (SC)', and spaces the list items", async () => {
|
|
const { container } = await renderService("consultoria-nextjs");
|
|
|
|
expect(container.textContent).toMatch(
|
|
/Garuva e Joinville \(SC\) · atendimento remoto em todo o Brasil/,
|
|
);
|
|
expect(container.textContent).not.toMatch(/a partir de Garuva/);
|
|
const lists = Array.from(container.querySelectorAll("ul.ds-list"));
|
|
expect(lists).toHaveLength(4);
|
|
for (const list of lists) {
|
|
expect(list).toHaveClass("space-y-sp-2");
|
|
}
|
|
});
|
|
|
|
it("labels the Twins Lab prototype as a laboratory project on the IoT service", async () => {
|
|
const { container } = await renderService("integracao-hardware-software-iot");
|
|
|
|
const caseLinks = screen.getAllByRole("link", { name: /^Ler case:/ });
|
|
expect(caseLinks.map((link) => link.getAttribute("href"))).toEqual([
|
|
"/cases/twins-lab",
|
|
]);
|
|
expect(screen.getByText("Twins Lab · Projeto próprio")).toBeVisible();
|
|
expect(screen.queryByText(/Protótipo de laboratório/)).toBeNull();
|
|
// sem captura gravada, não há moldura nem imagem
|
|
expect(container.querySelector(".ds-bframe")).toBeNull();
|
|
});
|
|
|
|
it("labels only the laboratory projects as 'Projeto próprio'", async () => {
|
|
for (const slug of ["criacao-de-sites", "integracao-hardware-software-iot"]) {
|
|
const { unmount } = await renderService(slug);
|
|
const related = getRelatedCases(getServicePage(slug)?.relatedCaseIds ?? []);
|
|
expect(related.length).toBeGreaterThan(0);
|
|
const labels = screen.queryAllByText(/· Projeto próprio$/);
|
|
expect(labels).toHaveLength(related.filter((c) => !isClientCase(c)).length);
|
|
unmount();
|
|
}
|
|
});
|
|
|
|
it("says in one sentence, per case, why it matters to the service", async () => {
|
|
for (const { slug, relatedCaseIds, caseRelevance } of servicePages) {
|
|
if (relatedCaseIds.length === 0) continue;
|
|
const { unmount } = await renderService(slug);
|
|
|
|
for (const id of relatedCaseIds) {
|
|
const sentence = caseRelevance[id];
|
|
expect(sentence).toBeTruthy();
|
|
const card = screen.getByText(sentence).closest("article");
|
|
expect(card).toContainElement(
|
|
screen.getByRole("link", {
|
|
name: `Ler case: ${caseStudies.find((c) => c.id === id)?.title}`,
|
|
}),
|
|
);
|
|
}
|
|
unmount();
|
|
}
|
|
});
|
|
|
|
it("cites the Apolo Energia experience in text on the IoT and architecture pages, with no link", async () => {
|
|
for (const slug of [
|
|
"integracao-hardware-software-iot",
|
|
"arquitetura-de-software",
|
|
]) {
|
|
const note = getServicePage(slug)?.experienceNote ?? "";
|
|
const { container, unmount } = await renderService(slug);
|
|
|
|
const paragraph = screen.getByText(/Apolo Energia/);
|
|
expect(paragraph).toBeVisible();
|
|
expect(paragraph.textContent).toContain(note);
|
|
expect(paragraph.textContent).toMatch(/Desde 2022/);
|
|
expect(paragraph.closest("a")).toBeNull();
|
|
expect(paragraph.querySelector("a")).toBeNull();
|
|
// dentro do bloco "Onde já apliquei isso", antes dos cards
|
|
const block = screen.getByRole("heading", {
|
|
name: "Onde já apliquei isso",
|
|
});
|
|
expect(isBefore(block, paragraph)).toBe(true);
|
|
expect(
|
|
isBefore(
|
|
paragraph,
|
|
container.querySelector("article.ds-card") as Element,
|
|
),
|
|
).toBe(true);
|
|
const hrefs = screen
|
|
.getAllByRole("link")
|
|
.map((link) => link.getAttribute("href") ?? "");
|
|
expect(hrefs.some((href) => /apolo/i.test(href))).toBe(false);
|
|
unmount();
|
|
}
|
|
});
|
|
|
|
it("does not mention Apolo Energia on the other service pages", async () => {
|
|
for (const slug of [
|
|
"criacao-de-sites",
|
|
"engenharia-de-software",
|
|
"consultoria-nextjs",
|
|
"tauri-rust",
|
|
]) {
|
|
const { container, unmount } = await renderService(slug);
|
|
expect(container.textContent).not.toMatch(/Apolo/);
|
|
unmount();
|
|
}
|
|
});
|
|
|
|
it("puts the FAQ before the next step and ends every service page with a contact button", async () => {
|
|
for (const { slug, faq, ctaLabel } of servicePages) {
|
|
const { container, unmount } = await renderService(slug);
|
|
|
|
const nextStep = screen.getByRole("heading", {
|
|
level: 2,
|
|
name: "Conte o que você precisa resolver.",
|
|
});
|
|
if (faq?.length) {
|
|
const faqHeading = screen.getByRole("heading", {
|
|
level: 2,
|
|
name: "Perguntas frequentes",
|
|
});
|
|
expect(isBefore(faqHeading, nextStep)).toBe(true);
|
|
}
|
|
|
|
// o botão de contato vem depois das dúvidas: o último elemento da página é a seção do próximo passo
|
|
const last = container.querySelector("main")?.lastElementChild;
|
|
expect(last).toContainElement(nextStep);
|
|
const cta = within(last as HTMLElement).getByRole("link", {
|
|
name: `${ctaLabel} no WhatsApp`,
|
|
});
|
|
expect(new URL(cta.getAttribute("href") ?? "").origin).toBe(
|
|
"https://wa.me",
|
|
);
|
|
for (const question of faq ?? []) {
|
|
expect(
|
|
isBefore(screen.getByRole("heading", { name: question.question }), cta),
|
|
).toBe(true);
|
|
}
|
|
unmount();
|
|
}
|
|
});
|
|
|
|
it("renders the offer block only when some field is filled in", async () => {
|
|
const empty = await renderService("criacao-de-sites");
|
|
expect(
|
|
screen.queryByRole("heading", { name: "Investimento e prazo" }),
|
|
).toBeNull();
|
|
empty.unmount();
|
|
|
|
const offerService = getServicePage("criacao-de-sites");
|
|
if (!offerService) throw new Error("Missing service");
|
|
offerService.typicalTimeline = "Texto de prazo de teste";
|
|
try {
|
|
await renderService("criacao-de-sites");
|
|
expect(
|
|
screen.getByRole("heading", { level: 2, name: "Investimento e prazo" }),
|
|
).toBeVisible();
|
|
expect(screen.getByText("Prazo típico")).toBeVisible();
|
|
expect(screen.getByText("Texto de prazo de teste")).toBeVisible();
|
|
expect(screen.queryByText("A partir de")).toBeNull();
|
|
expect(screen.queryByText("Garantia")).toBeNull();
|
|
} finally {
|
|
delete offerService.typicalTimeline;
|
|
}
|
|
});
|
|
|
|
it("keeps related case ids resolving to real cases", () => {
|
|
for (const id of service.relatedCaseIds) {
|
|
expect(caseStudies.some((item) => item.id === id)).toBe(true);
|
|
}
|
|
});
|