From 5ba8e37c1a9901dd1b262532f7bbf004fb0db90c Mon Sep 17 00:00:00 2001 From: Rudy Celekli Date: Mon, 12 Oct 2026 21:16:26 -0400 Subject: [PATCH] fix: gate meeting choices on available response channel --- .../meeting-time-picker.test.tsx | 31 +++++++++++++++++++ .../generative-ui/meeting-time-picker.tsx | 4 +++ 2 files changed, 35 insertions(+) create mode 100644 apps/app/src/components/generative-ui/meeting-time-picker.test.tsx diff --git a/apps/app/src/components/generative-ui/meeting-time-picker.test.tsx b/apps/app/src/components/generative-ui/meeting-time-picker.test.tsx new file mode 100644 index 0000000..acfa92a --- /dev/null +++ b/apps/app/src/components/generative-ui/meeting-time-picker.test.tsx @@ -0,0 +1,31 @@ +import { afterEach, expect, it, vi } from "vitest"; +import { cleanup, fireEvent, render, screen } from "@testing-library/react"; +import "@testing-library/jest-dom/vitest"; +import { MeetingTimePicker } from "./meeting-time-picker"; +afterEach(cleanup); +it("does not confirm or decline a meeting without a response channel", () => { + render(); + const options = screen.getAllByRole("button"); + for (const option of options) expect(option).toBeDisabled(); + fireEvent.click(options[0]); + expect(screen.queryByText("Meeting Scheduled")).not.toBeInTheDocument(); + fireEvent.click(screen.getByText("None of these work")); + expect(screen.queryByText("No Time Selected")).not.toBeInTheDocument(); +}); +it("enables selection when a response channel becomes available", () => { + const respond = vi.fn(); + const view = render(); + view.rerender(); + const option = screen.getByRole("button", { name: /Tomorrow/ }); + expect(option).toBeEnabled(); + fireEvent.click(option); + expect(respond).toHaveBeenCalledExactlyOnceWith("Meeting scheduled for Tomorrow at 2:00 PM (45 min)."); + expect(screen.getByText("Meeting Scheduled")).toBeInTheDocument(); +}); +it("submits an available decline response once", () => { + const respond = vi.fn(); + render(); + fireEvent.click(screen.getByText("None of these work")); + expect(respond).toHaveBeenCalledTimes(1); + expect(screen.getByText("No Time Selected")).toBeInTheDocument(); +}); diff --git a/apps/app/src/components/generative-ui/meeting-time-picker.tsx b/apps/app/src/components/generative-ui/meeting-time-picker.tsx index 0d01c82..ce24908 100644 --- a/apps/app/src/components/generative-ui/meeting-time-picker.tsx +++ b/apps/app/src/components/generative-ui/meeting-time-picker.tsx @@ -35,11 +35,13 @@ export function MeetingTimePicker({ const [declined, setDeclined] = useState(false); const handleSelectSlot = (slot: TimeSlot) => { + if (!respond) return; setSelectedSlot(slot); respond?.(`Meeting scheduled for ${slot.date} at ${slot.time}${slot.duration ? ` (${slot.duration})` : ''}.`); }; const handleDecline = () => { + if (!respond) return; setDeclined(true); respond?.("The user declined all proposed meeting times. Please suggest alternative times or ask for their availability."); }; @@ -91,6 +93,7 @@ export function MeetingTimePicker({ {slots.map((slot, index) => (