feat(site): blocos 3D da marca, novas ofertas e hero mais abrangente
Hero e movimento - cena 3D em tempo real com os blocos da marca (kit feito no Blender, 3d/blocos.py): marca, tela, integrações e dados; segue a rolagem, tem vida em repouso, reage ao cursor e, parada no topo, passa sozinha pelos estados com legenda - três níveis de aparelho (full, lite, static); sem WebGL fica o pôster - título novo: "Sites e sistemas sob medida para o jeito que a sua empresa trabalha." - faixa de clientes com o ramo, linha do primeiro passo, hero alinhado às seções - rótulos digitados, entrada dos blocos, cortina de blocos entre páginas e capa compartilhada entre a lista de cases e o case Ofertas - atendimento no WhatsApp com IA e nota fiscal para a reforma tributária (páginas de serviço, cartões da home, formulário e aviso com data de validade) - página /ia atualizada, com fontes - peças 3D em loop nos cartões e topos de página (3d/pecas.py) Design - hover por papel (bloco nos botões, barra nos links), rótulos sem caixa alta - um destino por rótulo: "Falar sobre meu projeto" leva ao formulário, "Chamar no WhatsApp" abre o WhatsApp - logo montado em blocos, 404 redesenhado, grades sem célula vazia - ícones do PWA e manifesto novos Specs em specs/, contexto de design em DESIGN.md e PRODUCT.md.
This commit is contained in:
1 parent
4671837c44
commit
c7d09d63d4
165 files changed
+7940
-3790
No files matched your search
@@ -5,9 +5,10 @@ import { WorkModalities } from "./WorkModalities";
|
||||
|
||||
/**
|
||||
* Testes de caminhos-base de WorkModalities e OfferCard: a entrada das ofertas (Reveal, CSS), o toque e o passe do
|
||||
* mouse (classes cv-press e cv-lift, sem foco nos cartões), as três ofertas (nome e "O que você recebe" vindos de
|
||||
* `serviceOffers`, foto do case ou ilustração, botão que abre o formulário com o tipo escolhido, "Primeiro passo" uma
|
||||
* vez só depois dos cartões), o estilo único de cartão, o texto de pelo menos 15 px e a lista compacta de etapas.
|
||||
* mouse (classes cv-press e cv-lift, sem foco nos cartões), as quatro ofertas (nome e "O que você recebe" vindos de
|
||||
* `serviceOffers`, foto do case, vídeo ou ilustração, botão que abre o formulário com o tipo escolhido, "Primeiro passo"
|
||||
* uma vez só depois dos cartões), a faixa da nota fiscal acima deles, o estilo único de cartão, o texto de pelo menos
|
||||
* 15 px e a lista compacta de etapas.
|
||||
* Nada de animação em JavaScript: o movimento reduzido é tratado pelo CSS (globals.css e conversion.css).
|
||||
*/
|
||||
|
||||
@@ -33,6 +34,18 @@ jest.mock("./Section", () => ({
|
||||
|
||||
jest.mock("./animations/DrawLine", () => ({ DrawLine: () => null }));
|
||||
|
||||
// o jsdom não toca vídeo: os cartões do sistema e do atendimento chamam play() ao aparecer
|
||||
beforeEach(() => {
|
||||
jest.spyOn(HTMLMediaElement.prototype, "play").mockResolvedValue(undefined);
|
||||
jest.spyOn(HTMLMediaElement.prototype, "pause").mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
const HOME_OFFERS = serviceOffers.filter(({ id }) => id !== "fiscal");
|
||||
|
||||
const original = (image: Element) =>
|
||||
new URL(image.getAttribute("src") ?? "", "http://localhost").searchParams.get(
|
||||
"url",
|
||||
@@ -43,20 +56,20 @@ describe("WorkModalities: movimento", () => {
|
||||
const { container } = render(<WorkModalities />);
|
||||
|
||||
const reveals = [...container.querySelectorAll<HTMLElement>(".ds-reveal")];
|
||||
expect(reveals).toHaveLength(3);
|
||||
expect(reveals).toHaveLength(4);
|
||||
expect(
|
||||
reveals.map((element) => element.style.getPropertyValue("--reveal-y")),
|
||||
).toEqual(["20px", "20px", "20px"]);
|
||||
).toEqual(["20px", "20px", "20px", "20px"]);
|
||||
expect(
|
||||
reveals.map((element) =>
|
||||
element.style.getPropertyValue("--reveal-duration"),
|
||||
),
|
||||
).toEqual(["0.75s", "0.75s", "0.75s"]);
|
||||
).toEqual(["0.75s", "0.75s", "0.75s", "0.75s"]);
|
||||
expect(
|
||||
reveals.map((element) =>
|
||||
Number.parseFloat(element.style.getPropertyValue("--reveal-delay")),
|
||||
),
|
||||
).toEqual([0.08, 0.16, 0.24]);
|
||||
).toEqual([0.08, 0.16, 0.24, 0.32]);
|
||||
// o estado escondido vive no CSS: sem opacidade nem transformação inline
|
||||
for (const element of reveals) {
|
||||
expect(element.style.opacity).toBe("");
|
||||
@@ -68,7 +81,7 @@ describe("WorkModalities: movimento", () => {
|
||||
const { container } = render(<WorkModalities />);
|
||||
|
||||
const cards = container.querySelectorAll("article");
|
||||
expect(cards).toHaveLength(3);
|
||||
expect(cards).toHaveLength(4);
|
||||
for (const card of cards) {
|
||||
expect(card).not.toHaveAttribute("tabindex");
|
||||
expect(card.className).not.toContain("outline-none");
|
||||
@@ -87,7 +100,7 @@ describe("WorkModalities: movimento", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("WorkModalities: as três ofertas", () => {
|
||||
describe("WorkModalities: as quatro ofertas", () => {
|
||||
it("path: the section title is 'Escolha por onde começar' and the offers follow the headline, in order, with the names of serviceOffers", () => {
|
||||
const { container } = render(<WorkModalities />);
|
||||
|
||||
@@ -98,7 +111,7 @@ describe("WorkModalities: as três ofertas", () => {
|
||||
}),
|
||||
).toBeInTheDocument();
|
||||
const articles = Array.from(container.querySelectorAll("article"));
|
||||
expect(articles).toHaveLength(3);
|
||||
expect(articles).toHaveLength(4);
|
||||
const titles = articles.map(
|
||||
(article) =>
|
||||
within(article).getByRole("heading", { level: 3 }).textContent,
|
||||
@@ -106,10 +119,24 @@ describe("WorkModalities: as três ofertas", () => {
|
||||
expect(titles).toEqual([
|
||||
"Site que traz pedidos pelo WhatsApp",
|
||||
"Sistema para tirar a operação da planilha",
|
||||
"Atendimento no WhatsApp com IA sob controle",
|
||||
"Revisão de sistema que já existe",
|
||||
]);
|
||||
// a fonte é uma só, a mesma de /servicos
|
||||
expect(titles).toEqual(serviceOffers.map(({ name }) => name));
|
||||
// a fonte é uma só, a mesma de /servicos; a nota fiscal fica na faixa, sem cartão
|
||||
expect(titles).toEqual(HOME_OFFERS.map(({ name }) => name));
|
||||
expect(
|
||||
screen.getByText(
|
||||
"Quatro pontos de partida. Escolha o que mais se parece com o seu caso: o formulário de contato já abre com o tipo de projeto marcado.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("path: two columns from 1024 px up and one below it", () => {
|
||||
const { container } = render(<WorkModalities />);
|
||||
|
||||
const grid = container.querySelector("article")?.closest(".grid");
|
||||
expect(grid).toHaveClass("grid-cols-1", "lg:grid-cols-2");
|
||||
expect(grid?.className).not.toMatch(/grid-cols-3/);
|
||||
});
|
||||
|
||||
it("path: the site offer cites the Lavação 047 and its deliverables are the ones of serviceOffers, the same list of /servicos", () => {
|
||||
@@ -124,10 +151,10 @@ describe("WorkModalities: as três ofertas", () => {
|
||||
).toEqual(serviceOffers[0].deliverables);
|
||||
});
|
||||
|
||||
it("path: only the site offer shows a photo (the Lavação 047 poster, sized for a third of the row); the other two have an illustration of their own", () => {
|
||||
it("path: only the site offer shows a photo (the Lavação 047 poster, sized for half the row); the system, the WhatsApp and the review cards loop a video", () => {
|
||||
const { container } = render(<WorkModalities />);
|
||||
|
||||
const [site, system, review] = Array.from(
|
||||
const [site, system, whatsapp, review] = Array.from(
|
||||
container.querySelectorAll("article"),
|
||||
);
|
||||
const siteImage = within(site).getByRole("img");
|
||||
@@ -137,55 +164,114 @@ describe("WorkModalities: as três ofertas", () => {
|
||||
);
|
||||
expect(siteImage).toHaveAttribute(
|
||||
"sizes",
|
||||
"(min-width: 1024px) 33vw, 100vw",
|
||||
"(min-width: 1024px) 50vw, 100vw",
|
||||
);
|
||||
expect(siteImage).toHaveAttribute("loading", "lazy");
|
||||
// a vitrine da SEV é uma loja, não um painel: contradiria o texto do sistema
|
||||
expect(system.querySelector("img")).toBeNull();
|
||||
expect(review.querySelector("img")).toBeNull();
|
||||
for (const card of [system, whatsapp, review]) {
|
||||
expect(card.querySelector("img")).toBeNull();
|
||||
}
|
||||
expect(container.innerHTML).not.toContain("sev-modaintima/desktop.webp");
|
||||
expect(site.querySelector("video")).toBeNull();
|
||||
expect(review.querySelector("video")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("path: the system and the review cards have no black box with an icon: each has an illustration in the same box as the site photo", () => {
|
||||
it("path: the system, the WhatsApp and the review videos are decorative 3D pieces: muted loop, no preload, the poster first, webm then mp4", () => {
|
||||
render(<WorkModalities />);
|
||||
|
||||
for (const id of ["sistema", "whatsapp", "revisao"]) {
|
||||
const art = screen.getByTestId(`offer-art-${id}`);
|
||||
const video = art.querySelector("video") as HTMLVideoElement;
|
||||
expect(video).not.toBeNull();
|
||||
expect(video).toHaveAttribute("aria-hidden", "true");
|
||||
expect(video).toHaveAttribute("tabindex", "-1");
|
||||
expect(video.muted).toBe(true);
|
||||
expect(video.loop).toBe(true);
|
||||
expect(video).toHaveAttribute("playsinline");
|
||||
expect(video).toHaveAttribute("preload", "none");
|
||||
expect(video).toHaveAttribute("poster", `/3d/peca-${id}.webp`);
|
||||
expect(
|
||||
Array.from(video.querySelectorAll("source")).map((source) => [
|
||||
source.getAttribute("src"),
|
||||
source.getAttribute("type"),
|
||||
]),
|
||||
).toEqual([
|
||||
[`/3d/peca-${id}.webm`, "video/webm"],
|
||||
[`/3d/peca-${id}.mp4`, "video/mp4"],
|
||||
]);
|
||||
}
|
||||
// a planilha em SVG saiu do cartão do sistema
|
||||
expect(
|
||||
screen.getByTestId("offer-art-sistema").querySelector("svg"),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("path: the WhatsApp video carries a decorative app badge in the top left corner: the WhatsApp mark in its green and the word, light on dark", () => {
|
||||
render(<WorkModalities />);
|
||||
|
||||
const art = screen.getByTestId("offer-art-whatsapp");
|
||||
expect(art.querySelector("video")?.parentElement).toHaveClass("relative");
|
||||
const badge = within(art).getByText("WhatsApp");
|
||||
expect(badge).toHaveAttribute("aria-hidden", "true");
|
||||
expect(badge).toHaveClass("absolute", "top-2", "left-2", "text-white");
|
||||
expect(badge.className).toMatch(/bg-black\/\d+/);
|
||||
expect(badge.className).not.toMatch(/rounded/);
|
||||
const mark = badge.querySelector("svg");
|
||||
expect(mark).not.toBeNull();
|
||||
expect(mark?.getAttribute("class")).toContain("text-[var(--whatsapp)]");
|
||||
// só o vídeo do atendimento leva o selo
|
||||
for (const id of ["sistema", "revisao"]) {
|
||||
expect(
|
||||
within(screen.getByTestId(`offer-art-${id}`)).queryByText("WhatsApp"),
|
||||
).toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
it("path: the report excerpt lies over the right edge of the review video, vertically centered, in a wide box, and below the frame in a narrow one, 20% larger (18 px)", () => {
|
||||
render(<WorkModalities />);
|
||||
|
||||
const art = screen.getByTestId("offer-art-revisao");
|
||||
expect(art).toHaveClass("@container", "relative");
|
||||
const excerpt = within(art).getByRole("img");
|
||||
// depois do quadro do vídeo, nunca dentro dele: na caixa estreita fica embaixo, sem cobrir a janela
|
||||
expect(
|
||||
excerpt.previousElementSibling?.querySelector("video"),
|
||||
).not.toBeNull();
|
||||
expect(excerpt).not.toHaveClass("absolute");
|
||||
expect(excerpt).toHaveClass(
|
||||
"@[29rem]:absolute",
|
||||
"@[29rem]:inset-y-0",
|
||||
"@[29rem]:right-0",
|
||||
"@[29rem]:items-end",
|
||||
"@[29rem]:justify-center",
|
||||
);
|
||||
for (const item of excerpt.querySelectorAll("li")) {
|
||||
expect(item).toHaveClass("text-[18px]");
|
||||
}
|
||||
expect(within(excerpt).getByText("Exemplo ilustrativo")).toHaveClass(
|
||||
"text-[18px]",
|
||||
);
|
||||
});
|
||||
|
||||
it("path: no black box with an icon: the photo and the videos sit in the same frame, in the same box", () => {
|
||||
const { container } = render(<WorkModalities />);
|
||||
|
||||
expect(screen.queryByTestId("offer-icon")).toBeNull();
|
||||
const boxes = ["site", "sistema", "revisao"].map((id) =>
|
||||
const boxes = HOME_OFFERS.map(({ id }) =>
|
||||
screen.getByTestId(`offer-art-${id}`),
|
||||
);
|
||||
// as três caixas têm as mesmas classes, então o mesmo tamanho
|
||||
// as quatro caixas e os quatro quadros têm as mesmas classes, então o mesmo tamanho
|
||||
expect(new Set(boxes.map((box) => box.className)).size).toBe(1);
|
||||
for (const box of boxes) {
|
||||
expect(box.className).toContain("aspect-[16/10]");
|
||||
expect(box.className).toContain("md:aspect-[21/9]");
|
||||
expect(box.className).toContain("lg:aspect-[16/10]");
|
||||
expect(box.firstElementChild).not.toBeNull();
|
||||
const frames = boxes.map((box) => box.firstElementChild as HTMLElement);
|
||||
expect(new Set(frames.map((frame) => frame.className)).size).toBe(1);
|
||||
for (const frame of frames) {
|
||||
expect(frame.className).toContain("aspect-[16/10]");
|
||||
expect(frame.className).toContain("md:aspect-[21/9]");
|
||||
expect(frame.className).toContain("lg:aspect-[16/10]");
|
||||
expect(frame.querySelector("img, video")).not.toBeNull();
|
||||
}
|
||||
expect(container.querySelectorAll("article")).toHaveLength(3);
|
||||
expect(container.querySelectorAll("article")).toHaveLength(4);
|
||||
});
|
||||
|
||||
it("path: the system illustration is a sheet -> arrow -> dashboard scheme in SVG, with three indicators, no text and no numbers", () => {
|
||||
render(<WorkModalities />);
|
||||
|
||||
const art = screen.getByTestId("offer-art-sistema");
|
||||
const scheme = within(art).getByRole("img");
|
||||
expect(scheme.tagName.toLowerCase()).toBe("svg");
|
||||
expect(scheme).toHaveAccessibleName(
|
||||
"Esquema ilustrativo: as linhas de uma planilha viram um painel com três indicadores e um gráfico de barras",
|
||||
);
|
||||
expect(scheme.getAttribute("viewBox")).toBe("0 0 240 150");
|
||||
expect(scheme.querySelector('[data-art="planilha"]')).not.toBeNull();
|
||||
expect(scheme.querySelector('[data-art="seta"]')).not.toBeNull();
|
||||
const panel = scheme.querySelector('[data-art="painel"]') as Element;
|
||||
expect(panel).not.toBeNull();
|
||||
// três indicadores genéricos: um quadro com um traço de rótulo e um bloco de valor cada, sem dígito algum
|
||||
expect(panel.querySelectorAll("g").length).toBe(3);
|
||||
expect(scheme.querySelector("text")).toBeNull();
|
||||
expect(art.textContent).toBe("");
|
||||
// nada de cor de destaque: o limão é só do contato
|
||||
expect(art.innerHTML).not.toMatch(/ccff00|var\(--slash\)/i);
|
||||
});
|
||||
|
||||
it("path: the review illustration is an excerpt of the report model of /ia: three findings with a severity badge and the caption 'Exemplo ilustrativo'", () => {
|
||||
render(<WorkModalities />);
|
||||
|
||||
@@ -206,26 +292,21 @@ describe("WorkModalities: as três ofertas", () => {
|
||||
for (const severity of ["Alta", "Média"]) {
|
||||
expect(within(art).getAllByText(severity).length).toBeGreaterThan(0);
|
||||
}
|
||||
// o texto da ilustração também tem 15 px, como o resto da seção
|
||||
// o texto do recorte tem 18 px embaixo do vídeo e 15 px ao lado da janela; nunca abaixo dos 15 px do resto da seção
|
||||
expect(art.innerHTML).not.toMatch(/text-\[(?:[0-9]|1[0-4])px\]/);
|
||||
});
|
||||
|
||||
it("path: the deliverables of the system and of the review come from serviceOffers, the same lists of /servicos", () => {
|
||||
it("path: the deliverables of the system, the WhatsApp and the review come from serviceOffers, the same lists of /servicos", () => {
|
||||
const { container } = render(<WorkModalities />);
|
||||
|
||||
const [, system, review] = Array.from(
|
||||
container.querySelectorAll("article"),
|
||||
);
|
||||
expect(
|
||||
within(system)
|
||||
.getAllByRole("listitem")
|
||||
.map((item) => item.textContent),
|
||||
).toEqual(serviceOffers[1].deliverables);
|
||||
expect(
|
||||
within(review)
|
||||
.getAllByRole("listitem")
|
||||
.map((item) => item.textContent),
|
||||
).toEqual(serviceOffers[2].deliverables);
|
||||
const articles = Array.from(container.querySelectorAll("article"));
|
||||
HOME_OFFERS.forEach((offer, index) => {
|
||||
expect(
|
||||
within(articles[index])
|
||||
.getAllByRole("listitem")
|
||||
.map((item) => item.textContent),
|
||||
).toEqual(offer.deliverables);
|
||||
});
|
||||
});
|
||||
|
||||
it("path: every card says what you receive, with three or four items, once", () => {
|
||||
@@ -241,7 +322,7 @@ describe("WorkModalities: as três ofertas", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("path: the first step shows once, below the three cards: a first conversation and, after it, the scope and the proposal in writing", () => {
|
||||
it("path: the first step shows once, below the cards: a first conversation and, after it, the scope and the proposal in writing", () => {
|
||||
const { container } = render(<WorkModalities />);
|
||||
|
||||
for (const card of container.querySelectorAll("article")) {
|
||||
@@ -262,13 +343,14 @@ describe("WorkModalities: as três ofertas", () => {
|
||||
expect(label.parentElement?.textContent).not.toMatch(/R\$|dias|prazo de/i);
|
||||
});
|
||||
|
||||
it("path: each button opens the contact form card with the project type already chosen, all three with the same filled style", () => {
|
||||
it("path: each button opens the contact form card with the project type already chosen, all four with the same filled style", () => {
|
||||
const { container } = render(<WorkModalities />);
|
||||
|
||||
const articles = Array.from(container.querySelectorAll("article"));
|
||||
const buttons = [
|
||||
["Começar pelo site", "/?tipo=site#contact-form"],
|
||||
["Começar pelo sistema", "/?tipo=sistema#contact-form"],
|
||||
["Começar pelo atendimento", "/?tipo=whatsapp#contact-form"],
|
||||
["Começar pela revisão", "/?tipo=revisao#contact-form"],
|
||||
] as const;
|
||||
buttons.forEach(([name, href], index) => {
|
||||
@@ -292,9 +374,19 @@ describe("WorkModalities: as três ofertas", () => {
|
||||
).toHaveAttribute("href", "/cases/sev-modaintima");
|
||||
expect(
|
||||
within(articles[2]).getByRole("link", {
|
||||
name: "Ver como funciona o atendimento",
|
||||
}),
|
||||
).toHaveAttribute("href", "/servicos/atendimento-whatsapp-ia");
|
||||
expect(
|
||||
within(articles[3]).getByRole("link", {
|
||||
name: "Ver como funciona a revisão",
|
||||
}),
|
||||
).toHaveAttribute("href", "/ia");
|
||||
expect(
|
||||
within(articles[2]).getByText(
|
||||
"Para quem atende e vende pelo WhatsApp e quer responder na hora sem deixar a IA inventar preço ou prazo.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("path: no duplicate service grid and no 'Reconhece algum destes cenários?' panel, only one link to /servicos after the cards", () => {
|
||||
@@ -306,19 +398,19 @@ describe("WorkModalities: as três ofertas", () => {
|
||||
name: "Ver todos os serviços",
|
||||
});
|
||||
expect(services).toHaveAttribute("href", "/servicos");
|
||||
expect(container.querySelectorAll("article")).toHaveLength(3);
|
||||
expect(container.querySelectorAll("article")).toHaveLength(4);
|
||||
// o link fica fora dos cartões, depois da grade
|
||||
expect(services.closest("article")).toBeNull();
|
||||
expect(
|
||||
screen
|
||||
.getAllByRole("link")
|
||||
.filter((link) => /^\/servicos/.test(link.getAttribute("href") ?? "")),
|
||||
.filter((link) => link.getAttribute("href") === "/servicos"),
|
||||
).toEqual([services]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("WorkModalities: um só estilo de cartão", () => {
|
||||
it("path: the three cards share the same classes and no card is highlighted (no featured border, no shadow variant)", () => {
|
||||
it("path: the four cards share the same classes and no card is highlighted (no featured border, no shadow variant)", () => {
|
||||
const { container } = render(<WorkModalities />);
|
||||
|
||||
const articles = Array.from(container.querySelectorAll("article"));
|
||||
@@ -393,3 +485,41 @@ describe("WorkModalities: sem prometer o que o dono não disse", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("WorkModalities: a faixa da nota fiscal", () => {
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it("path: up to 2027-03-31 a quiet line above the cards (no card, no colored box) says the Simples Nacional date and links to the page", () => {
|
||||
jest.useFakeTimers({ now: new Date("2027-03-31T23:59:00-03:00") });
|
||||
const { container } = render(<WorkModalities />);
|
||||
|
||||
const text = screen.getByText(
|
||||
/Os optantes do Simples Nacional entram na reforma tributária em 1º de janeiro de 2027\./,
|
||||
);
|
||||
expect(text.textContent).toBe(
|
||||
"Seu sistema emite nota fiscal? Os optantes do Simples Nacional entram na reforma tributária em 1º de janeiro de 2027.",
|
||||
);
|
||||
expect(
|
||||
screen.getByRole("link", { name: "Ver o que muda" }),
|
||||
).toHaveAttribute("href", "/servicos/nota-fiscal-reforma-tributaria");
|
||||
const notice = text.parentElement as HTMLElement;
|
||||
expect(notice.closest("article")).toBeNull();
|
||||
expect(
|
||||
notice.compareDocumentPosition(
|
||||
container.querySelector("article") as Element,
|
||||
) & Node.DOCUMENT_POSITION_FOLLOWING,
|
||||
).toBeTruthy();
|
||||
expect(notice.className).not.toMatch(/bg-|border-l-|ds-card|shadow/);
|
||||
});
|
||||
|
||||
it("path: from 2027-04-01 on, no notice, and the four cards stay", () => {
|
||||
jest.useFakeTimers({ now: new Date("2027-04-01T00:00:00-03:00") });
|
||||
const { container } = render(<WorkModalities />);
|
||||
|
||||
expect(screen.queryByText(/Seu sistema emite nota fiscal\?/)).toBeNull();
|
||||
expect(screen.queryByRole("link", { name: "Ver o que muda" })).toBeNull();
|
||||
expect(container.querySelectorAll("article")).toHaveLength(4);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user