mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
fix(ui): reject garbled number input instead of silently clearing limits
Switch the default team/user settings number fields from type=number to type=text with inputMode, so values the browser flags as badInput (e.g. 12e) reach zod validation and show an error instead of being read as empty and saved as null. Also add noValidate to the user settings form so zod messages render there too.
This commit is contained in:
parent
12dff6b319
commit
ed86414ab5
4 changed files with 41 additions and 12 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import React from "react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
|
@ -139,13 +139,28 @@ describe("DefaultTeamSettingsForm", () => {
|
|||
const { updateSettings } = renderForm();
|
||||
|
||||
await enterEditMode(user);
|
||||
fireEvent.change(await screen.findByLabelText("TPM Limit"), { target: { value: "12.5" } });
|
||||
await user.clear(await screen.findByLabelText("TPM Limit"));
|
||||
await user.type(screen.getByLabelText("TPM Limit"), "12.5");
|
||||
await user.click(await saveButton());
|
||||
|
||||
expect(await screen.findByText("Must be a non-negative whole number")).toBeInTheDocument();
|
||||
expect(updateSettings).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps number-ish junk visible and rejects it instead of silently clearing the limit", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { updateSettings } = renderForm();
|
||||
|
||||
await enterEditMode(user);
|
||||
await user.clear(await screen.findByLabelText("TPM Limit"));
|
||||
await user.type(screen.getByLabelText("TPM Limit"), "12e");
|
||||
await user.click(await saveButton());
|
||||
|
||||
expect(screen.getByLabelText("TPM Limit")).toHaveValue("12e");
|
||||
expect(await screen.findByText("Must be a non-negative whole number")).toBeInTheDocument();
|
||||
expect(updateSettings).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sends the models selection through unchanged, sentinel values included", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { updateSettings } = renderForm();
|
||||
|
|
@ -223,7 +238,7 @@ describe("DefaultTeamSettingsForm", () => {
|
|||
expect(NotificationsManager.fromBackend).toHaveBeenCalledWith("Set `'STORE_MODEL_IN_DB='True'` in your env."),
|
||||
);
|
||||
expect(await saveButton()).toBeEnabled();
|
||||
expect(screen.getByLabelText("Max Budget (USD)")).toHaveValue(250);
|
||||
expect(screen.getByLabelText("Max Budget (USD)")).toHaveValue("250");
|
||||
});
|
||||
|
||||
it("discards edits and returns to the read-only view when Cancel is pressed", async () => {
|
||||
|
|
@ -240,7 +255,7 @@ describe("DefaultTeamSettingsForm", () => {
|
|||
expect(updateSettings).not.toHaveBeenCalled();
|
||||
|
||||
await enterEditMode(user);
|
||||
expect(screen.getByLabelText("Max Budget (USD)")).toHaveValue(100);
|
||||
expect(screen.getByLabelText("Max Budget (USD)")).toHaveValue("100");
|
||||
expect(await saveButton()).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ const SettingsForm = ({ initialValues, updateSettings, onCancel, onSaved }: Sett
|
|||
label="Max Budget (USD)"
|
||||
description="Default maximum budget for new teams"
|
||||
>
|
||||
{({ ref, ...field }) => <Input {...field} ref={ref} type="number" step={0.01} min={0} />}
|
||||
{({ ref, ...field }) => <Input {...field} ref={ref} type="text" inputMode="decimal" />}
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
|
|
@ -210,7 +210,7 @@ const SettingsForm = ({ initialValues, updateSettings, onCancel, onSaved }: Sett
|
|||
label="TPM Limit"
|
||||
description="Default tokens-per-minute limit for new teams"
|
||||
>
|
||||
{({ ref, ...field }) => <Input {...field} ref={ref} type="number" step={1} min={0} />}
|
||||
{({ ref, ...field }) => <Input {...field} ref={ref} type="text" inputMode="numeric" />}
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
|
|
@ -219,7 +219,7 @@ const SettingsForm = ({ initialValues, updateSettings, onCancel, onSaved }: Sett
|
|||
label="RPM Limit"
|
||||
description="Default requests-per-minute limit for new teams"
|
||||
>
|
||||
{({ ref, ...field }) => <Input {...field} ref={ref} type="number" step={1} min={0} />}
|
||||
{({ ref, ...field }) => <Input {...field} ref={ref} type="text" inputMode="numeric" />}
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
|
|
|
|||
|
|
@ -161,6 +161,20 @@ describe("DefaultUserSettingsForm", () => {
|
|||
expect(updateSettings).toHaveBeenCalledWith({ ...SAVED_BODY, max_budget: null });
|
||||
});
|
||||
|
||||
it("keeps number-ish junk visible and rejects it instead of silently clearing the budget", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { updateSettings } = renderForm();
|
||||
|
||||
await enterEditMode(user);
|
||||
await user.clear(await screen.findByLabelText("Max Budget (USD)"));
|
||||
await user.type(screen.getByLabelText("Max Budget (USD)"), "12e");
|
||||
await user.click(await saveButton());
|
||||
|
||||
expect(screen.getByLabelText("Max Budget (USD)")).toHaveValue("12e");
|
||||
expect(await screen.findByText("Must be a non-negative number")).toBeInTheDocument();
|
||||
expect(updateSettings).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sends the models selection through unchanged, sentinel values included", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { updateSettings } = renderForm();
|
||||
|
|
@ -275,7 +289,7 @@ describe("DefaultUserSettingsForm", () => {
|
|||
expect(NotificationsManager.fromBackend).toHaveBeenCalledWith("Team(s) not found: team-alhpa."),
|
||||
);
|
||||
expect(await saveButton()).toBeEnabled();
|
||||
expect(screen.getByLabelText("Max Budget (USD)")).toHaveValue(250);
|
||||
expect(screen.getByLabelText("Max Budget (USD)")).toHaveValue("250");
|
||||
});
|
||||
|
||||
it("discards edits and returns to the read-only view when Cancel is pressed", async () => {
|
||||
|
|
@ -293,7 +307,7 @@ describe("DefaultUserSettingsForm", () => {
|
|||
expect(updateSettings).not.toHaveBeenCalled();
|
||||
|
||||
await enterEditMode(user);
|
||||
expect(screen.getByLabelText("Max Budget (USD)")).toHaveValue(100);
|
||||
expect(screen.getByLabelText("Max Budget (USD)")).toHaveValue("100");
|
||||
expect(await saveButton()).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ const TeamsField = ({ control }: { control: SettingsControl }) => {
|
|||
|
||||
<FormField control={control} name={`teams.${index}.max_budget_in_team`} label="Max Budget in Team (USD)">
|
||||
{({ ref, ...budgetField }) => (
|
||||
<Input {...budgetField} ref={ref} type="number" step={0.01} min={0} placeholder="Optional" />
|
||||
<Input {...budgetField} ref={ref} type="text" inputMode="decimal" placeholder="Optional" />
|
||||
)}
|
||||
</FormField>
|
||||
|
||||
|
|
@ -244,7 +244,7 @@ const SettingsForm = ({ initialValues, roleOptions, updateSettings, onCancel, on
|
|||
const onSubmit = form.handleSubmit((values) => mutation.mutate(values));
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
<form onSubmit={onSubmit} noValidate>
|
||||
<FieldGroup>
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
|
@ -281,7 +281,7 @@ const SettingsForm = ({ initialValues, roleOptions, updateSettings, onCancel, on
|
|||
label="Max Budget (USD)"
|
||||
description="Default maximum budget for new users"
|
||||
>
|
||||
{({ ref, ...field }) => <Input {...field} ref={ref} type="number" step={0.01} min={0} />}
|
||||
{({ ref, ...field }) => <Input {...field} ref={ref} type="text" inputMode="decimal" />}
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue