phase 12
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
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
This commit is contained in:
460
frontend/tests/canvas/align.test.ts
Normal file
460
frontend/tests/canvas/align.test.ts
Normal file
@@ -0,0 +1,460 @@
|
||||
/**
|
||||
* Tests for alignment and distribution operations
|
||||
* Tests align (top/bottom/left/right/center) and distribute
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import Konva from 'konva';
|
||||
import {
|
||||
alignTop,
|
||||
alignBottom,
|
||||
alignLeft,
|
||||
alignRight,
|
||||
centerHorizontal,
|
||||
centerVertical,
|
||||
centerBoth,
|
||||
} from '$lib/canvas/operations/align';
|
||||
import { distributeHorizontal, distributeVertical } from '$lib/canvas/operations/distribute';
|
||||
import { grid, snapToGrid, drawGrid, removeGrid, updateGrid } from '$lib/canvas/grid';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
describe('Alignment 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==';
|
||||
|
||||
// Create 3 images at different positions
|
||||
const positions = [
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 300, y: 150 },
|
||||
{ x: 200, y: 250 },
|
||||
];
|
||||
|
||||
positions.forEach((pos, index) => {
|
||||
const img = new Konva.Image({
|
||||
image: imageElement,
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
|
||||
layer.add(img);
|
||||
images.set(`img${index + 1}`, img);
|
||||
});
|
||||
|
||||
layer.draw();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stage.destroy();
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
describe('Align Top', () => {
|
||||
it('aligns images to topmost edge', () => {
|
||||
alignTop(images, ['img1', 'img2', 'img3']);
|
||||
|
||||
const img1Y = images.get('img1')!.getClientRect().y;
|
||||
const img2Y = images.get('img2')!.getClientRect().y;
|
||||
const img3Y = images.get('img3')!.getClientRect().y;
|
||||
|
||||
expect(img1Y).toBeCloseTo(img2Y);
|
||||
expect(img1Y).toBeCloseTo(img3Y);
|
||||
expect(img1Y).toBe(100); // Should align to img1 (topmost)
|
||||
});
|
||||
|
||||
it('calls callback on completion', () => {
|
||||
const callback = vi.fn();
|
||||
|
||||
alignTop(images, ['img1', 'img2'], { onAlignComplete: callback });
|
||||
|
||||
expect(callback).toHaveBeenCalledWith(['img1', 'img2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Align Bottom', () => {
|
||||
it('aligns images to bottommost edge', () => {
|
||||
alignBottom(images, ['img1', 'img2', 'img3']);
|
||||
|
||||
const img1Bottom = images.get('img1')!.getClientRect().y + 100;
|
||||
const img2Bottom = images.get('img2')!.getClientRect().y + 100;
|
||||
const img3Bottom = images.get('img3')!.getClientRect().y + 100;
|
||||
|
||||
expect(img1Bottom).toBeCloseTo(img2Bottom);
|
||||
expect(img1Bottom).toBeCloseTo(img3Bottom);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Align Left', () => {
|
||||
it('aligns images to leftmost edge', () => {
|
||||
alignLeft(images, ['img1', 'img2', 'img3']);
|
||||
|
||||
const img1X = images.get('img1')!.getClientRect().x;
|
||||
const img2X = images.get('img2')!.getClientRect().x;
|
||||
const img3X = images.get('img3')!.getClientRect().x;
|
||||
|
||||
expect(img1X).toBeCloseTo(img2X);
|
||||
expect(img1X).toBeCloseTo(img3X);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Align Right', () => {
|
||||
it('aligns images to rightmost edge', () => {
|
||||
alignRight(images, ['img1', 'img2', 'img3']);
|
||||
|
||||
const img1Right = images.get('img1')!.getClientRect().x + 100;
|
||||
const img2Right = images.get('img2')!.getClientRect().x + 100;
|
||||
const img3Right = images.get('img3')!.getClientRect().x + 100;
|
||||
|
||||
expect(img1Right).toBeCloseTo(img2Right);
|
||||
expect(img1Right).toBeCloseTo(img3Right);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Center Horizontal', () => {
|
||||
it('centers images horizontally within selection bounds', () => {
|
||||
centerHorizontal(images, ['img1', 'img2', 'img3']);
|
||||
|
||||
const img1CenterX = images.get('img1')!.getClientRect().x + 50;
|
||||
const img2CenterX = images.get('img2')!.getClientRect().x + 50;
|
||||
const img3CenterX = images.get('img3')!.getClientRect().x + 50;
|
||||
|
||||
expect(img1CenterX).toBeCloseTo(img2CenterX);
|
||||
expect(img1CenterX).toBeCloseTo(img3CenterX);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Center Vertical', () => {
|
||||
it('centers images vertically within selection bounds', () => {
|
||||
centerVertical(images, ['img1', 'img2', 'img3']);
|
||||
|
||||
const img1CenterY = images.get('img1')!.getClientRect().y + 50;
|
||||
const img2CenterY = images.get('img2')!.getClientRect().y + 50;
|
||||
const img3CenterY = images.get('img3')!.getClientRect().y + 50;
|
||||
|
||||
expect(img1CenterY).toBeCloseTo(img2CenterY);
|
||||
expect(img1CenterY).toBeCloseTo(img3CenterY);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Center Both', () => {
|
||||
it('centers images both horizontally and vertically', () => {
|
||||
centerBoth(images, ['img1', 'img2', 'img3']);
|
||||
|
||||
const img1Center = {
|
||||
x: images.get('img1')!.getClientRect().x + 50,
|
||||
y: images.get('img1')!.getClientRect().y + 50,
|
||||
};
|
||||
|
||||
const img2Center = {
|
||||
x: images.get('img2')!.getClientRect().x + 50,
|
||||
y: images.get('img2')!.getClientRect().y + 50,
|
||||
};
|
||||
|
||||
expect(img1Center.x).toBeCloseTo(img2Center.x);
|
||||
expect(img1Center.y).toBeCloseTo(img2Center.y);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('handles single image gracefully', () => {
|
||||
alignTop(images, ['img1']);
|
||||
|
||||
// Should not throw, position may change
|
||||
expect(images.get('img1')!.y()).toBeDefined();
|
||||
});
|
||||
|
||||
it('handles empty selection', () => {
|
||||
alignTop(images, []);
|
||||
alignLeft(images, []);
|
||||
|
||||
// Should not throw
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Distribution 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==';
|
||||
|
||||
// Create 4 images for distribution
|
||||
[100, 200, 500, 600].forEach((x, index) => {
|
||||
const img = new Konva.Image({
|
||||
image: imageElement,
|
||||
x,
|
||||
y: 100,
|
||||
width: 50,
|
||||
height: 50,
|
||||
});
|
||||
|
||||
layer.add(img);
|
||||
images.set(`img${index + 1}`, img);
|
||||
});
|
||||
|
||||
layer.draw();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stage.destroy();
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
describe('Distribute Horizontal', () => {
|
||||
it('distributes images with equal horizontal spacing', () => {
|
||||
distributeHorizontal(images, ['img1', 'img2', 'img3', 'img4']);
|
||||
|
||||
// Get positions after distribution
|
||||
const positions = ['img1', 'img2', 'img3', 'img4'].map(
|
||||
(id) => images.get(id)!.getClientRect().x
|
||||
);
|
||||
|
||||
// Check that spacing between consecutive images is equal
|
||||
const spacing1 = positions[1] - (positions[0] + 50);
|
||||
const spacing2 = positions[2] - (positions[1] + 50);
|
||||
|
||||
expect(spacing1).toBeCloseTo(spacing2, 1);
|
||||
});
|
||||
|
||||
it('preserves first and last image positions', () => {
|
||||
const firstX = images.get('img1')!.x();
|
||||
const lastX = images.get('img4')!.x();
|
||||
|
||||
distributeHorizontal(images, ['img1', 'img2', 'img3', 'img4']);
|
||||
|
||||
expect(images.get('img1')!.x()).toBe(firstX);
|
||||
expect(images.get('img4')!.x()).toBe(lastX);
|
||||
});
|
||||
|
||||
it('does nothing with less than 3 images', () => {
|
||||
const img1X = images.get('img1')!.x();
|
||||
const img2X = images.get('img2')!.x();
|
||||
|
||||
distributeHorizontal(images, ['img1', 'img2']);
|
||||
|
||||
expect(images.get('img1')!.x()).toBe(img1X);
|
||||
expect(images.get('img2')!.x()).toBe(img2X);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Distribute Vertical', () => {
|
||||
it('distributes images with equal vertical spacing', () => {
|
||||
// Reposition images vertically
|
||||
images.get('img1')!.y(100);
|
||||
images.get('img2')!.y(200);
|
||||
images.get('img3')!.y(400);
|
||||
images.get('img4')!.y(500);
|
||||
|
||||
distributeVertical(images, ['img1', 'img2', 'img3', 'img4']);
|
||||
|
||||
// Get positions after distribution
|
||||
const positions = ['img1', 'img2', 'img3', 'img4'].map(
|
||||
(id) => images.get(id)!.getClientRect().y
|
||||
);
|
||||
|
||||
// Check spacing
|
||||
const spacing1 = positions[1] - (positions[0] + 50);
|
||||
const spacing2 = positions[2] - (positions[1] + 50);
|
||||
|
||||
expect(spacing1).toBeCloseTo(spacing2, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Grid Functionality', () => {
|
||||
let stage: Konva.Stage;
|
||||
let layer: Konva.Layer;
|
||||
|
||||
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);
|
||||
layer.draw();
|
||||
|
||||
grid.reset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stage.destroy();
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
describe('Grid Store', () => {
|
||||
it('starts with default settings', () => {
|
||||
const settings = get(grid);
|
||||
|
||||
expect(settings.enabled).toBe(true);
|
||||
expect(settings.size).toBe(20);
|
||||
expect(settings.visible).toBe(false);
|
||||
expect(settings.snapEnabled).toBe(false);
|
||||
});
|
||||
|
||||
it('toggles visibility', () => {
|
||||
grid.toggleVisible();
|
||||
expect(get(grid).visible).toBe(true);
|
||||
|
||||
grid.toggleVisible();
|
||||
expect(get(grid).visible).toBe(false);
|
||||
});
|
||||
|
||||
it('toggles snap', () => {
|
||||
grid.toggleSnap();
|
||||
expect(get(grid).snapEnabled).toBe(true);
|
||||
|
||||
grid.toggleSnap();
|
||||
expect(get(grid).snapEnabled).toBe(false);
|
||||
});
|
||||
|
||||
it('sets grid size with bounds', () => {
|
||||
grid.setSize(50);
|
||||
expect(get(grid).size).toBe(50);
|
||||
|
||||
grid.setSize(1); // Below min
|
||||
expect(get(grid).size).toBe(5);
|
||||
|
||||
grid.setSize(300); // Above max
|
||||
expect(get(grid).size).toBe(200);
|
||||
});
|
||||
|
||||
it('resets to defaults', () => {
|
||||
grid.setSize(100);
|
||||
grid.toggleVisible();
|
||||
grid.toggleSnap();
|
||||
|
||||
grid.reset();
|
||||
|
||||
const settings = get(grid);
|
||||
expect(settings.size).toBe(20);
|
||||
expect(settings.visible).toBe(false);
|
||||
expect(settings.snapEnabled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Snap to Grid', () => {
|
||||
it('snaps position to grid', () => {
|
||||
const snapped = snapToGrid(123, 456, 20);
|
||||
|
||||
expect(snapped.x).toBe(120);
|
||||
expect(snapped.y).toBe(460);
|
||||
});
|
||||
|
||||
it('handles exact grid positions', () => {
|
||||
const snapped = snapToGrid(100, 200, 20);
|
||||
|
||||
expect(snapped.x).toBe(100);
|
||||
expect(snapped.y).toBe(200);
|
||||
});
|
||||
|
||||
it('rounds to nearest grid point', () => {
|
||||
const snapped1 = snapToGrid(19, 19, 20);
|
||||
expect(snapped1.x).toBe(20);
|
||||
|
||||
const snapped2 = snapToGrid(11, 11, 20);
|
||||
expect(snapped2.x).toBe(20);
|
||||
|
||||
const snapped3 = snapToGrid(9, 9, 20);
|
||||
expect(snapped3.x).toBe(0);
|
||||
});
|
||||
|
||||
it('works with different grid sizes', () => {
|
||||
const snapped50 = snapToGrid(123, 456, 50);
|
||||
expect(snapped50.x).toBe(100);
|
||||
expect(snapped50.y).toBe(450);
|
||||
|
||||
const snapped10 = snapToGrid(123, 456, 10);
|
||||
expect(snapped10.x).toBe(120);
|
||||
expect(snapped10.y).toBe(460);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Visual Grid', () => {
|
||||
it('draws grid on layer', () => {
|
||||
const gridGroup = drawGrid(layer, 800, 600, 20);
|
||||
|
||||
expect(gridGroup).toBeDefined();
|
||||
expect(gridGroup.children.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('removes grid from layer', () => {
|
||||
drawGrid(layer, 800, 600, 20);
|
||||
|
||||
removeGrid(layer);
|
||||
|
||||
const grids = layer.find('.grid');
|
||||
expect(grids.length).toBe(0);
|
||||
});
|
||||
|
||||
it('updates grid when settings change', () => {
|
||||
updateGrid(layer, get(grid), 800, 600);
|
||||
|
||||
// Grid should not be visible by default
|
||||
const grids = layer.find('.grid');
|
||||
expect(grids.length).toBe(0);
|
||||
|
||||
// Enable visibility
|
||||
grid.toggleVisible();
|
||||
updateGrid(layer, get(grid), 800, 600);
|
||||
|
||||
const gridsAfter = layer.find('.grid');
|
||||
expect(gridsAfter.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('grid lines respect opacity', () => {
|
||||
const gridGroup = drawGrid(layer, 800, 600, 20, '#000000', 0.3);
|
||||
|
||||
// Check first child (a line)
|
||||
const firstLine = gridGroup.children[0] as Konva.Line;
|
||||
expect(firstLine.opacity()).toBe(0.3);
|
||||
});
|
||||
});
|
||||
});
|
||||
280
frontend/tests/canvas/z-order.test.ts
Normal file
280
frontend/tests/canvas/z-order.test.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user