抽出素材上传生命周期模型
扩展 ImageCanvasUploadModel 承载素材上传占位、进度、失败和持久化回写状态迁移 精简 useImageCanvasUploadWorkflow 中的资产与图层状态补丁逻辑 补充上传模型单测覆盖生命周期状态和图层绑定 更新 TRACKING.md 记录第四十六执行批次与验证结果
This commit is contained in:
@@ -7,9 +7,17 @@ import type {
|
||||
} from './ImageCanvasEditorTypes';
|
||||
import {
|
||||
applyGenerationReferenceUpload,
|
||||
applyMeasuredUploadAssetSize,
|
||||
applyPersistedUploadAsset,
|
||||
applyUploadAssetCreateFailure,
|
||||
applyUploadAssetCreatePending,
|
||||
applyUploadAssetReadFailure,
|
||||
applyUploadAssetReadSuccess,
|
||||
bindUploadLayerToPersistedAsset,
|
||||
createUploadCanvasLayer,
|
||||
createUploadedGenerationReference,
|
||||
createUploadingAssetPlaceholder,
|
||||
expandUploadFolder,
|
||||
resizeUploadCanvasLayerToImage,
|
||||
resolveUploadFolderId,
|
||||
setFailedGenerationIdle,
|
||||
@@ -47,6 +55,19 @@ function createLayer(overrides: Partial<CanvasLayer> = {}): CanvasLayer {
|
||||
};
|
||||
}
|
||||
|
||||
function createUploadingAsset(
|
||||
overrides: Partial<ReturnType<typeof createUploadingAssetPlaceholder>> = {},
|
||||
) {
|
||||
return {
|
||||
...createUploadingAssetPlaceholder({
|
||||
uploadIndex: 1,
|
||||
fileName: '上传图片.png',
|
||||
folderId: 'project',
|
||||
}),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function createDialog(
|
||||
overrides: Partial<GenerateDialogState> = {},
|
||||
): GenerateDialogState {
|
||||
@@ -109,6 +130,167 @@ describe('ImageCanvasUploadModel', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('opens the target upload folder without changing other folders', () => {
|
||||
expect(
|
||||
expandUploadFolder({
|
||||
folders: [
|
||||
createFolder({ id: 'project', collapsed: true }),
|
||||
createFolder({ id: 'characters', label: '角色素材', collapsed: true }),
|
||||
],
|
||||
folderId: 'characters',
|
||||
}),
|
||||
).toEqual([
|
||||
createFolder({ id: 'project', collapsed: true }),
|
||||
createFolder({ id: 'characters', label: '角色素材', collapsed: false }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('applies upload asset lifecycle patches', () => {
|
||||
const asset = createUploadingAsset();
|
||||
const otherAsset = createUploadingAsset({
|
||||
id: 'upload-other',
|
||||
label: '其他图片.png',
|
||||
});
|
||||
|
||||
expect(
|
||||
applyUploadAssetReadSuccess({
|
||||
assets: [asset, otherAsset],
|
||||
uploadAssetId: asset.id,
|
||||
imageSrc: 'data:image/png;base64,read',
|
||||
}),
|
||||
).toMatchObject([
|
||||
{
|
||||
id: 'upload-1',
|
||||
src: 'data:image/png;base64,read',
|
||||
uploadProgress: 42,
|
||||
uploadMessage: '读取图片',
|
||||
},
|
||||
{ id: 'upload-other', label: '其他图片.png' },
|
||||
]);
|
||||
|
||||
expect(
|
||||
applyUploadAssetCreatePending({
|
||||
assets: [asset],
|
||||
uploadAssetId: asset.id,
|
||||
}),
|
||||
).toMatchObject([
|
||||
{
|
||||
id: 'upload-1',
|
||||
uploadStatus: 'uploading',
|
||||
uploadProgress: 68,
|
||||
uploadMessage: '上传中',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
applyUploadAssetReadFailure({
|
||||
assets: [asset],
|
||||
uploadAssetId: asset.id,
|
||||
}),
|
||||
).toMatchObject([
|
||||
{
|
||||
id: 'upload-1',
|
||||
uploadStatus: 'failed',
|
||||
uploadProgress: 100,
|
||||
uploadMessage: '读取失败',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
applyUploadAssetCreateFailure({
|
||||
assets: [asset],
|
||||
uploadAssetId: asset.id,
|
||||
message: '请先登录',
|
||||
}),
|
||||
).toMatchObject([
|
||||
{
|
||||
id: 'upload-1',
|
||||
uploadStatus: 'failed',
|
||||
uploadProgress: 100,
|
||||
uploadMessage: '请先登录',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('replaces upload placeholders with persisted assets and clears upload state', () => {
|
||||
expect(
|
||||
applyPersistedUploadAsset({
|
||||
assets: [createUploadingAsset()],
|
||||
uploadAssetId: 'upload-1',
|
||||
persistedAsset: {
|
||||
assetId: 'asset-1',
|
||||
folderId: 'characters',
|
||||
label: '角色.png',
|
||||
imageSrc: 'data:image/png;base64,persisted',
|
||||
width: 1024,
|
||||
height: 768,
|
||||
objectKey: 'object-key',
|
||||
assetObjectId: 'asset-object',
|
||||
},
|
||||
}),
|
||||
).toEqual([
|
||||
{
|
||||
id: 'asset-1',
|
||||
label: '角色.png',
|
||||
src: 'data:image/png;base64,persisted',
|
||||
width: 1024,
|
||||
height: 768,
|
||||
folderId: 'characters',
|
||||
sourceKind: 'uploaded',
|
||||
sourceType: 'uploaded',
|
||||
persisted: true,
|
||||
objectKey: 'object-key',
|
||||
assetObjectId: 'asset-object',
|
||||
uploadStatus: undefined,
|
||||
uploadProgress: undefined,
|
||||
uploadMessage: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('updates measured asset size after the browser loads the uploaded image', () => {
|
||||
expect(
|
||||
applyMeasuredUploadAssetSize({
|
||||
assets: [createUploadingAsset()],
|
||||
uploadAssetId: 'upload-1',
|
||||
originalWidth: 1600,
|
||||
originalHeight: 900,
|
||||
}),
|
||||
).toMatchObject([
|
||||
{
|
||||
id: 'upload-1',
|
||||
width: 1600,
|
||||
height: 900,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('binds uploaded canvas layers to persisted asset metadata', () => {
|
||||
expect(
|
||||
bindUploadLayerToPersistedAsset({
|
||||
layers: [createLayer({ objectKey: 'local-object' })],
|
||||
layerId: 'layer-upload-1',
|
||||
persistedAsset: {
|
||||
assetId: 'asset-1',
|
||||
folderId: 'project',
|
||||
label: '上传图片.png',
|
||||
imageSrc: 'data:image/png;base64,persisted',
|
||||
width: 420,
|
||||
height: 315,
|
||||
objectKey: 'object-key',
|
||||
assetObjectId: 'asset-object',
|
||||
},
|
||||
}),
|
||||
).toMatchObject([
|
||||
{
|
||||
id: 'layer-upload-1',
|
||||
sourceAssetId: 'asset-1',
|
||||
objectKey: 'object-key',
|
||||
assetObjectId: 'asset-object',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('creates uploaded generation references with fallback labels', () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date('2026-06-17T12:00:00.000Z'));
|
||||
|
||||
Reference in New Issue
Block a user