From fde7c1087ae55e1171a156e152859db64c2ee482 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Wed, 15 Apr 2026 12:24:41 -0700 Subject: [PATCH] Move Send Invitation Email checkbox into modal footer, add tests Place the checkbox inline with the submit button (left-aligned, vertically centered) in both the embedded and standalone forms. Add Vitest tests verifying the /user/new payload shape: default true, false when unchecked, in both form variants, plus an initial-state check for the modal. --- .../src/components/CreateUserButton.test.tsx | 122 ++++++++++++++++++ .../src/components/CreateUserButton.tsx | 44 ++++--- 2 files changed, 150 insertions(+), 16 deletions(-) diff --git a/ui/litellm-dashboard/src/components/CreateUserButton.test.tsx b/ui/litellm-dashboard/src/components/CreateUserButton.test.tsx index 03d982ca7c..d3e2e45290 100644 --- a/ui/litellm-dashboard/src/components/CreateUserButton.test.tsx +++ b/ui/litellm-dashboard/src/components/CreateUserButton.test.tsx @@ -354,4 +354,126 @@ describe("CreateUserButton", () => { expect(mockOrganizationMemberAddCall).not.toHaveBeenCalled(); }); }); + + describe("send invitation email toggle", () => { + it("should send send_invite_email true by default in embedded mode", async () => { + const user = userEvent.setup(); + mockUserCreateCall.mockResolvedValue({ data: { user_id: "default-on-user" } }); + + renderWithProviders( + , + ); + + await user.type(screen.getByLabelText(/user email/i), "default@example.com"); + await user.click(screen.getByRole("combobox", { name: /user role/i })); + await user.click(screen.getByText("User")); + await user.click(screen.getByRole("button", { name: /create user/i })); + + await waitFor(() => { + expect(mockUserCreateCall).toHaveBeenCalledWith("token", null, expect.objectContaining({ + send_invite_email: true, + })); + }); + }); + + it("should send send_invite_email false when the checkbox is unchecked in embedded mode", async () => { + const user = userEvent.setup(); + mockUserCreateCall.mockResolvedValue({ data: { user_id: "unchecked-user" } }); + + renderWithProviders( + , + ); + + await user.type(screen.getByLabelText(/user email/i), "off@example.com"); + await user.click(screen.getByRole("combobox", { name: /user role/i })); + await user.click(screen.getByText("User")); + await user.click(screen.getByRole("checkbox", { name: /send invitation email/i })); + await user.click(screen.getByRole("button", { name: /create user/i })); + + await waitFor(() => { + expect(mockUserCreateCall).toHaveBeenCalledWith("token", null, expect.objectContaining({ + send_invite_email: false, + })); + }); + }); + + it("should send send_invite_email true by default in standalone mode", async () => { + const user = userEvent.setup(); + mockUserCreateCall.mockResolvedValue({ data: { user_id: "standalone-default-user" } }); + mockInvitationCreateCall.mockResolvedValue({ + id: "inv-default", + user_id: "standalone-default-user", + has_user_setup_sso: false, + } as any); + + renderWithProviders( + , + ); + + await waitFor(() => { + expect(screen.getByRole("button", { name: /\+ invite user/i })).toBeInTheDocument(); + }); + await user.click(screen.getByRole("button", { name: /\+ invite user/i })); + + const dialog = screen.getByRole("dialog", { name: /invite user/i }); + await user.type(within(dialog).getByLabelText(/user email/i), "standalone-default@example.com"); + await user.click(within(dialog).getByRole("combobox", { name: /global proxy role/i })); + await user.click(screen.getByText("User")); + await user.click(within(dialog).getByRole("button", { name: /invite user/i })); + + await waitFor(() => { + expect(mockUserCreateCall).toHaveBeenCalledWith("token", null, expect.objectContaining({ + send_invite_email: true, + })); + }); + }); + + it("should send send_invite_email false when the checkbox is unchecked in standalone mode", async () => { + const user = userEvent.setup(); + mockUserCreateCall.mockResolvedValue({ data: { user_id: "standalone-off-user" } }); + mockInvitationCreateCall.mockResolvedValue({ + id: "inv-off", + user_id: "standalone-off-user", + has_user_setup_sso: false, + } as any); + + renderWithProviders( + , + ); + + await waitFor(() => { + expect(screen.getByRole("button", { name: /\+ invite user/i })).toBeInTheDocument(); + }); + await user.click(screen.getByRole("button", { name: /\+ invite user/i })); + + const dialog = screen.getByRole("dialog", { name: /invite user/i }); + await user.type(within(dialog).getByLabelText(/user email/i), "standalone-off@example.com"); + await user.click(within(dialog).getByRole("combobox", { name: /global proxy role/i })); + await user.click(screen.getByText("User")); + await user.click(within(dialog).getByRole("checkbox", { name: /send invitation email/i })); + await user.click(within(dialog).getByRole("button", { name: /invite user/i })); + + await waitFor(() => { + expect(mockUserCreateCall).toHaveBeenCalledWith("token", null, expect.objectContaining({ + send_invite_email: false, + })); + }); + }); + + it("should keep the checkbox checked by default when the modal is opened in standalone mode", async () => { + const user = userEvent.setup(); + + renderWithProviders( + , + ); + + await waitFor(() => { + expect(screen.getByRole("button", { name: /\+ invite user/i })).toBeInTheDocument(); + }); + await user.click(screen.getByRole("button", { name: /\+ invite user/i })); + + const dialog = screen.getByRole("dialog", { name: /invite user/i }); + expect(within(dialog).getByRole("checkbox", { name: /send invitation email/i })).toBeChecked(); + }); + }); }); diff --git a/ui/litellm-dashboard/src/components/CreateUserButton.tsx b/ui/litellm-dashboard/src/components/CreateUserButton.tsx index 7d2fc10150..5a3c035bd6 100644 --- a/ui/litellm-dashboard/src/components/CreateUserButton.tsx +++ b/ui/litellm-dashboard/src/components/CreateUserButton.tsx @@ -225,15 +225,21 @@ export const CreateUserButton: React.FC = ({ - - Send invitation email - - -
+ + Send invitation email +
@@ -370,15 +376,21 @@ export const CreateUserButton: React.FC = ({ - - Send invitation email - - -
+ + Send invitation email +