Files
cc-switch/tests/hooks/useSettingsMetadata.test.tsx
Jason 0b40e200f5 test: add useDirectorySettings and useSettingsMetadata hook tests
useDirectorySettings Tests:
- Test directory initialization with overrides and remote defaults
  - Verify app config override with space trimming
  - Load Claude/Codex directories from remote API
  - Calculate resolvedDirs correctly
- Test directory browsing functionality
  - Browse Claude/Codex config directories
  - Browse app config directory with proper default paths
  - Update settings callback when selection succeeds
- Test error handling scenarios
  - User cancels directory selection (returns null)
  - Directory picker throws error (shows toast)
  - Verify settings not updated on failure
- Test directory reset operations
  - Reset individual directories to computed defaults
  - Reset app config directory
  - Batch reset with provided server values
- Use vi.hoisted() for proper mock initialization
- Factory function for settings creation (reusability)

useSettingsMetadata Tests:
- Test portable mode flag loading
  - Verify initial loading state
  - Load portable flag from API
  - Handle async state transitions
- Test error tolerance when API fails
  - Silent failure on network errors
  - Default to non-portable mode
  - Continue without blocking UI
- Test restart flag management
  - Set restart required flag
  - Acknowledge restart to clear flag
  - State updates wrapped in act()

All tests passing: 10/10 (7 useDirectorySettings + 3 useSettingsMetadata)
2025-10-25 21:39:21 +08:00

71 lines
1.8 KiB
TypeScript

import { renderHook, act } from "@testing-library/react";
import { describe, it, expect, beforeEach, vi } from "vitest";
import { useSettingsMetadata } from "@/hooks/useSettingsMetadata";
const isPortableMock = vi.hoisted(() => vi.fn());
vi.mock("@/lib/api", () => ({
settingsApi: {
isPortable: (...args: unknown[]) => isPortableMock(...args),
},
}));
describe("useSettingsMetadata", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("loads portable flag and handles success path", async () => {
isPortableMock.mockResolvedValue(true);
const { result } = renderHook(() => useSettingsMetadata());
expect(result.current.isLoading).toBe(true);
expect(result.current.isPortable).toBe(false);
await act(async () => {
await Promise.resolve();
});
expect(result.current.isPortable).toBe(true);
expect(result.current.isLoading).toBe(false);
});
it("handles errors from settingsApi and proceeds", async () => {
isPortableMock.mockRejectedValue(new Error("network failure"));
const { result } = renderHook(() => useSettingsMetadata());
await act(async () => {
await Promise.resolve();
});
expect(result.current.isPortable).toBe(false);
expect(result.current.isLoading).toBe(false);
});
it("allows updating restart flag via setters", async () => {
isPortableMock.mockResolvedValue(false);
const { result } = renderHook(() => useSettingsMetadata());
await act(async () => {
await Promise.resolve();
});
await act(async () => {
result.current.setRequiresRestart(true);
await Promise.resolve();
});
expect(result.current.requiresRestart).toBe(true);
await act(async () => {
result.current.acknowledgeRestart();
await Promise.resolve();
});
expect(result.current.requiresRestart).toBe(false);
});
});