/** @vitest-environment jsdom */ import { act, cleanup, render, waitFor } from '@testing-library/react'; import React, { StrictMode } from 'react'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { useProjectSnapshotWorkspace } from '../src/features/app-shell/useProjectSnapshotWorkspace'; import { createProjectSnapshotWorkspaceRegistration } from '../src/services/projectSnapshotWorkspace'; afterEach(() => { cleanup(); delete window.__TAURI__; }); describe('项目快照窗口登记', () => { it('在无 projectPath URL 的单窗口登记、切换、离开,StrictMode 不误关项目', async () => { const invoke = vi.fn(async () => undefined); window.__TAURI__ = { core: { invoke: invoke as never } }; function Workspace({ path }: { path: string | null }) { useProjectSnapshotWorkspace(path); return null; } const view = (path: string | null) => ( ); expect(new URL(window.location.href).searchParams.has('projectPath')).toBe( false, ); const rendered = render(view('/projects/first')); await waitFor(() => expect(invoke).toHaveBeenCalledTimes(1)); expect(invoke).toHaveBeenLastCalledWith( 'set_active_project_snapshot_workspace', { projectPath: '/projects/first' }, ); rendered.rerender(view('/projects/first')); await act(async () => { await Promise.resolve(); }); expect(invoke).toHaveBeenCalledTimes(1); rendered.rerender(view('/projects/second')); await waitFor(() => expect(invoke).toHaveBeenCalledTimes(2)); expect(invoke).toHaveBeenLastCalledWith( 'set_active_project_snapshot_workspace', { projectPath: '/projects/second' }, ); rendered.rerender(view(null)); await waitFor(() => expect(invoke).toHaveBeenCalledTimes(3)); expect(invoke).toHaveBeenLastCalledWith( 'set_active_project_snapshot_workspace', { projectPath: null }, ); rendered.rerender(view('/projects/third')); await waitFor(() => expect(invoke).toHaveBeenCalledTimes(4)); rendered.unmount(); await waitFor(() => expect(invoke).toHaveBeenCalledTimes(5)); expect(invoke).toHaveBeenLastCalledWith( 'set_active_project_snapshot_workspace', { projectPath: null }, ); }); it('原生登记未完成时顺序提交后续切换,不让迟到回包覆盖新项目', async () => { let release!: () => void; const first = new Promise((resolve) => { release = resolve; }); const invoke = vi .fn() .mockImplementationOnce(() => first) .mockResolvedValue(undefined); const registration = createProjectSnapshotWorkspaceRegistration(invoke); void registration.setProject('/projects/first'); void registration.setProject('/projects/second'); const idle = registration.setProject(null); await Promise.resolve(); expect(invoke).toHaveBeenCalledTimes(1); release(); await idle; expect(invoke.mock.calls.map((call) => call[1]?.projectPath)).toEqual([ '/projects/first', '/projects/second', null, ]); }); it('失败写固定诊断并允许再次登记,不能泄漏原生错误中的路径和凭据', async () => { const invoke = vi .fn() .mockRejectedValueOnce(new Error('C:\\private\\project token=secret')) .mockResolvedValue(undefined); const registration = createProjectSnapshotWorkspaceRegistration(invoke); await expect( registration.setProject('/projects/first'), ).resolves.toBeUndefined(); expect(invoke).toHaveBeenLastCalledWith('append_application_log', { level: 'error', source: 'project-snapshot', message: 'project_snapshot.workspace.registration.failed', }); await registration.setProject('/projects/first'); expect(invoke).toHaveBeenCalledTimes(3); expect(invoke).toHaveBeenLastCalledWith( 'set_active_project_snapshot_workspace', { projectPath: '/projects/first' }, ); }); });