import { describe, expect, it } from 'vitest' import { clampRect, computeOutputSize, estimateOutputBytes, formatEstimatedSize, moveRect, ratioValueFor, rectForRatio, resizeRect, rotateRectBySteps, rotatedDimensions, widthPresetPixels, wholeRect, } from '../imageCrop' describe('rectForRatio', () => { it('centers a 16:9 crop inside a 4032x3024 image', () => { const rect = rectForRatio('16:9', 4032, 3024) expect(rect).toEqual({ x: 0, y: 378, width: 4032, height: 2268 }) }) it('centers a 1:1 crop inside a 4032x3024 image', () => { const rect = rectForRatio('1:1', 4032, 3024) expect(rect).toEqual({ x: 504, y: 0, width: 3024, height: 3024 }) }) it('returns the whole image for Original', () => { expect(rectForRatio('Original', 4032, 3024)).toEqual(wholeRect(4032, 3024)) }) }) describe('computeOutputSize', () => { it('never upscales past the crop width', () => { expect(computeOutputSize(640, 640, 1600)).toEqual({ width: 640, height: 640, }) }) it('caps to the width preset when the crop is larger', () => { expect(computeOutputSize(4032, 3024, 1600)).toEqual({ width: 1600, height: 1200, }) }) }) describe('estimateOutputBytes', () => { it('estimates 420 KB for the default 1600x1200 output', () => { expect(formatEstimatedSize(estimateOutputBytes(1600, 1200))).toBe('420 KB') }) }) describe('formatEstimatedSize', () => { it('stays in KB just under the 1000 KB threshold', () => { expect(formatEstimatedSize(999 * 1024)).toBe('999 KB') }) it('switches to MB at and above the 1000 KB threshold', () => { expect(formatEstimatedSize(1024 * 1024 * 2.5)).toBe('2.5 MB') }) }) describe('clampRect', () => { it('rounds fractional coordinates and dimensions to whole pixels', () => { expect(clampRect({ x: 100.2, y: 50.7, width: 200.4, height: 150.6 }, 4032, 3024)).toEqual({ x: 100, y: 51, width: 200, height: 151, }) }) it('clamps width and height to the bounds', () => { expect(clampRect({ x: 0, y: 0, width: 5000, height: 4000 }, 4032, 3024)).toEqual({ x: 0, y: 0, width: 4032, height: 3024, }) }) it('clamps x and y so the rect stays inside the bounds', () => { expect(clampRect({ x: 4000, y: 3000, width: 3024, height: 3024 }, 4032, 3024)).toEqual({ x: 1008, y: 0, width: 3024, height: 3024, }) }) it('never lets width or height drop below 1px', () => { expect(clampRect({ x: 0, y: 0, width: -10, height: 0 }, 4032, 3024)).toEqual({ x: 0, y: 0, width: 1, height: 1, }) }) }) describe('moveRect', () => { it('offsets the rect and clamps it inside the bounds', () => { const rect = { x: 100, y: 100, width: 200, height: 200 } expect(moveRect(rect, 50, -50, 4032, 3024)).toEqual({ x: 150, y: 50, width: 200, height: 200, }) }) it('stops at the bound edge instead of moving past it', () => { const rect = { x: 0, y: 0, width: 200, height: 200 } expect(moveRect(rect, -50, -50, 4032, 3024)).toEqual({ x: 0, y: 0, width: 200, height: 200, }) }) }) describe('resizeRect', () => { it('grows freely on both axes when no ratio is locked', () => { const rect = { x: 0, y: 0, width: 200, height: 200 } expect(resizeRect(rect, 20, 10, 4032, 3024, null)).toEqual({ x: 0, y: 0, width: 220, height: 210, }) }) it('derives height from width when a ratio is locked and width changes', () => { const rect = { x: 0, y: 0, width: 200, height: 200 } expect(resizeRect(rect, 100, 0, 4032, 3024, 16 / 9)).toEqual({ x: 0, y: 0, width: 300, height: 169, }) }) }) describe('ratioValueFor', () => { it('returns null for Free so the frame is unconstrained', () => { expect(ratioValueFor('Free', 4032, 3024)).toBeNull() }) it('derives Original from the current bounds', () => { expect(ratioValueFor('Original', 4032, 3024)).toBeCloseTo(4032 / 3024) }) it('returns the fixed value for a named ratio', () => { expect(ratioValueFor('1:1', 4032, 3024)).toBe(1) }) }) describe('widthPresetPixels', () => { it('returns null for Original so the crop keeps its own width', () => { expect(widthPresetPixels('Original')).toBeNull() }) it('returns the pixel target for a fixed preset', () => { expect(widthPresetPixels('800 px')).toBe(800) }) }) describe('rotateRectBySteps', () => { it('rotates a whole-image rect 90deg clockwise into the new bounds', () => { const rotated = rotateRectBySteps(wholeRect(4032, 3024), 4032, 3024, 1) expect(rotated).toEqual({ x: 0, y: 0, width: 3024, height: 4032 }) }) it('round-trips back to the original after four steps', () => { const rect = { x: 42, y: 34, width: 500, height: 300 } const rotated = rotateRectBySteps(rect, 4032, 3024, 4) expect(rotated).toEqual(rect) }) }) describe('rotatedDimensions', () => { it('swaps dimensions for a 90deg rotation', () => { expect(rotatedDimensions(4032, 3024, 90)).toEqual({ width: 3024, height: 4032, }) }) it('keeps dimensions for a 180deg rotation', () => { expect(rotatedDimensions(4032, 3024, 180)).toEqual({ width: 4032, height: 3024, }) }) })