Files
SnapOtter/tests/unit/web/aspect-ratio.test.ts
T
SnapOtterandGitHub d88999e7a9 feat(resize): add aspect-ratio proportion presets (#530)
Add a proportion chip row (Free, Original, 1:1, 4:3, 3:2, 16:9, 3:4, 9:16) to the Resize tool's Custom tab. Picking a ratio locks width and height so editing one recomputes the other, and prefills the largest box of that ratio that fits the source so it never upscales. Free stays the default, preserving existing behavior. Replaces the previously non-functional lock-aspect button. Frontend only, no backend or schema change; adds strings to all 21 locales.
2026-07-16 16:12:08 +08:00

92 lines
2.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
clampResizeDimension,
largestRatioBox,
MAX_RESIZE_DIMENSION,
pairedDimension,
RESIZE_RATIO_PRESETS,
} from "@/lib/aspect-ratio";
describe("pairedDimension", () => {
it("computes height from width for 16:9", () => {
expect(pairedDimension(1600, 16 / 9, "width")).toBe(900);
});
it("computes width from height for 16:9", () => {
expect(pairedDimension(900, 16 / 9, "height")).toBe(1600);
});
it("keeps a 1:1 square equal on both axes", () => {
expect(pairedDimension(200, 1, "width")).toBe(200);
expect(pairedDimension(200, 1, "height")).toBe(200);
});
it("rounds to the nearest whole pixel", () => {
// width 100 at 3:2 (1.5) -> height 66.67 -> 67
expect(pairedDimension(100, 3 / 2, "width")).toBe(67);
});
it("clamps the result to at least 1px", () => {
// 1 / (16/9) = 0.5625 -> rounds to 1
expect(pairedDimension(1, 16 / 9, "width")).toBe(1);
});
it("never exceeds the max dimension", () => {
expect(pairedDimension(MAX_RESIZE_DIMENSION, 9 / 16, "width")).toBeLessThanOrEqual(
MAX_RESIZE_DIMENSION,
);
});
});
describe("largestRatioBox", () => {
it("fits a 16:9 box inside a 4:3 landscape source, constrained by width", () => {
// 4000x3000 (1.333) is narrower than 16:9 (1.778) -> width wins
expect(largestRatioBox(4000, 3000, 16 / 9)).toEqual({ width: 4000, height: 2250 });
});
it("fits a 16:9 box inside a very wide source, constrained by height", () => {
// 3000x1000 (3.0) is wider than 16:9 -> height wins
expect(largestRatioBox(3000, 1000, 16 / 9)).toEqual({ width: 1778, height: 1000 });
});
it("returns the source itself when the ratio already matches", () => {
expect(largestRatioBox(1920, 1080, 16 / 9)).toEqual({ width: 1920, height: 1080 });
});
it("never upscales beyond the source dimensions", () => {
const box = largestRatioBox(1080, 1920, 16 / 9); // portrait source -> 16:9
expect(box.width).toBeLessThanOrEqual(1080);
expect(box.height).toBeLessThanOrEqual(1920);
});
});
describe("clampResizeDimension", () => {
it("rounds fractional pixels", () => {
expect(clampResizeDimension(66.6)).toBe(67);
});
it("floors at 1px", () => {
expect(clampResizeDimension(0)).toBe(1);
expect(clampResizeDimension(-5)).toBe(1);
});
it("caps at the max dimension", () => {
expect(clampResizeDimension(99999)).toBe(MAX_RESIZE_DIMENSION);
});
});
describe("RESIZE_RATIO_PRESETS", () => {
it("includes the common landscape and portrait ratios", () => {
const ids = RESIZE_RATIO_PRESETS.map((p) => p.id);
expect(ids).toContain("1:1");
expect(ids).toContain("16:9");
expect(ids).toContain("9:16");
expect(ids).toContain("4:3");
});
it("stores each ratio as width divided by height", () => {
const r = RESIZE_RATIO_PRESETS.find((p) => p.id === "16:9");
expect(r?.value).toBeCloseTo(16 / 9);
});
});