fix(ui): keep an untouched member budget duration distinct from never resets

The member duration dropdown reused its placeholder as "Never resets", so a
team with no member budget yet showed "Never resets" while sending nothing and
inheriting the team's own reset period. Use the dropdown's never-resets
sentinel for an explicit null and label the untouched state as inheriting.
This commit is contained in:
ryan-crabbe-berri 2026-08-26 13:32:03 -07:00
parent 99d4741586
commit f2f389cc6f
2 changed files with 41 additions and 7 deletions

View file

@ -4,7 +4,7 @@ import { fireEvent, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { chooseSelectOption, renderWithProviders, testQueryClient } from "../../../tests/test-utils";
import TeamInfoView from "./TeamInfo";
import TeamInfoView, { type TeamData } from "./TeamInfo";
const authState = vi.hoisted(() => ({ userRole: "Admin" }));
@ -1613,10 +1613,18 @@ describe("TeamInfoView - which team member fields reach the update payload depen
vi.clearAllMocks();
});
const openEditor = async (user: ReturnType<typeof userEvent.setup>) => {
const openEditor = async (
user: ReturnType<typeof userEvent.setup>,
teamMemberBudgetTable: TeamData["team_info"]["team_member_budget_table"] = {
max_budget: 42,
budget_duration: "30d",
tpm_limit: 11,
rpm_limit: 22,
},
) => {
vi.mocked(networking.teamInfoCall).mockResolvedValue(
createMockTeamData({
team_member_budget_table: { max_budget: 42, budget_duration: "30d", tpm_limit: 11, rpm_limit: 22 },
team_member_budget_table: teamMemberBudgetTable,
default_team_member_models: ["gpt-4"],
}),
);
@ -1682,6 +1690,31 @@ describe("TeamInfoView - which team member fields reach the update payload depen
expect(JSON.stringify(payload)).toContain('"team_member_budget_duration":null');
});
it("shows Never resets for a stored member budget whose duration is null", async () => {
const user = userEvent.setup({ delay: null });
await openEditor(user, { max_budget: 42, budget_duration: null, tpm_limit: null, rpm_limit: null });
await user.click(screen.getByText("Team Member Settings"));
expect(await screen.findByLabelText("Default Budget Duration")).toHaveTextContent("Never resets");
});
it("omits team_member_budget_duration when the dropdown is left untouched on a team with no member budget", async () => {
const user = userEvent.setup({ delay: null });
await openEditor(user, null);
await user.click(screen.getByText("Team Member Settings"));
const durationSelect = await screen.findByLabelText("Default Budget Duration");
expect(durationSelect).toHaveTextContent("Inherit team reset period");
expect(durationSelect).not.toHaveTextContent("Never resets");
await user.type(screen.getByLabelText("Default Budget (USD)"), "100");
const payload = await save(user);
expect(payload.team_member_budget).toBe(100);
expect(JSON.parse(JSON.stringify(payload))).not.toHaveProperty("team_member_budget_duration");
});
it("omits object_permission.search_tools while Search Tool Settings is closed", async () => {
const user = userEvent.setup({ delay: null });
await openEditor(user);

View file

@ -48,7 +48,7 @@ import { z } from "zod/v4";
import GuardrailsSelect from "./GuardrailsSelect";
import { copyToClipboard as utilCopyToClipboard } from "../../utils/dataUtils";
import AccessGroupSelector from "../common_components/AccessGroupSelector";
import BudgetDurationDropdown from "../common_components/budget_duration_dropdown";
import BudgetDurationDropdown, { NEVER_RESETS_BUDGET_DURATION } from "../common_components/budget_duration_dropdown";
import {
computeTeamModelBadges,
normalizeTeamModelSelection,
@ -1258,9 +1258,10 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
{({ id, value, onChange }) => (
<BudgetDurationDropdown
id={id}
placeholder="Never resets"
value={value}
onChange={(next) => onChange(next ?? null)}
showNeverResets
placeholder="Inherit team reset period"
value={value === null ? NEVER_RESETS_BUDGET_DURATION : value}
onChange={(next) => onChange(next === NEVER_RESETS_BUDGET_DURATION ? null : next)}
/>
)}
</FormField>