= {}) => {
+ const base: ImportExportMock = {
+ selectedFile: "",
+ status: "idle",
+ errorMessage: null,
+ backupId: null,
+ isImporting: false,
+ selectImportFile: vi.fn(),
+ importConfig: vi.fn(),
+ exportConfig: vi.fn(),
+ clearSelection: vi.fn(),
+ resetStatus: vi.fn(),
+ };
+
+ return { ...base, ...overrides };
+};
+
+let settingsMock = createSettingsMock();
+let importExportMock = createImportExportMock();
+
+vi.mock("@/hooks/useSettings", () => ({
+ useSettings: () => settingsMock,
+}));
+
+vi.mock("@/hooks/useImportExport", () => ({
+ useImportExport: () => importExportMock,
+}));
+
+vi.mock("@/lib/api", () => ({
+ settingsApi: {
+ restart: vi.fn().mockResolvedValue(true),
+ },
+}));
+
+const TabsContext = createContext<{ value: string; onValueChange?: (value: string) => void }>({
+ value: "general",
+});
+
+vi.mock("@/components/ui/dialog", () => ({
+ Dialog: ({ open, children }: any) => (open ? {children}
: null),
+ DialogContent: ({ children }: any) => {children}
,
+ DialogHeader: ({ children }: any) => {children}
,
+ DialogFooter: ({ children }: any) => {children}
,
+ DialogTitle: ({ children }: any) => {children}
,
+}));
+
+vi.mock("@/components/ui/tabs", () => {
+ return {
+ Tabs: ({ value, onValueChange, children }: any) => (
+
+ {children}
+
+ ),
+ TabsList: ({ children }: any) => {children}
,
+ TabsTrigger: ({ value, children }: any) => {
+ const ctx = useContext(TabsContext);
+ return (
+
+ );
+ },
+ TabsContent: ({ value, children }: any) => {
+ const ctx = useContext(TabsContext);
+ if (ctx.value !== value) return null;
+ return {children}
;
+ },
+ };
+});
+
+vi.mock("@/components/settings/LanguageSettings", () => ({
+ LanguageSettings: ({ value, onChange }: any) => (
+
+ language:{value}
+
+
+ ),
+}));
+
+vi.mock("@/components/settings/ThemeSettings", () => ({
+ ThemeSettings: () => theme-settings
,
+}));
+
+vi.mock("@/components/settings/WindowSettings", () => ({
+ WindowSettings: ({ onChange }: any) => (
+
+ ),
+}));
+
+vi.mock("@/components/settings/DirectorySettings", () => ({
+ DirectorySettings: ({ onBrowseDirectory }: any) => (
+
+ ),
+}));
+
+vi.mock("@/components/settings/AboutSection", () => ({
+ AboutSection: ({ isPortable }: any) => about:{String(isPortable)}
,
+}));
+
+let settingsApi: any;
+
+describe("SettingsDialog Component", () => {
+ beforeEach(async () => {
+ tMock.mockImplementation((key: string) => key);
+ settingsMock = createSettingsMock();
+ importExportMock = createImportExportMock();
+ toastSuccessMock.mockReset();
+ toastErrorMock.mockReset();
+ settingsApi = (await import("@/lib/api")).settingsApi;
+ settingsApi.restart.mockClear();
+ });
+
+ it("should not render form content when loading", () => {
+ settingsMock = createSettingsMock({ settings: null, isLoading: true });
+
+ render();
+
+ expect(screen.queryByText("language:zh")).not.toBeInTheDocument();
+ expect(screen.getByText("settings.title")).toBeInTheDocument();
+ });
+
+ it("should render general and advanced tabs and trigger child callbacks", () => {
+ const onOpenChange = vi.fn();
+ importExportMock = createImportExportMock({ selectedFile: "" });
+
+ render();
+
+ expect(screen.getByText("language:zh")).toBeInTheDocument();
+ expect(screen.getByText("theme-settings")).toBeInTheDocument();
+
+ fireEvent.click(screen.getByText("change-language"));
+ expect(settingsMock.updateSettings).toHaveBeenCalledWith({ language: "en" });
+
+ fireEvent.click(screen.getByText("window-settings"));
+ expect(settingsMock.updateSettings).toHaveBeenCalledWith({ minimizeToTrayOnClose: false });
+
+ fireEvent.click(screen.getByText("settings.tabAdvanced"));
+ fireEvent.click(screen.getByRole("button", { name: "settings.selectConfigFile" }));
+
+ expect(importExportMock.selectImportFile).toHaveBeenCalled();
+ });
+
+ it("should call saveSettings and close dialog when clicking save", async () => {
+ const onOpenChange = vi.fn();
+ importExportMock = createImportExportMock();
+
+ render();
+
+ fireEvent.click(screen.getByText("common.save"));
+
+ await waitFor(() => {
+ expect(settingsMock.saveSettings).toHaveBeenCalledTimes(1);
+ expect(importExportMock.clearSelection).toHaveBeenCalledTimes(1);
+ expect(importExportMock.resetStatus).toHaveBeenCalledTimes(2);
+ expect(settingsMock.acknowledgeRestart).toHaveBeenCalledTimes(1);
+ expect(onOpenChange).toHaveBeenCalledWith(false);
+ });
+ });
+
+ it("should reset settings and close dialog when clicking cancel", () => {
+ const onOpenChange = vi.fn();
+
+ render();
+
+ fireEvent.click(screen.getByText("common.cancel"));
+
+ expect(settingsMock.resetSettings).toHaveBeenCalledTimes(1);
+ expect(settingsMock.acknowledgeRestart).toHaveBeenCalledTimes(1);
+ expect(importExportMock.clearSelection).toHaveBeenCalledTimes(1);
+ expect(importExportMock.resetStatus).toHaveBeenCalledTimes(2);
+ expect(onOpenChange).toHaveBeenCalledWith(false);
+ });
+
+ it("should show restart prompt and allow immediate restart after save", async () => {
+ settingsMock = createSettingsMock({
+ requiresRestart: true,
+ saveSettings: vi.fn().mockResolvedValue({ requiresRestart: true }),
+ });
+
+ render();
+
+ expect(await screen.findByText("settings.restartRequired")).toBeInTheDocument();
+
+ fireEvent.click(screen.getByText("settings.restartNow"));
+
+ await waitFor(() => {
+ expect(toastSuccessMock).toHaveBeenCalledWith("settings.devModeRestartHint");
+ });
+ });
+});