Spaces:
Sleeping
Sleeping
| import { fireEvent, render, screen, waitFor } from "@testing-library/react"; | |
| import { describe, expect, it, vi } from "vitest"; | |
| import LeadForm from "./LeadForm"; | |
| import { scenarios } from "./demo/scenarios"; | |
| function fillRequiredFields() { | |
| fireEvent.change(screen.getByRole("textbox", { name: "Họ và tên" }), { | |
| target: { value: "Nguyễn Minh Anh" }, | |
| }); | |
| fireEvent.change(screen.getByRole("textbox", { name: "Vai trò" }), { | |
| target: { value: "Giám đốc phòng khám" }, | |
| }); | |
| fireEvent.change(screen.getByRole("textbox", { name: "Email hoặc Zalo" }), { | |
| target: { value: "minhanh@example.com" }, | |
| }); | |
| } | |
| describe("LeadForm", () => { | |
| it("posts the configured payload to the lead endpoint", async () => { | |
| const fetcher = vi.fn(async () => new Response(null, { status: 204 })) as unknown as typeof fetch; | |
| render( | |
| <LeadForm | |
| clinic="Phòng khám Minh Tâm" | |
| endpoint="https://leads.example.test/pilot" | |
| fetcher={fetcher} | |
| language="vi" | |
| scenario={scenarios[0]} | |
| specialty="Nội tổng quát" | |
| transcript="VI: Uống nửa viên.\nEN: Take half a tablet." | |
| />, | |
| ); | |
| fillRequiredFields(); | |
| fireEvent.click( | |
| screen.getByRole("button", { name: "Gửi yêu cầu thí điểm" }), | |
| ); | |
| await waitFor(() => | |
| expect(screen.getByText("Đã gửi yêu cầu thí điểm.")).toBeInTheDocument(), | |
| ); | |
| expect(fetcher).toHaveBeenCalledOnce(); | |
| const [, request] = (fetcher as ReturnType<typeof vi.fn>).mock.calls[0]; | |
| const payload = JSON.parse(String(request.body)); | |
| expect(payload.interest).toBe("both"); | |
| expect(payload.scenarioId).toBe(""); | |
| expect(payload.transcript).toBe(""); | |
| }); | |
| it("opens a prefilled mail draft when no endpoint is configured", async () => { | |
| const onMailto = vi.fn(); | |
| render( | |
| <LeadForm | |
| clinic="Phòng khám Minh Tâm" | |
| endpoint="" | |
| leadEmail="pilot@example.com" | |
| language="vi" | |
| onMailto={onMailto} | |
| scenario={scenarios[1]} | |
| specialty="Dị ứng" | |
| transcript="VI: Không dị ứng.\nEN: Not allergic." | |
| />, | |
| ); | |
| fillRequiredFields(); | |
| fireEvent.click( | |
| screen.getByRole("button", { name: "Mở email đã điền sẵn" }), | |
| ); | |
| await waitFor(() => expect(onMailto).toHaveBeenCalledOnce()); | |
| const mailto = decodeURIComponent(onMailto.mock.calls[0][0]); | |
| expect(mailto).toContain("mailto:pilot@example.com"); | |
| expect(mailto).not.toContain("Kiểm tra dị ứng — xác nhận phủ định"); | |
| expect(mailto).not.toContain("Not allergic"); | |
| expect( | |
| screen.getByText( | |
| "Đã mở bản nháp email. Hãy kiểm tra và gửi thư để hoàn tất.", | |
| ), | |
| ).toBeInTheDocument(); | |
| }); | |
| it("reports an error instead of a dead mail draft when no channel is configured", async () => { | |
| const onMailto = vi.fn(); | |
| render( | |
| <LeadForm | |
| clinic="Phòng khám Minh Tâm" | |
| endpoint="" | |
| leadEmail="" | |
| language="vi" | |
| onMailto={onMailto} | |
| scenario={scenarios[0]} | |
| specialty="Nội tổng quát" | |
| transcript="" | |
| />, | |
| ); | |
| fillRequiredFields(); | |
| fireEvent.click( | |
| screen.getByRole("button", { name: "Mở email đã điền sẵn" }), | |
| ); | |
| await waitFor(() => | |
| expect( | |
| screen.getByText( | |
| "Không thể gửi yêu cầu. Vui lòng kiểm tra kết nối và thử lại.", | |
| ), | |
| ).toBeInTheDocument(), | |
| ); | |
| expect(onMailto).not.toHaveBeenCalled(); | |
| }); | |
| it("shows inline errors without attempting submission", () => { | |
| const fetcher = vi.fn(); | |
| render( | |
| <LeadForm | |
| clinic="Phòng khám Minh Tâm" | |
| endpoint="https://leads.example.test/pilot" | |
| fetcher={fetcher as unknown as typeof fetch} | |
| language="vi" | |
| scenario={scenarios[0]} | |
| specialty="Nội tổng quát" | |
| transcript="" | |
| />, | |
| ); | |
| fireEvent.click( | |
| screen.getByRole("button", { name: "Gửi yêu cầu thí điểm" }), | |
| ); | |
| expect(screen.getAllByText("Vui lòng điền trường này.")).toHaveLength(3); | |
| expect(fetcher).not.toHaveBeenCalled(); | |
| }); | |
| it("submits a selectable Scribe interest without interpreter context", async () => { | |
| const fetcher = vi.fn(async () => new Response(null, { status: 204 })) as unknown as typeof fetch; | |
| render( | |
| <LeadForm | |
| clinic="Phòng khám Minh Tâm" | |
| endpoint="https://leads.example.test/pilot" | |
| fetcher={fetcher} | |
| language="vi" | |
| scenario={scenarios[0]} | |
| specialty="Nội tổng quát" | |
| transcript="VI: Uống nửa viên.\nEN: Take half a tablet." | |
| />, | |
| ); | |
| fireEvent.change(screen.getByRole("combobox", { name: "Chức năng quan tâm" }), { | |
| target: { value: "scribe" }, | |
| }); | |
| fillRequiredFields(); | |
| fireEvent.click( | |
| screen.getByRole("button", { name: "Gửi yêu cầu thí điểm" }), | |
| ); | |
| await waitFor(() => expect(fetcher).toHaveBeenCalledOnce()); | |
| const [, request] = (fetcher as ReturnType<typeof vi.fn>).mock.calls[0]; | |
| expect(JSON.parse(String(request.body))).toMatchObject({ | |
| interest: "scribe", | |
| scenarioId: "", | |
| scenarioTitle: "", | |
| transcript: "", | |
| }); | |
| }); | |
| it("reports controlled product-interest changes", () => { | |
| const onInterestChange = vi.fn(); | |
| render( | |
| <LeadForm | |
| clinic="Phòng khám Minh Tâm" | |
| interest="interpreter" | |
| language="vi" | |
| onInterestChange={onInterestChange} | |
| scenario={scenarios[0]} | |
| specialty="Nội tổng quát" | |
| transcript="" | |
| />, | |
| ); | |
| const selector = screen.getByRole("combobox", { name: "Chức năng quan tâm" }); | |
| expect(selector).toHaveValue("interpreter"); | |
| fireEvent.change(selector, { target: { value: "both" } }); | |
| expect(onInterestChange).toHaveBeenCalledWith("both"); | |
| }); | |
| it("submits a fixed Scribe interest when the landing selector is hidden", async () => { | |
| const fetcher = vi.fn(async () => new Response(null, { status: 204 })) as unknown as typeof fetch; | |
| render( | |
| <LeadForm | |
| clinic="Phòng khám Minh Tâm" | |
| endpoint="https://leads.example.test/pilot" | |
| fetcher={fetcher} | |
| hideInterestField | |
| interest="scribe" | |
| language="vi" | |
| scenario={scenarios[0]} | |
| specialty="Nội tổng quát" | |
| transcript="" | |
| />, | |
| ); | |
| expect(screen.queryByRole("combobox", { name: "Chức năng quan tâm" })).not.toBeInTheDocument(); | |
| fillRequiredFields(); | |
| fireEvent.click(screen.getByRole("button", { name: "Gửi yêu cầu thí điểm" })); | |
| await waitFor(() => expect(fetcher).toHaveBeenCalledOnce()); | |
| const [, request] = (fetcher as ReturnType<typeof vi.fn>).mock.calls[0]; | |
| expect(JSON.parse(String(request.body)).interest).toBe("scribe"); | |
| }); | |
| it("includes the simulation context only after explicit opt-in", async () => { | |
| const fetcher = vi.fn(async () => new Response(null, { status: 204 })) as unknown as typeof fetch; | |
| render( | |
| <LeadForm | |
| clinic="Phòng khám Minh Tâm" | |
| endpoint="https://leads.example.test/pilot" | |
| fetcher={fetcher} | |
| language="vi" | |
| scenario={scenarios[0]} | |
| specialty="Nội tổng quát" | |
| transcript="VI: Uống nửa viên.\nEN: Take half a tablet." | |
| />, | |
| ); | |
| fireEvent.click(screen.getByLabelText("Gửi kèm kịch bản mô phỏng và bản chép song ngữ")); | |
| fillRequiredFields(); | |
| fireEvent.click(screen.getByRole("button", { name: "Gửi yêu cầu thí điểm" })); | |
| await waitFor(() => expect(fetcher).toHaveBeenCalledOnce()); | |
| const [, request] = (fetcher as ReturnType<typeof vi.fn>).mock.calls[0]; | |
| expect(JSON.parse(String(request.body))).toMatchObject({ | |
| scenarioId: "prescription", | |
| transcript: expect.stringContaining("Take half a tablet"), | |
| }); | |
| }); | |
| }); | |