Files
web-cad/frontend/tests/ZoomPanController.test.ts
T
Leopoldadmin 1bcfaffdd4 fix: replace hardcoded localhost:3001 with relative API URL + add canvas mock and integration test
- Fix frontend Network error: API_BASE now defaults to empty string (same-origin)
- Fix 3 files: api.ts, AuthContext.tsx, Dashboard.tsx
- Add Canvas 2D context mock in tests/setup.ts for jsdom
- Fix -0 vs +0 in ZoomPanController.getViewport()
- Add IntegrationWorkflow.test.ts (35 tests, full CAD workflow)
2026-06-26 17:48:24 +02:00

181 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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);
});
});
});