fix(ui): keep team role and budget when the team ID is blank

normalizeTeams guarded the object branch on the truthiness of team.team_id,
so a row with an empty ID fell through to the reset branch that discards
user_role and max_budget_in_team. Setting a role before typing an ID showed
the row snapping back to "user", and the next edit to that row dropped
whatever had been set before it. Guard on the shape of the entry instead.
This commit is contained in:
Yuneng Jiang 2026-07-24 23:49:16 -07:00
parent b9b27c2beb
commit 7a718d19c2
No known key found for this signature in database
2 changed files with 108 additions and 1 deletions

View file

@ -150,4 +150,111 @@ describe("DefaultUserSettings", () => {
expect(screen.getByText("Edit Settings")).toBeInTheDocument();
});
describe("editable fields", () => {
const teamsOnlySettings = {
values: {
teams: [],
},
field_schema: {
description: "Default user settings",
properties: {
teams: {
type: "array",
description: "Teams",
},
},
},
};
const enterEditMode = async () => {
mockGetInternalUserSettings.mockResolvedValue(teamsOnlySettings);
mockUpdateInternalUserSettings.mockResolvedValue({ settings: {} });
render(<DefaultUserSettings {...defaultProps} />);
await waitFor(() => {
expect(screen.getByText("Edit Settings")).toBeInTheDocument();
});
act(() => {
fireEvent.click(screen.getByText("Edit Settings"));
});
};
const savedPayload = () => mockUpdateInternalUserSettings.mock.calls[0][1] as Record<string, unknown>;
const selectUserRole = async (optionText: string) => {
act(() => {
fireEvent.mouseDown(document.querySelector(".ant-select-selector")!);
});
await waitFor(() => {
expect(document.querySelectorAll(".ant-select-item-option").length).toBeGreaterThan(0);
});
const option = Array.from(document.querySelectorAll(".ant-select-item-option")).find((el) =>
el.textContent?.includes(optionText),
);
expect(option).toBeTruthy();
act(() => {
fireEvent.click(option!);
});
};
it("keeps the stored role and max budget of a team saved without an ID", async () => {
mockGetInternalUserSettings.mockResolvedValue({
...teamsOnlySettings,
values: { teams: [{ team_id: "", max_budget_in_team: 25, user_role: "admin" }] },
});
mockUpdateInternalUserSettings.mockResolvedValue({ settings: {} });
render(<DefaultUserSettings {...defaultProps} />);
await waitFor(() => {
expect(screen.getByText("Edit Settings")).toBeInTheDocument();
});
act(() => {
fireEvent.click(screen.getByText("Edit Settings"));
});
expect(screen.getByPlaceholderText("Enter team ID")).toHaveValue("");
expect(screen.getByPlaceholderText("Optional")).toHaveValue("25.00");
expect(document.querySelector(".ant-select-selection-item")).toHaveTextContent("Admin");
});
it("keeps showing the selected role of a team whose ID has not been typed yet", async () => {
await enterEditMode();
act(() => {
fireEvent.click(screen.getByText("Add Team"));
});
await selectUserRole("Admin");
expect(document.querySelector(".ant-select-selection-item")).toHaveTextContent("Admin");
});
it("keeps the role and max budget of a team whose ID has not been typed yet", async () => {
await enterEditMode();
act(() => {
fireEvent.click(screen.getByText("Add Team"));
});
act(() => {
fireEvent.change(screen.getByPlaceholderText("Optional"), { target: { value: "25" } });
});
await selectUserRole("Admin");
act(() => {
fireEvent.click(screen.getByText("Save Changes"));
});
await waitFor(() => {
expect(mockUpdateInternalUserSettings).toHaveBeenCalled();
});
expect(savedPayload().teams).toEqual([{ team_id: "", max_budget_in_team: 25, user_role: "admin" }]);
});
});
});

View file

@ -115,7 +115,7 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
team_id: team,
user_role: "user" as const,
};
} else if (typeof team === "object" && team.team_id) {
} else if (typeof team === "object" && team !== null && "team_id" in team) {
return {
team_id: team.team_id,
max_budget_in_team: team.max_budget_in_team,