mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-05 04:22:19 +00:00
Merge pull request #23171 from BerriAI/litellm_survey_vitest_tests
[Test] UI - Survey: add Vitest unit tests for untested components
This commit is contained in:
@@ -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(
|
||||
<ClaudeCodeModal isOpen={false} onClose={vi.fn()} onComplete={vi.fn()} />
|
||||
);
|
||||
expect(screen.queryByText(/Help us improve your experience/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render the feedback modal content when isOpen is true", () => {
|
||||
renderWithProviders(
|
||||
<ClaudeCodeModal isOpen={true} onClose={vi.fn()} onComplete={vi.fn()} />
|
||||
);
|
||||
expect(screen.getByText(/Help us improve your experience/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show the survey description text", () => {
|
||||
renderWithProviders(
|
||||
<ClaudeCodeModal isOpen={true} onClose={vi.fn()} onComplete={vi.fn()} />
|
||||
);
|
||||
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(
|
||||
<ClaudeCodeModal isOpen={true} onClose={vi.fn()} onComplete={onComplete} />
|
||||
);
|
||||
|
||||
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(
|
||||
<ClaudeCodeModal isOpen={true} onClose={onClose} onComplete={vi.fn()} />
|
||||
);
|
||||
|
||||
// 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();
|
||||
});
|
||||
});
|
||||
@@ -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 (
|
||||
<div>
|
||||
<span>{title}</span>
|
||||
<span>{description}</span>
|
||||
<button onClick={onOpen}>{buttonText}</button>
|
||||
<button onClick={onDismiss}>Dismiss</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
describe("ClaudeCodePrompt", () => {
|
||||
it("should render with the Claude Code Feedback title when visible", () => {
|
||||
renderWithProviders(
|
||||
<ClaudeCodePrompt isVisible={true} onOpen={vi.fn()} onDismiss={vi.fn()} />
|
||||
);
|
||||
expect(screen.getByText("Claude Code Feedback")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render the correct description text", () => {
|
||||
renderWithProviders(
|
||||
<ClaudeCodePrompt isVisible={true} onOpen={vi.fn()} onDismiss={vi.fn()} />
|
||||
);
|
||||
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(
|
||||
<ClaudeCodePrompt isVisible={true} onOpen={onOpen} onDismiss={vi.fn()} />
|
||||
);
|
||||
|
||||
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(
|
||||
<ClaudeCodePrompt isVisible={true} onOpen={vi.fn()} onDismiss={onDismiss} />
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /Dismiss/i }));
|
||||
|
||||
expect(onDismiss).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not render when isVisible is false", () => {
|
||||
renderWithProviders(
|
||||
<ClaudeCodePrompt isVisible={false} onOpen={vi.fn()} onDismiss={vi.fn()} />
|
||||
);
|
||||
expect(screen.queryByText("Claude Code Feedback")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -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(
|
||||
<SurveyModal isOpen={false} onClose={vi.fn()} onComplete={vi.fn()} />
|
||||
);
|
||||
expect(
|
||||
screen.queryByText(/Are you using LiteLLM at your company\?/i)
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render step 1 when the modal is opened", () => {
|
||||
renderWithProviders(
|
||||
<SurveyModal isOpen={true} onClose={vi.fn()} onComplete={vi.fn()} />
|
||||
);
|
||||
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(
|
||||
<SurveyModal isOpen={true} onClose={vi.fn()} onComplete={vi.fn()} />
|
||||
);
|
||||
expect(screen.getByRole("button", { name: /Next/i })).toBeDisabled();
|
||||
});
|
||||
|
||||
it("should enable the Next button after selecting Yes", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(
|
||||
<SurveyModal isOpen={true} onClose={vi.fn()} onComplete={vi.fn()} />
|
||||
);
|
||||
|
||||
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(
|
||||
<SurveyModal isOpen={true} onClose={vi.fn()} onComplete={vi.fn()} />
|
||||
);
|
||||
|
||||
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(
|
||||
<SurveyModal isOpen={true} onClose={vi.fn()} onComplete={vi.fn()} />
|
||||
);
|
||||
|
||||
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(
|
||||
<SurveyModal isOpen={true} onClose={vi.fn()} onComplete={vi.fn()} />
|
||||
);
|
||||
|
||||
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(
|
||||
<SurveyModal isOpen={true} onClose={vi.fn()} onComplete={vi.fn()} />
|
||||
);
|
||||
|
||||
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(
|
||||
<SurveyModal isOpen={true} onClose={vi.fn()} onComplete={vi.fn()} />
|
||||
);
|
||||
|
||||
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<typeof userEvent.setup>) {
|
||||
// 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(
|
||||
<SurveyModal isOpen={true} onClose={vi.fn()} onComplete={vi.fn()} />
|
||||
);
|
||||
|
||||
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(
|
||||
<SurveyModal isOpen={true} onClose={vi.fn()} onComplete={vi.fn()} />
|
||||
);
|
||||
|
||||
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(
|
||||
<SurveyModal isOpen={true} onClose={vi.fn()} onComplete={vi.fn()} />
|
||||
);
|
||||
|
||||
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(
|
||||
<SurveyModal isOpen={true} onClose={vi.fn()} onComplete={onComplete} />
|
||||
);
|
||||
|
||||
// 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(
|
||||
<SurveyModal isOpen={true} onClose={onClose} onComplete={vi.fn()} />
|
||||
);
|
||||
|
||||
// X close button is the first button in the modal header
|
||||
const buttons = screen.getAllByRole("button");
|
||||
await user.click(buttons[0]);
|
||||
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -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 (
|
||||
<div>
|
||||
<span>{title}</span>
|
||||
<span>{description}</span>
|
||||
<button onClick={onOpen}>{buttonText}</button>
|
||||
<button onClick={onDismiss}>Dismiss</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
describe("SurveyPrompt", () => {
|
||||
it("should render with the Quick feedback title when visible", () => {
|
||||
renderWithProviders(
|
||||
<SurveyPrompt isVisible={true} onOpen={vi.fn()} onDismiss={vi.fn()} />
|
||||
);
|
||||
expect(screen.getByText("Quick feedback")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render the correct description text", () => {
|
||||
renderWithProviders(
|
||||
<SurveyPrompt isVisible={true} onOpen={vi.fn()} onDismiss={vi.fn()} />
|
||||
);
|
||||
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(
|
||||
<SurveyPrompt isVisible={true} onOpen={onOpen} onDismiss={vi.fn()} />
|
||||
);
|
||||
|
||||
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(
|
||||
<SurveyPrompt isVisible={true} onOpen={vi.fn()} onDismiss={onDismiss} />
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /Dismiss/i }));
|
||||
|
||||
expect(onDismiss).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not render when isVisible is false", () => {
|
||||
renderWithProviders(
|
||||
<SurveyPrompt isVisible={false} onOpen={vi.fn()} onDismiss={vi.fn()} />
|
||||
);
|
||||
expect(screen.queryByText("Quick feedback")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user