Files
Genarrative/src/components/image-editor/ImageCanvasLayerCommandModel.test.ts
T
kdletters 3f47b2df87
Project CI / Native shell tests (push) Successful in 12m30s
Project CI / Repository checks (push) Successful in 1m3s
Project CI / Backend tests (push) Successful in 3m40s
Project CI / Frontend tests (push) Successful in 2m58s
修复master现有CI基线失败
整理六处前端导入顺序并移除未使用类型导入

同步Project Supervisor测试断言到权威Prompt Bundle文案
2026-08-04 21:37:12 +08:00

268 lines
7.9 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import type {
CanvasContextMenuState,
CanvasLayer,
} from './ImageCanvasEditorTypes';
import {
canCopyCanvasLayers,
createCanvasLayerClipboard,
duplicateCanvasLayers,
flipCanvasLayers,
getCanvasLayerCopyBlockReason,
getCanvasLayersByIds,
groupCanvasLayers,
moveCanvasLayers,
removeCanvasLayers,
resolveContextTargetLayerIds,
toggleCanvasLayersLock,
toggleCanvasLayersVisibility,
ungroupCanvasLayers,
} from './ImageCanvasLayerCommandModel';
function createLayer(overrides: Partial<CanvasLayer>): CanvasLayer {
const id = overrides.id ?? 'layer-a';
return {
id,
resourceId: `resource-${id}`,
title: id,
src: `data:image/png;base64,${id}`,
x: 10,
y: 20,
width: 100,
height: 80,
originalWidth: 100,
originalHeight: 80,
zIndex: 1,
sourceType: 'uploaded',
...overrides,
};
}
describe('ImageCanvasLayerCommandModel', () => {
it('resolves context menu targets from the current multi-selection', () => {
const menu: CanvasContextMenuState = {
kind: 'layer',
x: 0,
y: 0,
layerId: 'layer-b',
canvasPoint: { x: 10, y: 20 },
};
expect(resolveContextTargetLayerIds(menu, ['layer-a', 'layer-b'])).toEqual([
'layer-a',
'layer-b',
]);
expect(resolveContextTargetLayerIds(menu, ['layer-a'])).toEqual([
'layer-b',
]);
expect(
resolveContextTargetLayerIds(
{ kind: 'blank', x: 0, y: 0, canvasPoint: { x: 0, y: 0 } },
['layer-a'],
),
).toEqual([]);
});
it('duplicates layers to a canvas point while preserving relative offsets', () => {
const first = createLayer({
id: 'first',
title: '第一张',
x: 20,
y: 30,
resourceAssetKind: 'character',
assetKindOverride: 'icon',
assetKind: 'icon',
});
const second = createLayer({
id: 'second',
title: '第二张',
x: 70,
y: 90,
zIndex: 4,
groupId: 'group-old',
});
const duplicated = duplicateCanvasLayers({
sourceLayers: [first, second],
allLayers: [first, second],
canvasPoint: { x: 300, y: 200 },
stamp: 'test',
});
expect(duplicated).toMatchObject([
{
id: 'layer-copy-test-0',
resourceId: 'resource-first',
title: '第一张 副本',
x: 300,
y: 200,
zIndex: 5,
groupId: null,
},
{
id: 'layer-copy-test-1',
resourceId: 'resource-second',
title: '第二张 副本',
x: 350,
y: 260,
zIndex: 6,
groupId: null,
},
]);
const cutPaste = duplicateCanvasLayers({
sourceLayers: [first],
allLayers: [first, second],
renameCopies: false,
stamp: 'cut',
});
expect(cutPaste[0]?.title).toBe('第一张');
expect(cutPaste[0]?.x).toBe(first.x + 32);
expect(cutPaste[0]?.resourceId).toBe(first.resourceId);
expect(cutPaste[0]?.assetKindOverride).toBe('icon');
});
it('only allows copy semantics after every resource is registered', () => {
const readyLayer = createLayer({ resourceId: 'resource-ready' });
const pendingLayer = createLayer({
id: 'pending',
resourceId: 'local-resource-pending',
resourcePersistenceState: 'pending',
});
const selfContainedLayer = createLayer({
id: 'historical-sequence',
resourceId: 'local-resource-historical-sequence',
resourcePersistenceState: 'self-contained-local',
mediaType: 'image-sequence',
imageSequenceFrames: [
{
frameIndex: 1,
imageSrc: '/historical/frame-1.png',
width: 100,
height: 80,
},
],
});
const unresolvedLayer = createLayer({
id: 'unresolved',
resourceId: 'local-resource-unresolved',
resourcePersistenceState: 'unresolved-local',
});
expect(canCopyCanvasLayers([readyLayer])).toBe(true);
expect(canCopyCanvasLayers([readyLayer, pendingLayer])).toBe(false);
expect(getCanvasLayerCopyBlockReason([pendingLayer])).toBe(
'pending-resource',
);
expect(getCanvasLayerCopyBlockReason([selfContainedLayer])).toBe(
'self-contained-local-unsupported',
);
expect(getCanvasLayerCopyBlockReason([unresolvedLayer])).toBe(
'unresolved-resource',
);
expect(
duplicateCanvasLayers({
sourceLayers: [pendingLayer],
allLayers: [pendingLayer],
stamp: 'pending',
}),
).toEqual([]);
expect(
createCanvasLayerClipboard([pendingLayer], [pendingLayer.id], 'copy'),
).toBeNull();
});
it('creates a cloned clipboard and removes target layers', () => {
const layers = [
createLayer({ id: 'first' }),
createLayer({
id: 'second',
generationInputs: { fields: [], references: [] },
}),
];
const clipboard = createCanvasLayerClipboard(layers, ['second'], 'copy');
expect(clipboard?.mode).toBe('copy');
expect(clipboard?.layers).toEqual([layers[1]]);
expect(clipboard?.layers[0]).not.toBe(layers[1]);
expect(getCanvasLayersByIds(layers, ['first'])).toEqual([layers[0]]);
expect(removeCanvasLayers(layers, ['first'])).toEqual([layers[1]]);
});
it('moves layer z-indexes with the same commands as the context menu', () => {
const layers = [
createLayer({ id: 'bottom', zIndex: 1 }),
createLayer({ id: 'middle', zIndex: 3 }),
createLayer({ id: 'top', zIndex: 8 }),
];
expect(moveCanvasLayers(layers, ['middle'], 'up')[1]?.zIndex).toBe(4);
expect(moveCanvasLayers(layers, ['middle'], 'down')[1]?.zIndex).toBe(2);
expect(moveCanvasLayers(layers, ['bottom', 'middle'], 'top')).toMatchObject(
[
{ id: 'bottom', zIndex: 9 },
{ id: 'middle', zIndex: 10 },
{ id: 'top', zIndex: 8 },
],
);
expect(
moveCanvasLayers(layers, ['bottom', 'middle'], 'bottom'),
).toMatchObject([
{ id: 'bottom', zIndex: -2 },
{ id: 'middle', zIndex: -1 },
{ id: 'top', zIndex: 8 },
]);
});
it('groups, ungroups, toggles visibility and lock, and flips layers', () => {
const layers = [
createLayer({ id: 'first', hidden: true, locked: true }),
createLayer({ id: 'second' }),
createLayer({ id: 'third' }),
];
const targetIds = ['first', 'second'];
const groupedLayers = groupCanvasLayers(layers, targetIds, 'group-next');
expect(groupedLayers[0]?.groupId).toBe('group-next');
expect(groupedLayers[1]?.groupId).toBe('group-next');
expect(groupedLayers[2]?.groupId).toBeUndefined();
const ungroupedLayers = ungroupCanvasLayers(groupedLayers, targetIds);
expect(ungroupedLayers[0]?.groupId).toBeNull();
expect(ungroupedLayers[1]?.groupId).toBeNull();
expect(ungroupedLayers[2]?.groupId).toBeUndefined();
const hiddenLayers = toggleCanvasLayersVisibility(layers, targetIds);
expect(hiddenLayers[0]?.hidden).toBe(true);
expect(hiddenLayers[1]?.hidden).toBe(true);
expect(hiddenLayers[2]?.hidden).toBeUndefined();
const shownLayers = toggleCanvasLayersVisibility(
[
createLayer({ id: 'first', hidden: true }),
createLayer({ id: 'second', hidden: true }),
],
targetIds,
);
expect(shownLayers[0]?.hidden).toBe(false);
expect(shownLayers[1]?.hidden).toBe(false);
const lockedLayers = toggleCanvasLayersLock(layers, targetIds);
expect(lockedLayers[0]?.locked).toBe(true);
expect(lockedLayers[1]?.locked).toBe(true);
expect(lockedLayers[2]?.locked).toBeUndefined();
const flippedXLayers = flipCanvasLayers(layers, targetIds, 'x');
expect(flippedXLayers[0]?.flipX).toBe(true);
expect(flippedXLayers[1]?.flipX).toBe(true);
expect(flippedXLayers[2]?.flipX).toBeUndefined();
const flippedYLayers = flipCanvasLayers(layers, targetIds, 'y');
expect(flippedYLayers[0]?.flipY).toBe(true);
expect(flippedYLayers[1]?.flipY).toBe(true);
expect(flippedYLayers[2]?.flipY).toBeUndefined();
});
});