d1581892d7
- 素材重命名(跨范围契约):`rename_local_project_asset` 现在要求 `expectedProjectId` + `expectedProjectRevision`,前端按删除 / 分类保存同一套 CAS 口径补齐——先 `get_local_game_project_revision` 读当前 revision 再连同项目身份提交,不用可能过期的本地缓存值 - 失败文案统一:新增 `projectAssetCommandErrorMessage`,把 `project-identity-conflict` / `project-revision-conflict` 翻成用户可读中文,重命名 / 删除 / 分类保存三条链路共用一份映射,其余错误原样透出 - index.tsx:资源面板下载按 `resolveDownloadablePanelEntries` 过滤,版本卡这类合成 path 的条目不再进入落盘链路 - index.tsx:`characterAnimationPanel` 纳入画布浮层开关判据(抽 `resolveResourceCanvasFloatingPanelOpen`),「生成动画」面板与快速编辑 / 信息浮层共用同一条「点外部 / Esc 关闭」规则 - index.tsx:去掉 `ResourceBookScene` 上重复的 `onWheel`,滚轮只由 manager 上的原生 `passive:false` 监听处理,平移 / 缩放不再被翻倍 - index.tsx:快速编辑的源资源在层 id 命中不到时回落到已正规化的 `asset:<id>`,重试不再静默什么都不做;取不到时给出可见失败 - ResourceCanvasPanelView.tsx:下载按钮按可下载条数判可用、不再在上传中显示下载转圈;上传中禁掉 × / Esc / 遮罩三条关闭路径;预览占位文案抽 helper - GameRunVersionPicker.tsx:portal 菜单在滚动 / 缩放后重算位置;外部点击判定沿用共享 `useImageCanvasFloatingOptionDismiss` + `menuRef` 边界(review 该条已修,保留现状并补位置用例) - ResourceCanvasGenerationPanelView.tsx:`submitting` 改 `finally` 收回,成功路径不再永久锁住面板 - ResourceAssetDeleteDialog.tsx / ResourceRenameDialog.tsx / ResourceClassificationPanel.tsx:在飞时 `closeOnEscape` / `closeOnBackdrop` 与头部 × 一起挡住 - resourceEditModel.ts / resourceCanvasToolbarModel.ts / useProjectResourceCardPreviews.ts / projectResourceLiveUpdateModel.ts:抽 `resourceBaseName`、`canonicalProjectedResourceMediaType` 参数收窄去掉强转、删未使用参数、补「IO 失败会 reject」的签名说明 - resourceCanvasHistoryModel.ts:`resourceCanvasSnapshotsEqual` 不再比较不可恢复的 `manuallyPlaced`,消除「压进历史但撤销是空操作」 - resourceCanvasSectionMapping.ts:删掉从未接线、且注释与实现相矛盾的 `LEGACY_RESOURCE_CANVAS_SECTION_FALLBACK` - resourceCanvasChrome.css:`.game-resource-panel-grid` 补 `flex: 1 1 auto; min-height: 0`,卡片网格成为真正的滚动区 - 用例:重命名 CAS 载荷与读序、CAS 拒绝文案、版本卡下载门禁、上传中关闭路径、浮层判据、菜单跟随滚动、分区映射
199 lines
5.6 KiB
TypeScript
199 lines
5.6 KiB
TypeScript
// @vitest-environment jsdom
|
|
import {
|
|
cleanup,
|
|
fireEvent,
|
|
render,
|
|
screen,
|
|
within,
|
|
} from '@testing-library/react';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import {
|
|
isResourceCanvasExportable,
|
|
resolveDownloadablePanelEntries,
|
|
resolveResourceCanvasPanelEntries,
|
|
resolveSelectedPanelEntries,
|
|
} from '../src/features/resource-canvas/resourceCanvasAssetTransferModel';
|
|
import { ResourceCanvasPanelView } from '../src/features/resource-canvas/ResourceCanvasPanelView';
|
|
import type {
|
|
ProjectResource,
|
|
ProjectVersionResourceSummary,
|
|
} from '../src/view/project-development/resourceProjectionModel';
|
|
|
|
afterEach(cleanup);
|
|
|
|
function createResource(
|
|
overrides: Partial<ProjectResource> = {},
|
|
): ProjectResource {
|
|
return {
|
|
id: 'asset:hero',
|
|
category: 'character',
|
|
subtype: 'character',
|
|
label: '主角立绘',
|
|
path: 'assets/hero.png',
|
|
mediaType: 'image/png',
|
|
sourceLabel: '生成',
|
|
taskTitle: null,
|
|
manifestAssetId: 'hero',
|
|
producerTaskId: null,
|
|
externalResourceId: null,
|
|
referenceResourceIds: [],
|
|
dependencies: [],
|
|
dependencyDepth: 0,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
/** 虚拟版本条目:只有版本摘要与合成展示路径,没有对应文件。 */
|
|
function createVersionResource(): ProjectResource {
|
|
const version: ProjectVersionResourceSummary = {
|
|
versionId: 'version-1',
|
|
parentVersionId: null,
|
|
projectRevision: 3,
|
|
resourceBindings: [],
|
|
createdReason: 'initial',
|
|
createdAt: 1,
|
|
label: '版本 1',
|
|
childVersionIds: [],
|
|
};
|
|
return createResource({
|
|
id: 'version:version-1',
|
|
category: 'version',
|
|
subtype: 'version',
|
|
label: '项目版本 · 版本 1',
|
|
path: '',
|
|
mediaType: 'application/vnd.genarrative.project-version+json',
|
|
manifestAssetId: null,
|
|
version,
|
|
});
|
|
}
|
|
|
|
function panelEntries() {
|
|
return resolveResourceCanvasPanelEntries([
|
|
{
|
|
resource: createResource(),
|
|
categoryLabel: '角色与对象',
|
|
typeLabel: '图片',
|
|
previewIdentity: null,
|
|
previewStatus: 'idle',
|
|
previewSourceUrl: null,
|
|
previewError: null,
|
|
},
|
|
{
|
|
resource: createVersionResource(),
|
|
categoryLabel: '项目版本',
|
|
typeLabel: '版本',
|
|
previewIdentity: null,
|
|
previewStatus: 'idle',
|
|
previewSourceUrl: null,
|
|
previewError: null,
|
|
},
|
|
]);
|
|
}
|
|
|
|
describe('资源面板下载口径', () => {
|
|
it('虚拟版本条目不可下载:合成路径不许走到落盘链路', () => {
|
|
const entries = panelEntries();
|
|
const hero = entries[0]!;
|
|
const version = entries[1]!;
|
|
|
|
expect(isResourceCanvasExportable(hero)).toBe(true);
|
|
expect(hero.downloadable).toBe(true);
|
|
expect(version.downloadable).toBe(false);
|
|
|
|
// 下载目标只保留真能落盘的条目:版本卡的 path 是合成展示串,
|
|
// 传给 `save_local_project_asset_file` 只会失败。
|
|
expect(
|
|
resolveDownloadablePanelEntries(
|
|
resolveSelectedPanelEntries(entries, [
|
|
hero.resourceId,
|
|
version.resourceId,
|
|
]),
|
|
).map((entry) => entry.resourceId),
|
|
).toEqual([hero.resourceId]);
|
|
});
|
|
|
|
it('只选中不可下载条目时下载按钮保持禁用,不会变成点了没反应', () => {
|
|
const onDownloadSelection = vi.fn();
|
|
const entries = panelEntries();
|
|
const version = entries[1]!;
|
|
|
|
render(
|
|
<ResourceCanvasPanelView
|
|
entries={entries}
|
|
selectedResourceIds={[version.resourceId]}
|
|
onToggleEntry={vi.fn()}
|
|
onSelectAll={vi.fn()}
|
|
onClearSelection={vi.fn()}
|
|
onUploadFiles={vi.fn()}
|
|
onDownloadSelection={onDownloadSelection}
|
|
isUploading={false}
|
|
notice=""
|
|
onClose={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
const download = within(
|
|
screen.getByRole('dialog', { name: '资源面板' }),
|
|
).getByRole('button', { name: '下载' }) as HTMLButtonElement;
|
|
expect(download.disabled).toBe(true);
|
|
|
|
fireEvent.click(download);
|
|
expect(onDownloadSelection).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('可下载条目选中时按钮可用,并且上传中把三条关闭路径一起挡住', () => {
|
|
const onDownloadSelection = vi.fn();
|
|
const onClose = vi.fn();
|
|
const entries = panelEntries();
|
|
const hero = entries[0]!;
|
|
|
|
const { rerender } = render(
|
|
<ResourceCanvasPanelView
|
|
entries={entries}
|
|
selectedResourceIds={[hero.resourceId]}
|
|
onToggleEntry={vi.fn()}
|
|
onSelectAll={vi.fn()}
|
|
onClearSelection={vi.fn()}
|
|
onUploadFiles={vi.fn()}
|
|
onDownloadSelection={onDownloadSelection}
|
|
isUploading={false}
|
|
notice=""
|
|
onClose={onClose}
|
|
/>,
|
|
);
|
|
|
|
const panel = () => screen.getByRole('dialog', { name: '资源面板' });
|
|
fireEvent.click(within(panel()).getByRole('button', { name: '下载' }));
|
|
expect(onDownloadSelection).toHaveBeenCalledTimes(1);
|
|
|
|
rerender(
|
|
<ResourceCanvasPanelView
|
|
entries={entries}
|
|
selectedResourceIds={[hero.resourceId]}
|
|
onToggleEntry={vi.fn()}
|
|
onSelectAll={vi.fn()}
|
|
onClearSelection={vi.fn()}
|
|
onUploadFiles={vi.fn()}
|
|
onDownloadSelection={onDownloadSelection}
|
|
isUploading
|
|
notice=""
|
|
onClose={onClose}
|
|
/>,
|
|
);
|
|
|
|
// 上传在飞:× 禁用、Escape 与遮罩都不许把面板关掉(父级上传还在跑)。
|
|
expect(
|
|
(
|
|
within(panel()).getByRole('button', {
|
|
name: '关闭资源面板',
|
|
}) as HTMLButtonElement
|
|
).disabled,
|
|
).toBe(true);
|
|
fireEvent.keyDown(window, { key: 'Escape' });
|
|
expect(onClose).not.toHaveBeenCalled();
|
|
fireEvent.mouseDown(panel().parentElement as HTMLElement);
|
|
expect(onClose).not.toHaveBeenCalled();
|
|
});
|
|
});
|