fix(ui): keep confirmation modal open on API failure, restore response test

executeSave now returns a boolean; handleConfirmSave only clears modal
state on success. Adds tests for the error path and for verifying the
UI reflects API response values after a successful save.
This commit is contained in:
Ryan Crabbe 2026-03-21 12:01:01 -07:00
parent 30181aa71c
commit 94c22707a2
2 changed files with 92 additions and 8 deletions

View file

@ -310,6 +310,86 @@ describe("DefaultUserSettings", () => {
// Should still be in edit mode (Save Changes button visible)
expect(screen.getByText("Save Changes")).toBeInTheDocument();
});
it("should keep modal open when API save fails", async () => {
const settingsWithTeams = {
...mockSettings,
values: {
...mockSettings.values,
teams: [
{ team_id: "team-alpha", max_budget_in_team: 50, user_role: "user" },
],
},
};
mockGetInternalUserSettings.mockResolvedValue(settingsWithTeams);
mockUpdateInternalUserSettings.mockRejectedValue(new Error("Server error"));
render(<DefaultUserSettings {...defaultProps} />);
await waitFor(() => {
expect(screen.getByText("Edit Settings")).toBeInTheDocument();
});
// Enter edit mode, remove team, click Save → modal opens
act(() => {
fireEvent.click(screen.getByText("Edit Settings"));
});
act(() => {
fireEvent.click(screen.getAllByText("Remove")[0]);
});
act(() => {
fireEvent.click(screen.getByText("Save Changes"));
});
await waitFor(() => {
expect(screen.getByText("Review Changes")).toBeInTheDocument();
});
// Click Confirm — API will reject
await act(async () => {
fireEvent.click(screen.getByText("Confirm Changes"));
});
// Modal should still be open (not cleared on failure)
await waitFor(() => {
expect(screen.getByText("Review Changes")).toBeInTheDocument();
expect(screen.getByText("Confirm Changes")).toBeInTheDocument();
});
});
it("should reflect API response values after successful save", async () => {
mockGetInternalUserSettings.mockResolvedValue(mockSettings);
// API returns different max_budget than what was sent
mockUpdateInternalUserSettings.mockResolvedValue({
settings: {
...mockSettings.values,
max_budget: 2000,
},
});
render(<DefaultUserSettings {...defaultProps} />);
await waitFor(() => {
expect(screen.getByText("Edit Settings")).toBeInTheDocument();
});
act(() => {
fireEvent.click(screen.getByText("Edit Settings"));
});
const saveButton = screen.getByText("Save Changes");
act(() => {
fireEvent.click(saveButton);
});
await waitFor(() => {
expect(mockUpdateInternalUserSettings).toHaveBeenCalled();
});
// Should exit edit mode and show the API response value
expect(screen.getByText("Edit Settings")).toBeInTheDocument();
expect(screen.getByText("2000")).toBeInTheDocument();
});
});
// ---------------------------------------------------------------------------

View file

@ -167,18 +167,20 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
fetchSSOSettings();
}, [accessToken]);
/** Perform the actual API save with the given processed values. */
const executeSave = async (processedValues: DefaultUserSettingsValues) => {
if (!accessToken) return;
/** Perform the actual API save. Returns true on success, false on failure. */
const executeSave = async (processedValues: DefaultUserSettingsValues): Promise<boolean> => {
if (!accessToken) return false;
setSaving(true);
try {
const updatedSettings = await updateInternalUserSettings(accessToken, processedValues);
setSettings({ ...settings, values: updatedSettings.settings });
setIsEditing(false);
return true;
} catch (error) {
console.error("Error updating SSO settings:", error);
NotificationManager.fromBackend("Failed to update settings: " + error);
return false;
} finally {
setSaving(false);
}
@ -209,13 +211,15 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
await executeSave(processedValues);
};
/** Called when user confirms changes in the modal. */
/** Called when user confirms changes in the modal. Only clears modal on success. */
const handleConfirmSave = async () => {
if (!pendingProcessedValues) return;
await executeSave(pendingProcessedValues);
setShowConfirmModal(false);
setPendingChanges([]);
setPendingProcessedValues(null);
const success = await executeSave(pendingProcessedValues);
if (success) {
setShowConfirmModal(false);
setPendingChanges([]);
setPendingProcessedValues(null);
}
};
const handleTextInputChange = (key: keyof DefaultUserSettingsValues, value: DefaultUserSettingsValues[typeof key]) => {