feat(web-platform): release V0.8.1 常规地图优化
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled

This commit is contained in:
2026-09-03 13:18:35 +08:00
parent deead17a9a
commit a9b07e0abf
49 changed files with 4987 additions and 569 deletions
+227 -3
View File
@@ -1,5 +1,5 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { MAP_ASSET_DRAG_MIME, MAP_ASSET_PLACEMENT_MIME } from './editor/assetCatalog';
import { decodeMapLibraryDragPayload, MAP_LIBRARY_DRAG_MIME } from './editor/assetCatalog';
import { MapAssetLibrary } from './MapAssetLibrary';
const renderLibrary = (
@@ -16,6 +16,162 @@ const renderLibrary = (
);
describe('MapAssetLibrary', () => {
it('把全部来源呈现在同一个放置与应用工作流中', () => {
expect(decodeMapLibraryDragPayload('not-json')).toBeNull();
expect(decodeMapLibraryDragPayload('{"kind":"terrain","preset":"unknown"}')).toBeNull();
renderLibrary();
expect(screen.getByLabelText('地图资产库')).toHaveTextContent('统一地图资产库');
expect(screen.getByLabelText('地图资产库')).toHaveTextContent('放置 → 预览 → 一次应用');
});
it('显示已放置地图树并支持选择、删除和重复添加工程地图源', () => {
const onSelectMap = vi.fn();
const onRemoveMap = vi.fn();
const onAddProjectMap = vi.fn();
render(
<MapAssetLibrary
disabled={false}
terrainSize={8}
maps={[
{
descriptorPath: 'maps/warehouse/map.json',
schemaVersion: 1,
id: 'warehouse',
name: '仓库',
spawnPoints: [],
},
]}
placedMaps={[
{
id: 'map-a',
name: '仓库',
selection: { kind: 'project', descriptorPath: 'maps/warehouse/map.json' },
},
{
id: 'map-b',
name: '粗糙地形',
selection: {
kind: 'builtin',
config: {
preset: 'rough',
size: 8,
friction: 1,
positionX: 0,
positionY: 0,
yawDeg: 0,
slopeAngle: 12,
stairCount: 8,
obstacleCount: 10,
seed: 1,
terrainDifficulty: 0.5,
terrainHorizontalScale: 0.12,
terrainVerticalScale: 0.01,
},
},
},
]}
activeMapId="map-a"
onAdd={() => {}}
onAddProjectMap={onAddProjectMap}
onSelectTerrain={() => {}}
onSelectMap={onSelectMap}
onRemoveMap={onRemoveMap}
/>,
);
expect(screen.getByLabelText('场景资产树')).toHaveTextContent('2 个实例');
fireEvent.click(screen.getByRole('button', { name: '选择地图实例 粗糙地形' }));
fireEvent.click(screen.getByRole('button', { name: '删除地图实例 仓库' }));
fireEvent.click(screen.getByRole('button', { name: '放置工程地图 仓库' }));
expect(onSelectMap).toHaveBeenCalledWith('map-b');
expect(onRemoveMap).toHaveBeenCalledWith('map-a');
expect(onAddProjectMap).toHaveBeenCalledWith('maps/warehouse/map.json');
});
it('场景树为所有地图实例展示认证对象并可跨实例直接选择', () => {
const onSelectObject = vi.fn();
const editable = {
schemaVersion: 1 as const,
mapId: 'warehouse',
revision: 0,
objects: [
{
id: 'box-1',
name: '共享方盒',
type: 'box' as const,
pose: {
position: [0, 0, 0.5] as [number, number, number],
quaternion: [1, 0, 0, 0] as [number, number, number, number],
},
parameters: { sizeX: 1, sizeY: 1, sizeZ: 1 },
friction: [1, 0.005, 0.0001] as [number, number, number],
rgba: [0.5, 0.6, 0.7, 1] as [number, number, number, number],
placementMode: 'auto_ground' as const,
enabled: true,
},
],
spawnPoints: [],
};
render(
<MapAssetLibrary
disabled={false}
terrainSize={8}
documents={new Map([['maps/warehouse/map.json', editable]])}
placedMaps={[
{
id: 'map-a',
name: '仓库 A',
selection: { kind: 'project', descriptorPath: 'maps/warehouse/map.json' },
},
{
id: 'map-b',
name: '仓库 B',
selection: { kind: 'project', descriptorPath: 'maps/warehouse/map.json' },
},
]}
activeMapId="map-a"
onAdd={() => {}}
onSelectTerrain={() => {}}
onSelectObject={onSelectObject}
/>,
);
expect(screen.getByLabelText('场景资产树')).toHaveTextContent('2 个实例 · 2 个对象');
fireEvent.click(screen.getAllByRole('button', { name: /共享方盒/ })[1]);
expect(onSelectObject).toHaveBeenCalledWith('map-b', 'box-1');
});
it('工程地图源使用与资产卡片一致的拖放入口', () => {
const values = new Map<string, string>();
const dataTransfer = {
effectAllowed: 'none',
setData: (type: string, value: string) => values.set(type, value),
} as unknown as DataTransfer;
render(
<MapAssetLibrary
disabled={false}
terrainSize={8}
maps={[
{
descriptorPath: 'maps/warehouse/map.json',
schemaVersion: 1,
id: 'warehouse',
name: '仓库',
spawnPoints: [],
},
]}
onAdd={() => {}}
onSelectTerrain={() => {}}
/>,
);
const source = document.querySelector('[data-project-map="maps/warehouse/map.json"]')!;
expect(source).toHaveAttribute('draggable', 'true');
fireEvent.dragStart(source, { dataTransfer });
expect(decodeMapLibraryDragPayload(values.get(MAP_LIBRARY_DRAG_MIME) ?? '')).toEqual({
kind: 'project',
descriptorPath: 'maps/warehouse/map.json',
});
expect(values.get('text/plain')).toBe('仓库');
});
it('按所选放置方式添加认证资产', () => {
const onAdd = vi.fn();
renderLibrary(onAdd);
@@ -37,8 +193,11 @@ describe('MapAssetLibrary', () => {
target: { value: 'locked' },
});
fireEvent.dragStart(document.querySelector('[data-map-asset="ramp"]')!, { dataTransfer });
expect(values.get(MAP_ASSET_DRAG_MIME)).toBe('ramp');
expect(values.get(MAP_ASSET_PLACEMENT_MIME)).toBe('locked');
expect(decodeMapLibraryDragPayload(values.get(MAP_LIBRARY_DRAG_MIME) ?? '')).toEqual({
kind: 'certified',
type: 'ramp',
placementMode: 'locked',
});
});
it('以示意图卡片选择系统参数化地形', () => {
@@ -48,4 +207,69 @@ describe('MapAssetLibrary', () => {
fireEvent.click(screen.getByRole('button', { name: '添加随机粗糙地形' }));
expect(onSelectTerrain).toHaveBeenCalledWith('rough');
});
it('可把参数化地形拖到画布,并写入独立拖放类型', () => {
const values = new Map<string, string>();
const dataTransfer = {
effectAllowed: 'none',
setData: (type: string, value: string) => values.set(type, value),
} as unknown as DataTransfer;
renderLibrary();
const terrain = document.querySelector('[data-system-terrain="rough"]')!;
expect(terrain).toHaveAttribute('draggable', 'true');
fireEvent.dragStart(terrain, { dataTransfer });
expect(decodeMapLibraryDragPayload(values.get(MAP_LIBRARY_DRAG_MIME) ?? '')).toEqual({
kind: 'terrain',
preset: 'rough',
});
expect(values.get('text/plain')).toBe('随机粗糙地形');
});
it('集中显示认证资产与参数地形更改,并只触发一次场景编译', () => {
const onApplyScene = vi.fn();
const onDiscardScene = vi.fn();
render(
<MapAssetLibrary
disabled={false}
terrainSize={8}
pendingSceneChangeCount={3}
pendingSceneIds={['terrain-a']}
placedMaps={[
{
id: 'terrain-a',
name: '粗糙地形',
selection: {
kind: 'builtin',
config: {
preset: 'rough',
size: 8,
friction: 1,
positionX: 0,
positionY: 0,
yawDeg: 0,
slopeAngle: 12,
stairCount: 8,
obstacleCount: 10,
seed: 1,
terrainDifficulty: 0.5,
terrainHorizontalScale: 0.12,
terrainVerticalScale: 0.01,
},
},
},
]}
onAdd={() => {}}
onSelectTerrain={() => {}}
onApplyScene={onApplyScene}
onDiscardScene={onDiscardScene}
/>,
);
expect(screen.getByRole('status')).toHaveTextContent('3 项场景更改待应用');
expect(screen.getByRole('status')).toHaveTextContent('认证资产与参数地形共享同一草稿');
expect(screen.getByLabelText('场景资产树')).toHaveTextContent('待应用');
fireEvent.click(screen.getByRole('button', { name: '一次编译应用' }));
fireEvent.click(screen.getByRole('button', { name: '放弃场景更改' }));
expect(onApplyScene).toHaveBeenCalledTimes(1);
expect(onDiscardScene).toHaveBeenCalledTimes(1);
});
});