All checks were successful
CI/CD Pipeline / VM Test - backend-integration (push) Successful in 8s
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 16s
CI/CD Pipeline / Nix Flake Check (push) Successful in 38s
CI/CD Pipeline / CI Summary (push) Successful in 0s
281 lines
7.0 KiB
TypeScript
281 lines
7.0 KiB
TypeScript
/**
|
|
* Tests for Z-order (layering) operations
|
|
* Tests bring to front/back, forward/backward
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
import Konva from 'konva';
|
|
import {
|
|
bringToFront,
|
|
sendToBack,
|
|
bringForward,
|
|
sendBackward,
|
|
setZOrder,
|
|
getZOrder,
|
|
bulkBringToFront,
|
|
bulkSendToBack,
|
|
} from '$lib/canvas/operations/z-order';
|
|
|
|
describe('Z-Order Operations', () => {
|
|
let stage: Konva.Stage;
|
|
let layer: Konva.Layer;
|
|
let images: Map<string, Konva.Image>;
|
|
|
|
beforeEach(() => {
|
|
const container = document.createElement('div');
|
|
container.id = 'test-container';
|
|
document.body.appendChild(container);
|
|
|
|
stage = new Konva.Stage({
|
|
container: 'test-container',
|
|
width: 800,
|
|
height: 600,
|
|
});
|
|
|
|
layer = new Konva.Layer();
|
|
stage.add(layer);
|
|
|
|
images = new Map();
|
|
|
|
const imageElement = new Image();
|
|
imageElement.src =
|
|
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
|
|
|
|
['img1', 'img2', 'img3'].forEach((id, index) => {
|
|
const img = new Konva.Image({
|
|
image: imageElement,
|
|
width: 100,
|
|
height: 100,
|
|
});
|
|
|
|
img.zIndex(index);
|
|
layer.add(img);
|
|
images.set(id, img);
|
|
});
|
|
|
|
layer.draw();
|
|
});
|
|
|
|
afterEach(() => {
|
|
stage.destroy();
|
|
document.body.innerHTML = '';
|
|
});
|
|
|
|
describe('Bring to Front', () => {
|
|
it('brings image to highest Z-order', () => {
|
|
const img1 = images.get('img1')!;
|
|
|
|
bringToFront(img1, 'img1', images);
|
|
|
|
expect(img1.zIndex()).toBeGreaterThan(images.get('img2')!.zIndex());
|
|
expect(img1.zIndex()).toBeGreaterThan(images.get('img3')!.zIndex());
|
|
});
|
|
|
|
it('calls callback with new Z-order', () => {
|
|
const callback = vi.fn();
|
|
const img1 = images.get('img1')!;
|
|
|
|
bringToFront(img1, 'img1', images, { onZOrderChange: callback });
|
|
|
|
expect(callback).toHaveBeenCalled();
|
|
expect(callback.mock.calls[0][0]).toBe('img1');
|
|
expect(typeof callback.mock.calls[0][1]).toBe('number');
|
|
});
|
|
|
|
it('handles already front image', () => {
|
|
const img3 = images.get('img3')!;
|
|
const initialZ = img3.zIndex();
|
|
|
|
bringToFront(img3, 'img3', images);
|
|
|
|
expect(img3.zIndex()).toBeGreaterThanOrEqual(initialZ);
|
|
});
|
|
});
|
|
|
|
describe('Send to Back', () => {
|
|
it('sends image to Z-order 0', () => {
|
|
const img3 = images.get('img3')!;
|
|
|
|
sendToBack(img3, 'img3');
|
|
|
|
expect(img3.zIndex()).toBe(0);
|
|
});
|
|
|
|
it('calls callback', () => {
|
|
const callback = vi.fn();
|
|
const img3 = images.get('img3')!;
|
|
|
|
sendToBack(img3, 'img3', { onZOrderChange: callback });
|
|
|
|
expect(callback).toHaveBeenCalledWith('img3', 0);
|
|
});
|
|
|
|
it('handles already back image', () => {
|
|
const img1 = images.get('img1')!;
|
|
|
|
sendToBack(img1, 'img1');
|
|
|
|
expect(img1.zIndex()).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('Bring Forward', () => {
|
|
it('increases Z-order by 1', () => {
|
|
const img1 = images.get('img1')!;
|
|
const initialZ = img1.zIndex();
|
|
|
|
bringForward(img1, 'img1');
|
|
|
|
expect(img1.zIndex()).toBe(initialZ + 1);
|
|
});
|
|
|
|
it('can be called multiple times', () => {
|
|
const img1 = images.get('img1')!;
|
|
const initialZ = img1.zIndex();
|
|
|
|
bringForward(img1, 'img1');
|
|
bringForward(img1, 'img1');
|
|
bringForward(img1, 'img1');
|
|
|
|
expect(img1.zIndex()).toBe(initialZ + 3);
|
|
});
|
|
|
|
it('calls callback', () => {
|
|
const callback = vi.fn();
|
|
const img1 = images.get('img1')!;
|
|
|
|
bringForward(img1, 'img1', { onZOrderChange: callback });
|
|
|
|
expect(callback).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('Send Backward', () => {
|
|
it('decreases Z-order by 1', () => {
|
|
const img3 = images.get('img3')!;
|
|
const initialZ = img3.zIndex();
|
|
|
|
sendBackward(img3, 'img3');
|
|
|
|
expect(img3.zIndex()).toBe(initialZ - 1);
|
|
});
|
|
|
|
it('does not go below 0', () => {
|
|
const img1 = images.get('img1')!;
|
|
|
|
sendBackward(img1, 'img1');
|
|
sendBackward(img1, 'img1');
|
|
|
|
expect(img1.zIndex()).toBe(0);
|
|
});
|
|
|
|
it('calls callback', () => {
|
|
const callback = vi.fn();
|
|
const img3 = images.get('img3')!;
|
|
|
|
sendBackward(img3, 'img3', { onZOrderChange: callback });
|
|
|
|
expect(callback).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('Set Z-Order', () => {
|
|
it('sets specific Z-order', () => {
|
|
const img1 = images.get('img1')!;
|
|
|
|
setZOrder(img1, 'img1', 10);
|
|
|
|
expect(img1.zIndex()).toBe(10);
|
|
});
|
|
|
|
it('does not allow negative Z-order', () => {
|
|
const img1 = images.get('img1')!;
|
|
|
|
setZOrder(img1, 'img1', -5);
|
|
|
|
expect(img1.zIndex()).toBe(0);
|
|
});
|
|
|
|
it('calls callback', () => {
|
|
const callback = vi.fn();
|
|
const img1 = images.get('img1')!;
|
|
|
|
setZOrder(img1, 'img1', 5, { onZOrderChange: callback });
|
|
|
|
expect(callback).toHaveBeenCalledWith('img1', 5);
|
|
});
|
|
});
|
|
|
|
describe('Get Z-Order', () => {
|
|
it('returns current Z-order', () => {
|
|
const img2 = images.get('img2')!;
|
|
|
|
expect(getZOrder(img2)).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('Bulk Z-Order Operations', () => {
|
|
it('bulk brings multiple images to front', () => {
|
|
bulkBringToFront(images, ['img1', 'img2'], images);
|
|
|
|
const img1Z = images.get('img1')!.zIndex();
|
|
const img2Z = images.get('img2')!.zIndex();
|
|
const img3Z = images.get('img3')!.zIndex();
|
|
|
|
expect(img1Z).toBeGreaterThan(img3Z);
|
|
expect(img2Z).toBeGreaterThan(img3Z);
|
|
});
|
|
|
|
it('bulk sends multiple images to back', () => {
|
|
bulkSendToBack(images, ['img2', 'img3'], images);
|
|
|
|
expect(images.get('img2')!.zIndex()).toBeLessThan(images.get('img1')!.zIndex());
|
|
expect(images.get('img3')!.zIndex()).toBeLessThan(images.get('img1')!.zIndex());
|
|
});
|
|
|
|
it('maintains relative order in bulk operations', () => {
|
|
bulkBringToFront(images, ['img1', 'img2'], images);
|
|
|
|
const img1Z = images.get('img1')!.zIndex();
|
|
const img2Z = images.get('img2')!.zIndex();
|
|
|
|
// img2 should be in front of img1 (relative order preserved)
|
|
expect(img2Z).toBeGreaterThan(img1Z);
|
|
});
|
|
|
|
it('calls callback for each image in bulk operation', () => {
|
|
const callback = vi.fn();
|
|
|
|
bulkBringToFront(images, ['img1', 'img2'], images, { onZOrderChange: callback });
|
|
|
|
expect(callback).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|
|
|
|
describe('Edge Cases', () => {
|
|
it('handles large Z-order values', () => {
|
|
const img1 = images.get('img1')!;
|
|
|
|
setZOrder(img1, 'img1', 999999);
|
|
|
|
expect(img1.zIndex()).toBe(999999);
|
|
});
|
|
|
|
it('handles empty selection in bulk operations', () => {
|
|
bulkBringToFront(images, [], images);
|
|
bulkSendToBack(images, [], images);
|
|
|
|
// Should not throw
|
|
});
|
|
|
|
it('handles single image in bulk operations', () => {
|
|
const img1 = images.get('img1')!;
|
|
const initialZ = img1.zIndex();
|
|
|
|
bulkBringToFront(images, ['img1'], images);
|
|
|
|
expect(img1.zIndex()).toBeGreaterThan(initialZ);
|
|
});
|
|
});
|
|
});
|