/** @vitest-environment jsdom */ import { act, renderHook, waitFor } from '@testing-library/react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { normalizeProjectCreationDirectory } from '../src/features/app-shell/model'; import { useProjectCreationDirectory } from '../src/features/app-shell/useProjectCreationDirectory'; import { readWorkspacePreferences, resetWorkspacePreferencesForTests, setProjectCreationDirectory, } from '../src/features/app-shell/workspacePreferences'; describe('项目创建目录偏好', () => { beforeEach(() => { resetWorkspacePreferencesForTests(); delete window.__TAURI__; }); it('去掉首尾空白与多余分隔符,并保留盘根', () => { expect(normalizeProjectCreationDirectory(' ')).toBe(''); expect(normalizeProjectCreationDirectory(' F:\\Projects\\游戏\\ ')).toBe( 'F:\\Projects\\游戏', ); expect(normalizeProjectCreationDirectory('F:/Projects/游戏/')).toBe( 'F:/Projects/游戏', ); expect(normalizeProjectCreationDirectory('C:\\')).toBe('C:\\'); // 首尾空白按 trim 处理;目录中间的控制字符必须整条拒绝。 expect(normalizeProjectCreationDirectory('F:\\游戏\n')).toBe('F:\\游戏'); expect(normalizeProjectCreationDirectory('F:\\游\n戏')).toBe(''); }); it('在无 Tauri 时由内存快照承接设置交互', async () => { await expect(readWorkspacePreferences()).resolves.toMatchObject({ projectCreationDirectory: null, }); await expect( setProjectCreationDirectory('C:\\Projects\\games'), ).resolves.toMatchObject({ projectCreationDirectory: 'C:\\Projects\\games', }); await expect(readWorkspacePreferences()).resolves.toMatchObject({ projectCreationDirectory: 'C:\\Projects\\games', }); await expect(setProjectCreationDirectory(null)).resolves.toMatchObject({ projectCreationDirectory: null, }); }); it('非法目录不会进入正式快照', async () => { await expect( setProjectCreationDirectory('relative/games'), ).resolves.toMatchObject({ projectCreationDirectory: null, }); }); it('订阅成功后读取的旧快照不会覆盖较新的 Rust 偏好事件', async () => { let resolveRead: | ((snapshot: { projectCreationDirectory: string | null; recentWorkspaces: string[]; }) => void) | null = null; let eventHandler: | ((event: { payload: { projectCreationDirectory: string | null; recentWorkspaces: string[]; }; }) => void) | null = null; const invoke = vi.fn(async (command: string) => { if (command === 'read_workspace_preferences') { return await new Promise<{ projectCreationDirectory: string | null; recentWorkspaces: string[]; }>((resolve) => { resolveRead = resolve; }); } throw new Error('unexpected invoke ' + command); }); window.__TAURI__ = { core: { invoke }, event: { listen: vi.fn(async (_event, handler) => { eventHandler = handler; return () => { eventHandler = null; }; }), }, }; const { result } = renderHook(() => useProjectCreationDirectory()); await waitFor(() => { expect(invoke).toHaveBeenCalledWith('read_workspace_preferences'); }); act(() => { eventHandler?.({ payload: { projectCreationDirectory: 'F:\\Projects\\new', recentWorkspaces: [], }, }); }); await act(async () => { resolveRead?.({ projectCreationDirectory: null, recentWorkspaces: [], }); }); await waitFor(() => { expect(result.current.projectCreationDirectory).toBe('F:\\Projects\\new'); }); }); });