/** * 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; 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); }); }); });