diff --git a/ui/litellm-dashboard/src/components/survey/ClaudeCodeModal.test.tsx b/ui/litellm-dashboard/src/components/survey/ClaudeCodeModal.test.tsx
new file mode 100644
index 0000000000..a319fad46d
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/survey/ClaudeCodeModal.test.tsx
@@ -0,0 +1,66 @@
+import { screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { renderWithProviders } from "../../../tests/test-utils";
+import { ClaudeCodeModal } from "./ClaudeCodeModal";
+
+describe("ClaudeCodeModal", () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ it("should render nothing when isOpen is false", () => {
+ renderWithProviders(
+
+ );
+ expect(screen.queryByText(/Help us improve your experience/i)).not.toBeInTheDocument();
+ });
+
+ it("should render the feedback modal content when isOpen is true", () => {
+ renderWithProviders(
+
+ );
+ expect(screen.getByText(/Help us improve your experience/i)).toBeInTheDocument();
+ });
+
+ it("should show the survey description text", () => {
+ renderWithProviders(
+
+ );
+ expect(screen.getByText(/your experience using LiteLLM with Claude Code/i)).toBeInTheDocument();
+ });
+
+ it("should open the Google Form and call onComplete when the feedback button is clicked", async () => {
+ const onComplete = vi.fn();
+ const openSpy = vi.spyOn(window, "open").mockImplementation(() => null);
+ const user = userEvent.setup();
+
+ renderWithProviders(
+
+ );
+
+ await user.click(screen.getByRole("button", { name: /Open Feedback Form/i }));
+
+ expect(openSpy).toHaveBeenCalledWith(
+ "https://forms.gle/LZeJQ3XytBakckYa9",
+ "_blank",
+ "noopener,noreferrer"
+ );
+ expect(onComplete).toHaveBeenCalled();
+ });
+
+ it("should call onClose when the close button is clicked", async () => {
+ const onClose = vi.fn();
+ const user = userEvent.setup();
+
+ renderWithProviders(
+
+ );
+
+ // The X close button is the first button; the "Open Feedback Form" button is the second
+ const buttons = screen.getAllByRole("button");
+ await user.click(buttons[0]);
+
+ expect(onClose).toHaveBeenCalled();
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/survey/ClaudeCodePrompt.test.tsx b/ui/litellm-dashboard/src/components/survey/ClaudeCodePrompt.test.tsx
new file mode 100644
index 0000000000..71e1a8d07b
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/survey/ClaudeCodePrompt.test.tsx
@@ -0,0 +1,82 @@
+import { screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { describe, expect, it, vi } from "vitest";
+import { renderWithProviders } from "../../../tests/test-utils";
+import { ClaudeCodePrompt } from "./ClaudeCodePrompt";
+
+vi.mock("./NudgePrompt", () => ({
+ NudgePrompt: ({
+ title,
+ description,
+ buttonText,
+ onOpen,
+ onDismiss,
+ isVisible,
+ }: {
+ title: string;
+ description: string;
+ buttonText: string;
+ onOpen: () => void;
+ onDismiss: () => void;
+ isVisible: boolean;
+ }) => {
+ if (!isVisible) return null;
+ return (
+
+ {title}
+ {description}
+
+
+
+ );
+ },
+}));
+
+describe("ClaudeCodePrompt", () => {
+ it("should render with the Claude Code Feedback title when visible", () => {
+ renderWithProviders(
+
+ );
+ expect(screen.getByText("Claude Code Feedback")).toBeInTheDocument();
+ });
+
+ it("should render the correct description text", () => {
+ renderWithProviders(
+
+ );
+ expect(screen.getByText(/Help us improve your Claude Code experience/i)).toBeInTheDocument();
+ });
+
+ it("should call onOpen when the share feedback button is clicked", async () => {
+ const onOpen = vi.fn();
+ const user = userEvent.setup();
+
+ renderWithProviders(
+
+ );
+
+ await user.click(screen.getByRole("button", { name: /Share feedback/i }));
+
+ expect(onOpen).toHaveBeenCalled();
+ });
+
+ it("should call onDismiss when the dismiss button is clicked", async () => {
+ const onDismiss = vi.fn();
+ const user = userEvent.setup();
+
+ renderWithProviders(
+
+ );
+
+ await user.click(screen.getByRole("button", { name: /Dismiss/i }));
+
+ expect(onDismiss).toHaveBeenCalled();
+ });
+
+ it("should not render when isVisible is false", () => {
+ renderWithProviders(
+
+ );
+ expect(screen.queryByText("Claude Code Feedback")).not.toBeInTheDocument();
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/survey/SurveyModal.test.tsx b/ui/litellm-dashboard/src/components/survey/SurveyModal.test.tsx
new file mode 100644
index 0000000000..33af6d28c4
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/survey/SurveyModal.test.tsx
@@ -0,0 +1,200 @@
+import { screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { renderWithProviders } from "../../../tests/test-utils";
+import { SurveyModal } from "./SurveyModal";
+
+describe("SurveyModal", () => {
+ beforeEach(() => {
+ vi.spyOn(global, "fetch").mockResolvedValue(new Response());
+ });
+
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ it("should render nothing when isOpen is false", () => {
+ renderWithProviders(
+
+ );
+ expect(
+ screen.queryByText(/Are you using LiteLLM at your company\?/i)
+ ).not.toBeInTheDocument();
+ });
+
+ it("should render step 1 when the modal is opened", () => {
+ renderWithProviders(
+
+ );
+ expect(
+ screen.getByText(/Are you using LiteLLM at your company\?/i)
+ ).toBeInTheDocument();
+ });
+
+ it("should disable the Next button until a step 1 choice is made", () => {
+ renderWithProviders(
+
+ );
+ expect(screen.getByRole("button", { name: /Next/i })).toBeDisabled();
+ });
+
+ it("should enable the Next button after selecting Yes", async () => {
+ const user = userEvent.setup();
+ renderWithProviders(
+
+ );
+
+ await user.click(screen.getByRole("button", { name: /We use it for work/i }));
+
+ expect(screen.getByRole("button", { name: /Next/i })).not.toBeDisabled();
+ });
+
+ it("should navigate to the company name step when Yes is selected and Next is clicked", async () => {
+ const user = userEvent.setup();
+ renderWithProviders(
+
+ );
+
+ await user.click(screen.getByRole("button", { name: /We use it for work/i }));
+ await user.click(screen.getByRole("button", { name: /Next/i }));
+
+ expect(
+ screen.getByText(/What company are you using LiteLLM at\?/i)
+ ).toBeInTheDocument();
+ });
+
+ it("should skip the company name step when No is selected and go straight to step 3", async () => {
+ const user = userEvent.setup();
+ renderWithProviders(
+
+ );
+
+ await user.click(screen.getByRole("button", { name: /Personal project/i }));
+ await user.click(screen.getByRole("button", { name: /Next/i }));
+
+ expect(screen.getByText(/When did you start using LiteLLM\?/i)).toBeInTheDocument();
+ });
+
+ it("should show 5 total steps when using at a company", async () => {
+ const user = userEvent.setup();
+ renderWithProviders(
+
+ );
+
+ await user.click(screen.getByRole("button", { name: /We use it for work/i }));
+
+ expect(screen.getByText(/Step 1 of 5/i)).toBeInTheDocument();
+ });
+
+ it("should show 4 total steps when not using at a company", async () => {
+ const user = userEvent.setup();
+ renderWithProviders(
+
+ );
+
+ await user.click(screen.getByRole("button", { name: /Personal project/i }));
+
+ expect(screen.getByText(/Step 1 of 4/i)).toBeInTheDocument();
+ });
+
+ it("should navigate back to step 1 from step 3 when No was previously selected", async () => {
+ const user = userEvent.setup();
+ renderWithProviders(
+
+ );
+
+ await user.click(screen.getByRole("button", { name: /Personal project/i }));
+ await user.click(screen.getByRole("button", { name: /Next/i }));
+ await user.click(screen.getByRole("button", { name: /Back/i }));
+
+ expect(
+ screen.getByText(/Are you using LiteLLM at your company\?/i)
+ ).toBeInTheDocument();
+ });
+
+ describe("when step 4 (reasons) is reached", () => {
+ async function navigateToStep4(user: ReturnType) {
+ // No path: step 1 → 3 → 4
+ await user.click(screen.getByRole("button", { name: /Personal project/i }));
+ await user.click(screen.getByRole("button", { name: /Next/i }));
+ await user.click(screen.getByRole("radio", { name: /Less than a month ago/i }));
+ await user.click(screen.getByRole("button", { name: /Next/i }));
+ }
+
+ it("should show a text input when the Other reason is selected", async () => {
+ const user = userEvent.setup();
+ renderWithProviders(
+
+ );
+
+ await navigateToStep4(user);
+ await user.click(screen.getByRole("button", { name: /Something else not listed above/i }));
+
+ expect(screen.getByPlaceholderText(/Please specify/i)).toBeInTheDocument();
+ });
+
+ it("should keep the Next button disabled when Other is selected but the text field is empty", async () => {
+ const user = userEvent.setup();
+ renderWithProviders(
+
+ );
+
+ await navigateToStep4(user);
+ await user.click(screen.getByRole("button", { name: /Something else not listed above/i }));
+
+ expect(screen.getByRole("button", { name: /Next/i })).toBeDisabled();
+ });
+
+ it("should enable Next when a standard reason is selected", async () => {
+ const user = userEvent.setup();
+ renderWithProviders(
+
+ );
+
+ await navigateToStep4(user);
+ await user.click(
+ screen.getByRole("button", { name: /Stars, contributors, forks, community support/i })
+ );
+
+ expect(screen.getByRole("button", { name: /Next/i })).not.toBeDisabled();
+ });
+ });
+
+ it("should call onComplete after successfully submitting the form", async () => {
+ const onComplete = vi.fn();
+ const user = userEvent.setup();
+ renderWithProviders(
+
+ );
+
+ // Navigate through the No path: step 1 → 3 → 4 → 5 → submit
+ await user.click(screen.getByRole("button", { name: /Personal project/i }));
+ await user.click(screen.getByRole("button", { name: /Next/i }));
+ await user.click(screen.getByRole("radio", { name: /Less than a month ago/i }));
+ await user.click(screen.getByRole("button", { name: /Next/i }));
+ await user.click(
+ screen.getByRole("button", { name: /Stars, contributors, forks, community support/i })
+ );
+ await user.click(screen.getByRole("button", { name: /Next/i }));
+ // Step 5: email is optional
+ await user.click(screen.getByRole("button", { name: /Submit/i }));
+
+ await waitFor(() => {
+ expect(onComplete).toHaveBeenCalled();
+ });
+ });
+
+ it("should call onClose when the close button is clicked", async () => {
+ const onClose = vi.fn();
+ const user = userEvent.setup();
+ renderWithProviders(
+
+ );
+
+ // X close button is the first button in the modal header
+ const buttons = screen.getAllByRole("button");
+ await user.click(buttons[0]);
+
+ expect(onClose).toHaveBeenCalled();
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/survey/SurveyPrompt.test.tsx b/ui/litellm-dashboard/src/components/survey/SurveyPrompt.test.tsx
new file mode 100644
index 0000000000..ae5bc1a9c6
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/survey/SurveyPrompt.test.tsx
@@ -0,0 +1,82 @@
+import { screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { describe, expect, it, vi } from "vitest";
+import { renderWithProviders } from "../../../tests/test-utils";
+import { SurveyPrompt } from "./SurveyPrompt";
+
+vi.mock("./NudgePrompt", () => ({
+ NudgePrompt: ({
+ title,
+ description,
+ buttonText,
+ onOpen,
+ onDismiss,
+ isVisible,
+ }: {
+ title: string;
+ description: string;
+ buttonText: string;
+ onOpen: () => void;
+ onDismiss: () => void;
+ isVisible: boolean;
+ }) => {
+ if (!isVisible) return null;
+ return (
+
+ {title}
+ {description}
+
+
+
+ );
+ },
+}));
+
+describe("SurveyPrompt", () => {
+ it("should render with the Quick feedback title when visible", () => {
+ renderWithProviders(
+
+ );
+ expect(screen.getByText("Quick feedback")).toBeInTheDocument();
+ });
+
+ it("should render the correct description text", () => {
+ renderWithProviders(
+
+ );
+ expect(screen.getByText(/Help us improve LiteLLM/i)).toBeInTheDocument();
+ });
+
+ it("should call onOpen when the share feedback button is clicked", async () => {
+ const onOpen = vi.fn();
+ const user = userEvent.setup();
+
+ renderWithProviders(
+
+ );
+
+ await user.click(screen.getByRole("button", { name: /Share feedback/i }));
+
+ expect(onOpen).toHaveBeenCalled();
+ });
+
+ it("should call onDismiss when the dismiss button is clicked", async () => {
+ const onDismiss = vi.fn();
+ const user = userEvent.setup();
+
+ renderWithProviders(
+
+ );
+
+ await user.click(screen.getByRole("button", { name: /Dismiss/i }));
+
+ expect(onDismiss).toHaveBeenCalled();
+ });
+
+ it("should not render when isVisible is false", () => {
+ renderWithProviders(
+
+ );
+ expect(screen.queryByText("Quick feedback")).not.toBeInTheDocument();
+ });
+});