import { act, render, screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import Contact from "./Contact"; import { CONTACT_KIND_EVENT } from "./contact-subject"; /** * O tipo de projeto do formulário vindo da oferta escolhida na home: pelo evento que o botão da oferta dispara * (na própria home) ou pelo `?tipo=` do endereço (ao chegar de outra página). As três ofertas entram no formulário * com o nome que têm na home, o formulário avisa "Você escolheu: " e o link direto do WhatsApp leva o tipo. */ const subjectSelect = () => screen.getByRole("combobox", { name: /tipo de projeto/i, }) as HTMLSelectElement; const choose = (kind: unknown) => act(() => { window.dispatchEvent(new CustomEvent(CONTACT_KIND_EVENT, { detail: kind })); }); afterEach(() => { window.history.replaceState(null, "", "/"); }); describe("Contact: opções do tipo de projeto", () => { it("lists the three offers by their names and keeps 'Ainda preciso definir' first and selected", () => { render(); const options = within(subjectSelect()).getAllByRole("option"); expect(options.map((option) => option.textContent)).toEqual([ "Ainda preciso definir", "Site que traz pedidos pelo WhatsApp", "Sistema para tirar a operação da planilha", "Aplicativos", "Integrações e automações", "Evolução de um sistema existente", "Revisão de sistema que já existe", ]); expect(subjectSelect()).toHaveValue("Ainda preciso definir"); }); it("puts the review type in the message that goes to WhatsApp and e-mail", async () => { const user = userEvent.setup({ delay: null }); render(); await user.selectOptions( subjectSelect(), "Revisão de sistema que já existe", ); const mail = screen.getByRole("link", { name: /e-mail/i }); const body = new URL(mail.getAttribute("href") ?? "").searchParams.get( "body", ); expect(body).toContain("Tipo de projeto: Revisão de sistema que já existe"); }); }); describe("Contact: tipo escolhido pela oferta", () => { it.each([ ["site", "Site que traz pedidos pelo WhatsApp"], ["sistema", "Sistema para tirar a operação da planilha"], ["revisao", "Revisão de sistema que já existe"], ])("the offer event with %s selects %s", (kind, option) => { render(); choose(kind); expect(subjectSelect()).toHaveValue(option); }); it("a later offer replaces the earlier choice, and a person's own pick is replaced by the offer too", async () => { const user = userEvent.setup({ delay: null }); render(); choose("site"); await user.selectOptions(subjectSelect(), "Aplicativos"); expect(subjectSelect()).toHaveValue("Aplicativos"); choose("revisao"); expect(subjectSelect()).toHaveValue("Revisão de sistema que já existe"); }); it.each([ "desconhecido", "constructor", "__proto__", "", undefined, null, 42, ])("an unknown type %p changes nothing", (kind) => { render(); choose("sistema"); choose(kind); expect(subjectSelect()).toHaveValue( "Sistema para tirar a operação da planilha", ); }); it("reads ?tipo= from the address when the form mounts (arrival from another page)", () => { window.history.replaceState(null, "", "/?tipo=revisao#contact-form"); render(); expect(subjectSelect()).toHaveValue("Revisão de sistema que já existe"); expect(screen.getByRole("status")).toHaveTextContent( "Você escolheu: Revisão de sistema que já existe", ); }); it("ignores an unknown ?tipo= in the address and keeps the default", () => { window.history.replaceState(null, "", "/?tipo=outra-coisa"); render(); expect(subjectSelect()).toHaveValue("Ainda preciso definir"); }); it("stops listening when it unmounts", () => { const add = jest.spyOn(window, "addEventListener"); const remove = jest.spyOn(window, "removeEventListener"); const { unmount } = render(); const registered = add.mock.calls.find( ([type]) => type === CONTACT_KIND_EVENT, ); expect(registered).toBeDefined(); unmount(); expect(remove).toHaveBeenCalledWith(CONTACT_KIND_EVENT, registered?.[1]); add.mockRestore(); remove.mockRestore(); }); }); describe("Contact: você escolheu e o WhatsApp direto", () => { const directLink = () => screen.getByRole("link", { name: /Prefere conversar direto/ }); const directMessage = () => new URL(directLink().getAttribute("href") ?? "").searchParams.get("text"); it("renders the form card with the id the offers scroll to, and an empty status until an offer is chosen", () => { const { container } = render(); expect(container.querySelector("form")).toHaveAttribute( "id", "contact-form", ); expect(screen.getByRole("status")).toBeEmptyDOMElement(); }); it.each([ ["site", "Site que traz pedidos pelo WhatsApp"], ["sistema", "Sistema para tirar a operação da planilha"], ["revisao", "Revisão de sistema que já existe"], ])( "after the offer %s the form says 'Você escolheu: '", (kind, name) => { render(); choose(kind); expect(screen.getByRole("status")).toHaveTextContent( `Você escolheu: ${name}`, ); expect(within(screen.getByRole("status")).getByText(name).tagName).toBe( "STRONG", ); }, ); it("drops the message when the person picks another type in the form, and keeps it for an unknown offer", async () => { const user = userEvent.setup({ delay: null }); render(); choose("desconhecido"); expect(screen.getByRole("status")).toBeEmptyDOMElement(); choose("sistema"); expect(screen.getByRole("status")).not.toBeEmptyDOMElement(); await user.selectOptions(subjectSelect(), "Aplicativos"); expect(screen.getByRole("status")).toBeEmptyDOMElement(); }); it("takes the chosen type to the direct WhatsApp link of the contact column, as a phrase, and back to 'um projeto'", async () => { const user = userEvent.setup({ delay: null }); render(); expect(directMessage()).toBe( "Olá, Evandro! Quero conversar sobre um projeto. Contato iniciado pelo site evandro.dev.br.", ); choose("sistema"); expect(directMessage()).toBe( "Olá, Evandro! Quero conversar sobre um sistema para tirar a operação da planilha. Contato iniciado pelo site evandro.dev.br.", ); choose("revisao"); expect(directMessage()).toContain( "Quero conversar sobre a revisão de um sistema que já existe.", ); // quem escolhe o mesmo tipo pelo formulário também o leva para a mensagem await user.selectOptions( subjectSelect(), "Site que traz pedidos pelo WhatsApp", ); expect(directMessage()).toContain( "Quero conversar sobre um site que traz pedidos pelo WhatsApp.", ); // um tipo sem oferta volta para a frase de "um projeto" await user.selectOptions(subjectSelect(), "Aplicativos"); expect(directMessage()).toContain("Quero conversar sobre um projeto."); // nada de nome de oferta com maiúscula na frase expect(directMessage()).not.toMatch(/sobre [A-Z]/); }); }); describe("Contact: texto da seção", () => { it("keeps 'conversar direto' only on the WhatsApp line, with no other 'fala direto' phrasing", () => { const { container } = render(); expect(screen.getByText("Prefere conversar direto?")).toBeInTheDocument(); expect(container.textContent).not.toMatch(/fala direto|fale direto/i); expect( screen.getByRole("heading", { level: 3, name: "Como a conversa começa.", }), ).toBeInTheDocument(); }); });