/** * Tests for clipboard operations (copy, cut, paste) * Tests clipboard store and operations */ import { describe, it, expect, beforeEach } from 'vitest'; import { get } from 'svelte/store'; import { clipboard, hasClipboardContent, clipboardCount, isCutOperation, } from '$lib/stores/clipboard'; import type { ClipboardImageData } from '$lib/stores/clipboard'; import { copySelectedImages, copyImages, copySingleImage, hasClipboardContent as hasContent, getClipboardCount, } from '$lib/canvas/clipboard/copy'; import { cutSelectedImages, cutImages, cutSingleImage } from '$lib/canvas/clipboard/cut'; import { pasteFromClipboard, pasteAtPosition, canPaste, getPastePreview, } from '$lib/canvas/clipboard/paste'; import { selection } from '$lib/stores/selection'; import { viewport } from '$lib/stores/viewport'; describe('Clipboard Store', () => { beforeEach(() => { clipboard.clear(); selection.clearSelection(); }); it('starts empty', () => { const state = get(clipboard); expect(state.images).toEqual([]); expect(state.operation).toBeNull(); }); it('stores copied images', () => { const images: ClipboardImageData[] = [ { boardImageId: 'bi1', imageId: 'img1', position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }, ]; clipboard.copy(images); const state = get(clipboard); expect(state.images).toHaveLength(1); expect(state.operation).toBe('copy'); }); it('stores cut images', () => { const images: ClipboardImageData[] = [ { boardImageId: 'bi1', imageId: 'img1', position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }, ]; clipboard.cut(images); const state = get(clipboard); expect(state.images).toHaveLength(1); expect(state.operation).toBe('cut'); }); it('clears clipboard', () => { const images: ClipboardImageData[] = [ { boardImageId: 'bi1', imageId: 'img1', position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }, ]; clipboard.copy(images); clipboard.clear(); const state = get(clipboard); expect(state.images).toEqual([]); expect(state.operation).toBeNull(); }); it('hasClipboardContent reflects state', () => { expect(get(hasClipboardContent)).toBe(false); clipboard.copy([ { boardImageId: 'bi1', imageId: 'img1', position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }, ]); expect(get(hasClipboardContent)).toBe(true); }); it('clipboardCount reflects count', () => { expect(get(clipboardCount)).toBe(0); clipboard.copy([ { boardImageId: 'bi1', imageId: 'img1', position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }, { boardImageId: 'bi2', imageId: 'img2', position: { x: 200, y: 200 }, transformations: {}, zOrder: 1, }, ]); expect(get(clipboardCount)).toBe(2); }); it('isCutOperation reflects operation type', () => { clipboard.copy([ { boardImageId: 'bi1', imageId: 'img1', position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }, ]); expect(get(isCutOperation)).toBe(false); clipboard.cut([ { boardImageId: 'bi1', imageId: 'img1', position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }, ]); expect(get(isCutOperation)).toBe(true); }); }); describe('Copy Operations', () => { beforeEach(() => { clipboard.clear(); selection.clearSelection(); }); it('copies selected images', () => { selection.selectMultiple(['img1', 'img2']); const getImageData = (id: string): ClipboardImageData | null => ({ boardImageId: `bi-${id}`, imageId: id, position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }); const count = copySelectedImages(getImageData); expect(count).toBe(2); expect(get(clipboardCount)).toBe(2); expect(get(isCutOperation)).toBe(false); }); it('copies specific images', () => { const getImageData = (id: string): ClipboardImageData | null => ({ boardImageId: `bi-${id}`, imageId: id, position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }); const count = copyImages(['img1', 'img2', 'img3'], getImageData); expect(count).toBe(3); expect(get(clipboardCount)).toBe(3); }); it('copies single image', () => { const getImageData = (id: string): ClipboardImageData | null => ({ boardImageId: `bi-${id}`, imageId: id, position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }); const success = copySingleImage(getImageData, 'img1'); expect(success).toBe(true); expect(get(clipboardCount)).toBe(1); }); it('returns 0 when copying empty selection', () => { const getImageData = (): ClipboardImageData | null => null; const count = copySelectedImages(getImageData); expect(count).toBe(0); }); it('hasClipboardContent returns true after copy', () => { const getImageData = (id: string): ClipboardImageData | null => ({ boardImageId: `bi-${id}`, imageId: id, position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }); copyImages(['img1'], getImageData); expect(hasContent()).toBe(true); expect(getClipboardCount()).toBe(1); }); }); describe('Cut Operations', () => { beforeEach(() => { clipboard.clear(); selection.clearSelection(); }); it('cuts selected images', () => { selection.selectMultiple(['img1', 'img2']); const getImageData = (id: string): ClipboardImageData | null => ({ boardImageId: `bi-${id}`, imageId: id, position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }); const count = cutSelectedImages(getImageData); expect(count).toBe(2); expect(get(clipboardCount)).toBe(2); expect(get(isCutOperation)).toBe(true); }); it('cuts specific images', () => { const getImageData = (id: string): ClipboardImageData | null => ({ boardImageId: `bi-${id}`, imageId: id, position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }); const count = cutImages(['img1', 'img2'], getImageData); expect(count).toBe(2); expect(get(isCutOperation)).toBe(true); }); it('cuts single image', () => { const getImageData = (id: string): ClipboardImageData | null => ({ boardImageId: `bi-${id}`, imageId: id, position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }); const success = cutSingleImage(getImageData, 'img1'); expect(success).toBe(true); expect(get(clipboardCount)).toBe(1); expect(get(isCutOperation)).toBe(true); }); }); describe('Paste Operations', () => { beforeEach(() => { clipboard.clear(); viewport.reset(); }); it('pastes images at viewport center', () => { clipboard.copy([ { boardImageId: 'bi1', imageId: 'img1', position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }, { boardImageId: 'bi2', imageId: 'img2', position: { x: 200, y: 100 }, transformations: {}, zOrder: 1, }, ]); const pasted = pasteFromClipboard(800, 600); expect(pasted).toHaveLength(2); expect(pasted[0].newPosition).toBeDefined(); expect(pasted[1].newPosition).toBeDefined(); }); it('pastes at specific position', () => { clipboard.copy([ { boardImageId: 'bi1', imageId: 'img1', position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }, ]); const pasted = pasteAtPosition(500, 500); expect(pasted).toHaveLength(1); expect(pasted[0].newPosition.x).toBe(500); expect(pasted[0].newPosition.y).toBe(500); }); it('preserves relative positions when pasting', () => { clipboard.copy([ { boardImageId: 'bi1', imageId: 'img1', position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }, { boardImageId: 'bi2', imageId: 'img2', position: { x: 200, y: 150 }, transformations: {}, zOrder: 1, }, ]); const pasted = pasteAtPosition(0, 0); // Relative distance should be preserved const deltaX1 = pasted[0].newPosition.x; const deltaX2 = pasted[1].newPosition.x; expect(deltaX2 - deltaX1).toBe(100); // Original was 200 - 100 = 100 }); it('clears clipboard after cut paste', () => { clipboard.cut([ { boardImageId: 'bi1', imageId: 'img1', position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }, ]); pasteAtPosition(200, 200); expect(get(hasClipboardContent)).toBe(false); }); it('preserves clipboard after copy paste', () => { clipboard.copy([ { boardImageId: 'bi1', imageId: 'img1', position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }, ]); pasteAtPosition(200, 200); expect(get(hasClipboardContent)).toBe(true); }); it('returns empty array when pasting empty clipboard', () => { const pasted = pasteFromClipboard(800, 600); expect(pasted).toEqual([]); }); it('canPaste reflects clipboard state', () => { expect(canPaste()).toBe(false); clipboard.copy([ { boardImageId: 'bi1', imageId: 'img1', position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }, ]); expect(canPaste()).toBe(true); }); it('getPastePreview shows where images will be pasted', () => { clipboard.copy([ { boardImageId: 'bi1', imageId: 'img1', position: { x: 100, y: 100 }, transformations: {}, zOrder: 0, }, ]); const preview = getPastePreview(800, 600); expect(preview).toHaveLength(1); expect(preview[0]).toHaveProperty('x'); expect(preview[0]).toHaveProperty('y'); }); });