import { describe, expect, it } from 'vitest'; import { createCanvasLayerClipboard, duplicateCanvasLayers, flipCanvasLayers, getCanvasLayersByIds, groupCanvasLayers, moveCanvasLayers, removeCanvasLayers, resolveContextTargetLayerIds, toggleCanvasLayersLock, toggleCanvasLayersVisibility, ungroupCanvasLayers, } from './ImageCanvasLayerCommandModel'; import type { CanvasContextMenuState, CanvasLayer, } from './ImageCanvasEditorTypes'; function createLayer(overrides: Partial): 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 }); 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: 'local-resource-copy-test-0', title: '第一张 副本', x: 300, y: 200, zIndex: 5, groupId: null, }, { id: 'layer-copy-test-1', resourceId: 'local-resource-copy-test-1', 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); }); 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(); }); });