fix(home): oferta leva ao formulário, ofertas logo depois da vitrine
O botão das ofertas rola até o contato com o tipo marcado e não quebra mais as âncoras; ofertas com o que você recebe e primeiro passo, logo depois da vitrine; bloco de IA fora da home; cartão largo da SEV no hero do celular; separador da faixa de clientes só entre itens; favicon de 13 para 1,8 KB; pré-carregamento só no hover e nunca de /#... fora da home; X do menu e cabeçalho opaco; WhatsApp com "o case"; /ia com 3 passos e modelo de relatório ilustrativo; Sobre alinhado ao currículo; CSP Report-Only com os domínios do Ads.
This commit is contained in:
1 parent
fc44a69fb3
commit
9e2d65a926
51 files changed
+1730
-420
No files matched your search
+84
-16
@@ -98,21 +98,26 @@ it("presents the review as an offer: what the person receives, with no price or
|
||||
expect(container.textContent).not.toMatch(/R\$|\d+ dias|em até \d+/);
|
||||
});
|
||||
|
||||
it("shows the report list in a card on the right half of the hero, next to the headline", () => {
|
||||
it("shows 'Como funciona em 3 passos' in a card on the right half of the hero, next to the headline, instead of repeating the deliverables", () => {
|
||||
render(<AiPage />);
|
||||
|
||||
const card = screen.getByTestId("ia-review-card");
|
||||
expect(within(card).getByText("O que você recebe")).toBeInTheDocument();
|
||||
const card = screen.getByTestId("ia-steps-card");
|
||||
expect(
|
||||
within(card)
|
||||
.getAllByRole("listitem")
|
||||
.map((item) => item.textContent?.replace(/^\d+/, "")),
|
||||
within(card).getByText("Como funciona em 3 passos"),
|
||||
).toBeInTheDocument();
|
||||
const steps = within(card).getAllByRole("listitem");
|
||||
expect(steps).toHaveLength(3);
|
||||
expect(
|
||||
steps.map((item) => item.querySelector("strong")?.textContent),
|
||||
).toEqual([
|
||||
"As falhas de acesso a dados",
|
||||
"O que dá para aproveitar",
|
||||
"O que é melhor refazer",
|
||||
"Um orçamento para pôr em produção",
|
||||
"Conversa pelo WhatsApp.",
|
||||
"Teste e avaliação.",
|
||||
"Relatório e orçamento.",
|
||||
]);
|
||||
// a conversa vem primeiro e repete o aviso da página: nada de senhas nem acessos por mensagem
|
||||
expect(steps[0]).toHaveTextContent(
|
||||
"Não envie senhas nem acessos por mensagem.",
|
||||
);
|
||||
// o cartão divide a grade do hero com o título (metade direita no desktop) e não cria uma região de página
|
||||
const hero = screen.getByRole("heading", { level: 1 }).closest("section");
|
||||
expect(hero).toContainElement(card);
|
||||
@@ -120,13 +125,22 @@ it("shows the report list in a card on the right half of the hero, next to the h
|
||||
"lg:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]",
|
||||
);
|
||||
expect(screen.queryByRole("complementary")).toBeNull();
|
||||
// o título do cartão não repete o h2 "O que você recebe na revisão" da seção seguinte
|
||||
// os quatro entregáveis ficam só na seção "O que você recebe na revisão": o hero não os repete
|
||||
expect(within(card).queryByText("O que você recebe")).toBeNull();
|
||||
for (const item of [
|
||||
"As falhas de acesso a dados",
|
||||
"O que dá para aproveitar",
|
||||
"O que é melhor refazer",
|
||||
"Um orçamento para pôr em produção",
|
||||
]) {
|
||||
expect(within(card).queryByText(item)).toBeNull();
|
||||
}
|
||||
expect(
|
||||
screen.getAllByRole("heading", { name: /O que você recebe/ }),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("keeps the secondary hero button inside the page: the portfolio cases are not reviews of AI-built apps", () => {
|
||||
it("keeps the in-page secondary link and brings back a secondary link to the cases in the hero, both as text links", () => {
|
||||
render(<AiPage />);
|
||||
|
||||
const hero = screen.getByRole("heading", { level: 1 }).closest("section");
|
||||
@@ -135,12 +149,66 @@ it("keeps the secondary hero button inside the page: the portfolio cases are not
|
||||
});
|
||||
expect(secondary).toHaveAttribute("href", "#ia-receber");
|
||||
expect(document.querySelector(`section[id="ia-receber"]`)).not.toBeNull();
|
||||
for (const link of within(hero as HTMLElement).getAllByRole("link")) {
|
||||
expect(link.getAttribute("href")).not.toMatch(/^\/cases/);
|
||||
const cases = within(hero as HTMLElement).getByRole("link", {
|
||||
name: "Ver projetos entregues",
|
||||
});
|
||||
expect(cases).toHaveAttribute("href", "/cases");
|
||||
// os dois são links de texto; o único botão cheio do hero é o do WhatsApp
|
||||
expect(cases.className).toBe(secondary.className);
|
||||
const hrefs = within(hero as HTMLElement)
|
||||
.getAllByRole("link")
|
||||
.map((link) => link.getAttribute("href"));
|
||||
expect(hrefs.filter((href) => href?.startsWith("/cases"))).toEqual([
|
||||
"/cases",
|
||||
]);
|
||||
});
|
||||
|
||||
it("shows the report model with three illustrative findings (flaw, severity, suggested fix), labelled as examples and with no client name", () => {
|
||||
const { container } = render(<AiPage />);
|
||||
|
||||
const heading = screen.getByRole("heading", {
|
||||
level: 2,
|
||||
name: "Modelo do relatório",
|
||||
});
|
||||
const section = heading.closest("section") as HTMLElement;
|
||||
expect(section).toHaveAttribute("id", "ia-modelo");
|
||||
expect(section).toHaveTextContent("exemplos ilustrativos");
|
||||
expect(section).toHaveTextContent("não são de nenhum cliente");
|
||||
|
||||
const findings = within(section).getAllByRole("article");
|
||||
expect(findings).toHaveLength(3);
|
||||
for (const [index, finding] of findings.entries()) {
|
||||
expect(finding).toHaveTextContent(`Exemplo ilustrativo 0${index + 1}`);
|
||||
expect(
|
||||
Array.from(finding.querySelectorAll("dt")).map(
|
||||
(term) => term.textContent,
|
||||
),
|
||||
).toEqual(["Falha", "Gravidade", "Correção sugerida"]);
|
||||
for (const definition of finding.querySelectorAll("dd")) {
|
||||
expect(definition.textContent?.trim()).not.toBe("");
|
||||
}
|
||||
}
|
||||
// a gravidade é uma palavra só, de uma escala simples
|
||||
expect(
|
||||
screen.queryByRole("link", { name: "Ver projetos entregues" }),
|
||||
).toBeNull();
|
||||
findings.map((finding) => finding.querySelectorAll("dd")[1].textContent),
|
||||
).toEqual(["Alta", "Alta", "Média"]);
|
||||
// sem nome de cliente do portfólio, sem preço nem prazo
|
||||
expect(section.textContent).not.toMatch(
|
||||
/SEV|Lavação|SC Plus|AS Serviços|Apolo|R\$|\d+ dias|em até \d+/,
|
||||
);
|
||||
// o modelo vem depois do que se recebe e antes de "por que contratar"
|
||||
const receive = container.querySelector("#ia-receber") as HTMLElement;
|
||||
expect(
|
||||
receive.compareDocumentPosition(section) & Node.DOCUMENT_POSITION_FOLLOWING,
|
||||
).toBeTruthy();
|
||||
const hire = screen
|
||||
.getByRole("heading", {
|
||||
name: "Se a IA cria um sistema, por que contratar um desenvolvedor?",
|
||||
})
|
||||
.closest("section") as HTMLElement;
|
||||
expect(
|
||||
section.compareDocumentPosition(hire) & Node.DOCUMENT_POSITION_FOLLOWING,
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it("keeps the incident news collapsed in a <details>, closed by default", () => {
|
||||
|
||||
Reference in new issue
Block a user