-
Notifications
You must be signed in to change notification settings - Fork 12k
fix: Plain chat not opening from help button for free users and on bookings list page #23491
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -17,11 +17,15 @@ vi.mock("@calcom/ui/components/toast", () => ({ | |||||||||||||||||||||||||||||||||||||||||||||||||
| const mockUseSession = vi.mocked(useSession); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const mockShowToast = vi.mocked(showToast); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const mockFetch = vi.fn(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| global.fetch = mockFetch; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| global.fetch = mockFetch as unknown as typeof fetch; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| describe("PlainContactForm", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let mockSetIsOpen: ReturnType<typeof vi.fn>; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| beforeEach(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| vi.clearAllMocks(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| mockSetIsOpen = vi.fn(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
19
to
+28
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainReset the fetch mock between tests to avoid leaked implementations The “loading” test installs a never-resolving impl; since only Apply in beforeEach: beforeEach(() => {
vi.clearAllMocks();
+ mockFetch.mockReset();
mockSetIsOpen = vi.fn();🏁 Script executed: #!/bin/bash
sed -n '1,200p' apps/web/components/plain/__tests__/PlainContactForm.test.tsxLength of output: 5663 Reset fetch mock implementation between tests
Apply in beforeEach: beforeEach(() => {
vi.clearAllMocks();
+ mockFetch.mockReset();
mockSetIsOpen = vi.fn();📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
| mockUseSession.mockReturnValue({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| data: { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| hasValidLicense: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -35,62 +39,59 @@ describe("PlainContactForm", () => { | |||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| status: "authenticated", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| update: vi.fn(), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } as any); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| it("should render contact button when closed", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| it("renders the contact button when closed", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm open={false} setIsOpen={mockSetIsOpen} />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const button = screen.getByRole("button"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(button).toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| it("should open contact form when button is clicked", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| it("opens when the launcher button is clicked (calls setIsOpen(true))", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm open={false} setIsOpen={mockSetIsOpen} />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const button = screen.getByRole("button"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fireEvent.click(button); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(mockSetIsOpen).toHaveBeenCalledWith(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| it("shows the form when open=true", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm open={true} setIsOpen={mockSetIsOpen} />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(screen.getByText("Contact support")).toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(screen.getByLabelText("Describe the issue")).toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(screen.getByText("Attachments (optional)")).toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| it("should show empty form initially", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const button = screen.getByRole("button"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fireEvent.click(button); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| it("shows empty form initially", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm open={true} setIsOpen={mockSetIsOpen} />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const messageInput = screen.getByLabelText("Describe the issue") as HTMLTextAreaElement; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(messageInput.value).toBe(""); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| it("should close form when X button is clicked", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const openButton = screen.getByRole("button"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fireEvent.click(openButton); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| it("closes when X button is clicked (calls setIsOpen(false))", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm open={true} setIsOpen={mockSetIsOpen} />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const buttons = screen.getAllByRole("button"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const closeButton = buttons.find((button) => button.querySelector('svg use[href="#x"]')); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(closeButton).toBeDefined(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| fireEvent.click(closeButton!); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(screen.queryByText("Contact support")).not.toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(mockSetIsOpen).toHaveBeenCalledWith(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| it("should handle form submission successfully", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| it("handles form submission successfully", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| mockFetch.mockResolvedValueOnce({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ok: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| json: () => Promise.resolve({ success: true }), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const openButton = screen.getByRole("button"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fireEvent.click(openButton); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm open={true} setIsOpen={mockSetIsOpen} />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| fireEvent.change(screen.getByLabelText("Describe the issue"), { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| target: { value: "Test message" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -115,14 +116,11 @@ describe("PlainContactForm", () => { | |||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| it("should show loading state during submission", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // eslint-disable-next-line @typescript-eslint/no-empty-function | ||||||||||||||||||||||||||||||||||||||||||||||||||
| it("shows loading state during submission", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // never resolves → stays loading | ||||||||||||||||||||||||||||||||||||||||||||||||||
| mockFetch.mockImplementation(() => new Promise((_resolve) => {})); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const openButton = screen.getByRole("button"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fireEvent.click(openButton); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm open={true} setIsOpen={mockSetIsOpen} />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| fireEvent.change(screen.getByLabelText("Describe the issue"), { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| target: { value: "Test message" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -135,23 +133,19 @@ describe("PlainContactForm", () => { | |||||||||||||||||||||||||||||||||||||||||||||||||
| await expect(submitButton).toBeDisabled(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| it("should reset form after successful submission", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| it("resets form after successful submission", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| mockFetch.mockResolvedValueOnce({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ok: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| json: () => Promise.resolve({ success: true }), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const openButton = screen.getByRole("button"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fireEvent.click(openButton); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm open={true} setIsOpen={mockSetIsOpen} />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| fireEvent.change(screen.getByLabelText("Describe the issue"), { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| target: { value: "Test message" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const submitButton = screen.getByRole("button", { name: /send message/i }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fireEvent.click(submitButton); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fireEvent.click(screen.getByRole("button", { name: /send message/i })); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| await waitFor(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(screen.getByText("Message Sent")).toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -166,27 +160,21 @@ describe("PlainContactForm", () => { | |||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| it("should handle missing user session", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| it("handles missing user session", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| mockUseSession.mockReturnValue({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| data: null, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| status: "unauthenticated", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| update: vi.fn(), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } as any); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const openButton = screen.getByRole("button"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fireEvent.click(openButton); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm open={true} setIsOpen={mockSetIsOpen} />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const messageInput = screen.getByLabelText("Describe the issue") as HTMLTextAreaElement; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(messageInput.value).toBe(""); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| it("should require message field", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const openButton = screen.getByRole("button"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fireEvent.click(openButton); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| it("requires message field", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<PlainContactForm open={true} setIsOpen={mockSetIsOpen} />); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| await expect(screen.getByLabelText("Describe the issue")).toHaveAttribute("required"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,11 +56,19 @@ export function UserDropdown({ small }: UserDropdownProps) { | |
|
|
||
| const [menuOpen, setMenuOpen] = useState(false); | ||
|
|
||
| const handleHelpClick = () => { | ||
| if (window.Plain) { | ||
| window.Plain.open(); | ||
| } | ||
| const handleHelpClick = (e?: React.MouseEvent) => { | ||
| e?.preventDefault(); | ||
| e?.stopPropagation(); | ||
|
|
||
| setMenuOpen(false); | ||
|
|
||
| // Defer to next frame to avoid the originating click closing the dialog | ||
| requestAnimationFrame(() => { | ||
| // double RAF is extra-safe if your dropdown unmounts with a transition | ||
| requestAnimationFrame(() => { | ||
| if (window.Plain) window.Plain.open(); | ||
| }); | ||
|
Comment on lines
+60
to
+70
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoids closing the PlainContactForm component |
||
| }); | ||
| }; | ||
|
|
||
| // Prevent rendering dropdown if user isn't available. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.