diff --git a/src/ui/sidebar/AdminSettingsPanel.tsx b/src/ui/sidebar/AdminSettingsPanel.tsx index 535126c2d..4c91c589c 100644 --- a/src/ui/sidebar/AdminSettingsPanel.tsx +++ b/src/ui/sidebar/AdminSettingsPanel.tsx @@ -217,6 +217,12 @@ export function AdminSettingsPanel({ setUserPage(0); }, [userSearch]); + // The panel stays mounted across Settings pages, so an open user view + // would otherwise replace whichever section is picked next. + useEffect(() => { + setManageUser(null); + }, [section]); + useEffect(() => { onEditingChange?.(manageUser !== null); return () => onEditingChange?.(false); diff --git a/src/ui/tests/sidebar/AdminSettingsPanel.test.tsx b/src/ui/tests/sidebar/AdminSettingsPanel.test.tsx new file mode 100644 index 000000000..8402b00b9 --- /dev/null +++ b/src/ui/tests/sidebar/AdminSettingsPanel.test.tsx @@ -0,0 +1,113 @@ +import { describe, it, expect, vi, afterEach } from "vitest"; +import { cleanup, render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +import { AdminSettingsPanel } from "../../sidebar/AdminSettingsPanel"; +import type { AdminUser } from "../../sidebar/AdminManagementSections"; + +vi.mock("@/main-axios", () => ({ + getUserList: vi.fn(async () => ({ users: [], total: 0 })), + getSessions: vi.fn(async () => ({ sessions: [] })), + getRoles: vi.fn(async () => ({ roles: [] })), + getApiKeys: vi.fn(async () => ({ apiKeys: [] })), + getUserRoles: vi.fn(async () => ({ roles: [] })), + isElectron: () => false, +})); + +vi.mock("@/api/settings-api", () => ({})); +vi.mock("@/api/tls-api", () => ({ getTlsStatus: vi.fn(async () => null) })); +vi.mock("@/api/private-endpoints-api", () => ({})); + +vi.mock("@/contexts/BrandingContext", () => ({ + useBranding: () => ({ applyBranding: vi.fn() }), +})); + +vi.mock("@/components/surface/surface-scope", () => ({ + useConfirm: () => vi.fn(async () => true), +})); + +vi.mock("@/settings/FeatureSettingsSections", () => ({ + FeatureSettingsSection: () => null, + featureSectionId: (id: string) => `feature:${id}`, + useFeatureSettings: () => [], +})); + +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ t: (key: string) => key }), +})); + +vi.mock("sonner", () => ({ + toast: { success: vi.fn(), error: vi.fn() }, +})); + +const { bob, section } = vi.hoisted(() => ({ + bob: { + id: "u2", + username: "bob", + isAdmin: false, + isExternal: false, + passwordHash: "hash", + dataUnlocked: true, + } as AdminUser, + section: + (label: string) => + ({ open }: { open: boolean }) => + open ?
{label}
: null, +})); + +vi.mock("../../sidebar/AdminManagementSections", () => ({ + AdminUsersSection: ({ + open, + onManageUser, + }: { + open: boolean; + onManageUser: (user: AdminUser) => void; + }) => + open ? : null, + AdminSessionsSection: section("sessions section"), + AdminRolesSection: section("roles section"), +})); + +vi.mock("../../sidebar/AdminSettingsSections", () => ({ + AdminGeneralSettingsSection: section("general section"), + AdminHostDefaultsSection: section("host defaults section"), + AdminDatabaseSection: section("database section"), + AdminSSLSection: section("ssl section"), +})); + +vi.mock("../../sidebar/AdminApiKeysSection", () => ({ + AdminApiKeysSection: section("api keys section"), +})); +vi.mock("../../sidebar/AdminAuditLogSection", () => ({ + AdminAuditLogSection: section("audit log section"), +})); +vi.mock("../../sidebar/AdminBrandingSection", () => ({ + AdminBrandingSection: section("branding section"), +})); +vi.mock("../../sidebar/AdminUserDialogs", () => ({ + AdminCreateUserDialog: () => null, + AdminEditUserDialog: () => null, + AdminLinkAccountDialog: () => null, + AdminUnlinkAccountDialog: () => null, +})); +vi.mock("../../sidebar/AdminUserManagePanel", () => ({ + AdminUserManagePanel: ({ user }: { user: AdminUser }) => ( +
managing {user.username}
+ ), +})); + +afterEach(cleanup); + +describe("AdminSettingsPanel", () => { + it("leaves the user manage view when another section is selected", async () => { + const { rerender } = render(); + + await userEvent.click(screen.getByText("manage bob")); + expect(screen.getByText("managing bob")).toBeTruthy(); + + rerender(); + + expect(screen.getByText("sessions section")).toBeTruthy(); + expect(screen.queryByText("managing bob")).toBeNull(); + }); +});