Files

181 lines
5.0 KiB
TypeScript
Raw Permalink Normal View History

/**
* ZoomPanController Tests Zoom & Pan Transformation
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { ZoomPanController } from '../src/canvas/ZoomPanController';
function createMockCanvas(w = 800, h = 600): HTMLCanvasElement {
const canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.fillRect = (() => {}) as any;
ctx.strokeRect = (() => {}) as any;
ctx.beginPath = (() => {}) as any;
ctx.moveTo = (() => {}) as any;
ctx.lineTo = (() => {}) as any;
ctx.stroke = (() => {}) as any;
ctx.fill = (() => {}) as any;
ctx.save = (() => {}) as any;
ctx.restore = (() => {}) as any;
ctx.scale = (() => {}) as any;
ctx.arc = (() => {}) as any;
ctx.setLineDash = (() => {}) as any;
ctx.clearRect = (() => {}) as any;
ctx.translate = (() => {}) as any;
ctx.rotate = (() => {}) as any;
}
return canvas;
}
describe('ZoomPanController', () => {
let canvas: HTMLCanvasElement;
let zpc: ZoomPanController;
beforeEach(() => {
canvas = createMockCanvas(800, 600);
zpc = new ZoomPanController(canvas);
});
describe('initial state', () => {
it('should have scale=1, offsetX=0, offsetY=0', () => {
const t = zpc.getTransform();
expect(t.a).toBe(1);
expect(t.d).toBe(1);
expect(t.e).toBe(0);
expect(t.f).toBe(0);
});
it('getScale should return 1 initially', () => {
expect(zpc.getScale()).toBe(1);
});
});
describe('getViewport', () => {
it('should return viewport covering full canvas at scale=1', () => {
const vp = zpc.getViewport();
expect(vp.minX).toBe(0);
expect(vp.minY).toBe(0);
expect(vp.maxX).toBe(800);
expect(vp.maxY).toBe(600);
});
it('should shrink visible area when zoomed in', () => {
zpc.zoomAt(400, 300, 2);
const vp = zpc.getViewport();
const w = vp.maxX - vp.minX;
const h = vp.maxY - vp.minY;
expect(w).toBeCloseTo(400, 1);
expect(h).toBeCloseTo(300, 1);
});
});
describe('zoomAt', () => {
it('should increase scale with factor > 1', () => {
zpc.zoomAt(100, 100, 2);
expect(zpc.getScale()).toBe(2);
});
it('should decrease scale with factor < 1', () => {
zpc.zoomAt(100, 100, 0.5);
expect(zpc.getScale()).toBe(0.5);
});
it('should keep the zoom center point stable', () => {
zpc.zoomAt(400, 300, 2);
const screen = zpc.worldToScreen(400, 300);
expect(screen.x).toBeCloseTo(400, 0);
expect(screen.y).toBeCloseTo(300, 0);
});
it('should clamp scale to minimum 0.01', () => {
zpc.zoomAt(0, 0, 0.001);
expect(zpc.getScale()).toBeGreaterThanOrEqual(0.01);
});
it('should clamp scale to maximum 100', () => {
for (let i = 0; i < 20; i++) {
zpc.zoomAt(400, 300, 10);
}
expect(zpc.getScale()).toBeLessThanOrEqual(100);
});
});
describe('pan', () => {
it('should update offsetX and offsetY', () => {
zpc.pan(50, 30);
const t = zpc.getTransform();
expect(t.e).toBe(50);
expect(t.f).toBe(30);
});
it('should accumulate pan calls', () => {
zpc.pan(10, 20);
zpc.pan(30, 40);
const t = zpc.getTransform();
expect(t.e).toBe(40);
expect(t.f).toBe(60);
});
});
describe('reset', () => {
it('should restore scale=1 and offset=0,0', () => {
zpc.zoomAt(100, 100, 3);
zpc.pan(50, 50);
zpc.reset();
const t = zpc.getTransform();
expect(t.a).toBe(1);
expect(t.d).toBe(1);
expect(t.e).toBe(0);
expect(t.f).toBe(0);
expect(zpc.getScale()).toBe(1);
});
});
describe('worldToScreen & screenToWorld', () => {
it('should convert world to screen at identity transform', () => {
const s = zpc.worldToScreen(100, 200);
expect(s.x).toBe(100);
expect(s.y).toBe(200);
});
it('should convert world to screen with scale and offset', () => {
zpc.zoomAt(0, 0, 2);
zpc.pan(50, 50);
const s = zpc.worldToScreen(100, 100);
expect(s.x).toBe(250);
expect(s.y).toBe(250);
});
});
describe('zoomFit', () => {
it('should not crash with empty elements', () => {
zpc.zoomFit([]);
expect(zpc.getScale()).toBe(1);
});
it('should fit elements within canvas', () => {
zpc.zoomFit([
{ x: 0, y: 0, width: 200, height: 200 },
{ x: 400, y: 400, width: 200, height: 200 },
]);
expect(zpc.getScale()).toBeGreaterThan(0);
expect(zpc.getScale()).toBeLessThan(100);
});
});
describe('zoomToRect', () => {
it('should zoom to a given rect', () => {
zpc.zoomToRect({ minX: 0, minY: 0, maxX: 400, maxY: 300 });
expect(zpc.getScale()).toBeGreaterThan(0);
});
it('should do nothing for zero-size rect', () => {
const before = zpc.getScale();
zpc.zoomToRect({ minX: 0, minY: 0, maxX: 0, maxY: 0 });
expect(zpc.getScale()).toBe(before);
});
});
});