import { describe, expect, it } from 'vitest'; import { INITIAL_ZOOM, MAX_SCALE, MIN_SCALE, clampScale, panBy, resetZoom, wheelFactor, zoomAt, type ZoomState, } from '../viewer/zoom'; // 大图查看器缩放状态机(Task 8)纯函数层:钳制、不动点缩放、平移门控与复位。 describe('zoom state machine (Task 8)', () => { it('clamps scale into [1, 4]', () => { expect(clampScale(0.2)).toBe(MIN_SCALE); expect(clampScale(3.9)).toBe(3.9); expect(clampScale(11)).toBe(MAX_SCALE); }); it('zooms around the given anchor point: the anchor stays fixed on screen', () => { const state: ZoomState = { scale: 1, tx: 0, ty: 0 }; // 锚点 (100, 40)(相对图片中心):缩放前屏幕位置 = 100*1+0 = 100。 const next = zoomAt(state, 2, 100, 40); expect(next.scale).toBe(2); expect(next.tx).toBe(100 * (1 - 2)); expect(next.ty).toBe(40 * (1 - 2)); // 锚点屏幕位置不变:100*2 + (-100) = 100。 expect(100 * next.scale + next.tx).toBe(100); expect(40 * next.scale + next.ty).toBe(40); }); it('clamps at max scale and keeps the anchor fixed with the actual factor', () => { const state: ZoomState = { scale: 3, tx: -50, ty: 20 }; const next = zoomAt(state, 10, 200, 0); expect(next.scale).toBe(MAX_SCALE); // 实际因子 4/3;锚点 200*3-50 = 550 → 200*4 + tx' = 550 → tx' = -250。 expect(200 * next.scale + next.tx).toBe(200 * state.scale + state.tx); }); it('resets translation when scale returns to min', () => { const state: ZoomState = { scale: 2, tx: -120, ty: 80 }; const next = zoomAt(state, 0.1, 300, 300); expect(next).toEqual(resetZoom()); }); it('pans only when zoomed in; pan accumulates', () => { const flat: ZoomState = { ...INITIAL_ZOOM }; expect(panBy(flat, 40, 20)).toBe(flat); // scale=1:无平移空间 const zoomed = zoomAt(flat, 2, 0, 0); const panned = panBy(panBy(zoomed, 10, -5), 6, 6); expect(panned.tx).toBe(16); expect(panned.ty).toBe(1); }); it('wheel factor maps deltaY sign consistently and stays continuous', () => { expect(wheelFactor(-120)).toBeGreaterThan(1); // 上滚/外张 → 放大 expect(wheelFactor(120)).toBeLessThan(1); // 下滚/内收 → 缩小 expect(wheelFactor(0)).toBe(1); }); });