All checks were successful
CI/CD Pipeline / VM Test - backend-integration (push) Successful in 7s
CI/CD Pipeline / VM Test - full-stack (push) Successful in 7s
CI/CD Pipeline / VM Test - performance (push) Successful in 7s
CI/CD Pipeline / VM Test - security (push) Successful in 7s
CI/CD Pipeline / Backend Linting (push) Successful in 2s
CI/CD Pipeline / Frontend Linting (push) Successful in 15s
CI/CD Pipeline / Nix Flake Check (push) Successful in 41s
CI/CD Pipeline / CI Summary (push) Successful in 1s
444 lines
10 KiB
TypeScript
444 lines
10 KiB
TypeScript
/**
|
|
* 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');
|
|
});
|
|
});
|