/** @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' },
);
});
});