refactor: release v1.0.5 安全精简与网站发布
web-platform-ci / Standalone decision service (no cloud credentials) (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
lekiwi-compatibility / cpu-compatibility (push) Has been cancelled
web-platform-ci / Standalone decision service (no cloud credentials) (pull_request) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (pull_request) Has been cancelled
web-platform-ci / Playwright E2E (pull_request) Has been cancelled
lekiwi-compatibility / cpu-compatibility (pull_request) Has been cancelled
web-platform-ci / Standalone decision service (no cloud credentials) (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
lekiwi-compatibility / cpu-compatibility (push) Has been cancelled
web-platform-ci / Standalone decision service (no cloud credentials) (pull_request) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (pull_request) Has been cancelled
web-platform-ci / Playwright E2E (pull_request) Has been cancelled
lekiwi-compatibility / cpu-compatibility (pull_request) Has been cancelled
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { downloadBytes, readCachedText } from '../../project/cachedFiles';
|
||||
import type { ProjectManifest } from '../../project/types';
|
||||
import type { PhysicsAdapter } from '../../simulation/PhysicsAdapter';
|
||||
import { useProjectSource } from './useProjectSource';
|
||||
|
||||
vi.mock('../../project/cachedFiles', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import('../../project/cachedFiles')>()),
|
||||
downloadBytes: vi.fn(),
|
||||
}));
|
||||
beforeEach(() => vi.clearAllMocks());
|
||||
|
||||
function fixture() {
|
||||
const text = '<robot name="test"><link name="base"/></robot>',
|
||||
data = new TextEncoder().encode(text);
|
||||
const manifest = {
|
||||
current: {
|
||||
id: 'p',
|
||||
name: '测试 工程.zip',
|
||||
files: [{ path: 'robot.urdf', data, size: data.length, source: 'zip', mimeType: '' }],
|
||||
entries: [{ path: 'robot.urdf', format: 'urdf', label: 'robot' }],
|
||||
maps: [],
|
||||
totalBytes: data.length,
|
||||
} as ProjectManifest | null,
|
||||
};
|
||||
const exported = new TextEncoder().encode('<mujoco><worldbody/></mujoco>');
|
||||
const backend = {
|
||||
describeRobot: () => ({ profileId: 'lekiwi-v1' }),
|
||||
exportMjcf: vi.fn(() => exported),
|
||||
cachedSupportFiles: () => [],
|
||||
};
|
||||
const events: string[] = [];
|
||||
const options: Parameters<typeof useProjectSource>[0] = {
|
||||
manifest,
|
||||
adapter: { current: backend as unknown as PhysicsAdapter },
|
||||
robotProfiles: { current: new Map() },
|
||||
selectedEntry: 'robot.urdf',
|
||||
selectedFormat: 'urdf',
|
||||
onProjectUpdated: vi.fn(() => {
|
||||
events.push('project');
|
||||
}),
|
||||
onDiagnostic: vi.fn(),
|
||||
notify: vi.fn(() => {
|
||||
events.push('notice');
|
||||
}),
|
||||
loadEntry: vi.fn(async () => {
|
||||
events.push('load');
|
||||
return false;
|
||||
}),
|
||||
};
|
||||
return { options, backend, events, text, exported };
|
||||
}
|
||||
|
||||
describe('工程源码与导出动作', () => {
|
||||
it('先保留 profile/写缓存/发布清单和通知,再调用加载;编译失败仍保留可修改源码', async () => {
|
||||
const { options, events } = fixture();
|
||||
const { result } = renderHook(() => useProjectSource(options));
|
||||
await act(async () => {
|
||||
await result.current.saveCachedSource('cache.xml', '<mujoco model="edited"/>');
|
||||
});
|
||||
expect(events).toEqual(['project', 'notice', 'load']);
|
||||
expect(options.robotProfiles.current.get('cache.xml')).toBe('lekiwi-v1');
|
||||
expect(readCachedText(options.manifest.current!, 'cache.xml')).toBe('<mujoco model="edited"/>');
|
||||
expect(options.loadEntry).toHaveBeenCalledWith('cache.xml');
|
||||
});
|
||||
|
||||
it('URDF 导出读取当前缓存,切换格式后入口失效;MJCF 保留 XML 资源提示', () => {
|
||||
const { options, text, exported } = fixture();
|
||||
const { result, rerender } = renderHook((opts) => useProjectSource(opts), {
|
||||
initialProps: options,
|
||||
});
|
||||
result.current.exportUrdf();
|
||||
expect(downloadBytes).toHaveBeenCalledWith(new TextEncoder().encode(text), '测试_工程.urdf');
|
||||
vi.mocked(downloadBytes).mockClear();
|
||||
rerender({ ...options, selectedFormat: 'mjcf' });
|
||||
result.current.exportUrdf();
|
||||
expect(downloadBytes).not.toHaveBeenCalled();
|
||||
result.current.exportMjcf();
|
||||
expect(downloadBytes).toHaveBeenCalledWith(exported, '测试_工程.xml');
|
||||
expect(options.notify).toHaveBeenCalledWith('MJCF 已导出', expect.stringContaining('仅 XML'));
|
||||
});
|
||||
|
||||
it('MJCF/ZIP 导出异常保留不同诊断类别与入口路径', () => {
|
||||
const { options, backend } = fixture();
|
||||
backend.exportMjcf.mockImplementation(() => {
|
||||
throw new Error('导出失败');
|
||||
});
|
||||
const { result } = renderHook(() => useProjectSource(options));
|
||||
result.current.exportMjcf();
|
||||
result.current.exportProjectPackage();
|
||||
expect(options.onDiagnostic).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({ category: '模型编译', path: 'robot.urdf', detail: '导出失败' }),
|
||||
);
|
||||
expect(options.onDiagnostic).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({ category: '文件系统', path: 'robot.urdf', detail: '导出失败' }),
|
||||
);
|
||||
expect(downloadBytes).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user