diff --git a/src/utils/aspectRatioUtils.test.ts b/src/utils/aspectRatioUtils.test.ts new file mode 100644 index 0000000..6909f3d --- /dev/null +++ b/src/utils/aspectRatioUtils.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from "vitest"; +import { getNativeAspectRatioValue } from "./aspectRatioUtils"; + +const FALLBACK_RATIO = 16 / 9; + +describe("getNativeAspectRatioValue", () => { + it("returns the video ratio when no crop region is provided", () => { + expect(getNativeAspectRatioValue(1920, 1080)).toBe(16 / 9); + }); + + it("applies crop width and height to the video ratio", () => { + expect(getNativeAspectRatioValue(1920, 1080, { x: 0, y: 0, width: 0.5, height: 1 })).toBe( + 8 / 9, + ); + }); + + it("falls back when video metadata is zero or non-finite", () => { + expect(getNativeAspectRatioValue(0, 1080)).toBe(FALLBACK_RATIO); + expect(getNativeAspectRatioValue(1920, 0)).toBe(FALLBACK_RATIO); + expect(getNativeAspectRatioValue(Number.NaN, 1080)).toBe(FALLBACK_RATIO); + expect(getNativeAspectRatioValue(1920, Number.POSITIVE_INFINITY)).toBe(FALLBACK_RATIO); + }); + + it("falls back when crop dimensions are non-positive or non-finite", () => { + expect(getNativeAspectRatioValue(1920, 1080, { x: 0, y: 0, width: 0, height: 1 })).toBe( + FALLBACK_RATIO, + ); + expect(getNativeAspectRatioValue(1920, 1080, { x: 0, y: 0, width: 1, height: -1 })).toBe( + FALLBACK_RATIO, + ); + expect( + getNativeAspectRatioValue(1920, 1080, { + x: 0, + y: 0, + width: Number.POSITIVE_INFINITY, + height: 1, + }), + ).toBe(FALLBACK_RATIO); + }); +}); diff --git a/src/utils/aspectRatioUtils.ts b/src/utils/aspectRatioUtils.ts index 887b543..168242b 100644 --- a/src/utils/aspectRatioUtils.ts +++ b/src/utils/aspectRatioUtils.ts @@ -11,6 +11,8 @@ export const ASPECT_RATIOS = [ export type AspectRatio = (typeof ASPECT_RATIOS)[number]; +const NATIVE_ASPECT_RATIO_FALLBACK = 16 / 9; + /** * Returns the numeric value of an aspect ratio. * For "native", returns a fallback ratio of 16/9. @@ -33,7 +35,7 @@ export function getAspectRatioValue(aspectRatio: AspectRatio): number { case "10:16": return 10 / 16; case "native": - return 16 / 9; + return NATIVE_ASPECT_RATIO_FALLBACK; default: { const _exhaustiveCheck: never = aspectRatio; return _exhaustiveCheck; @@ -48,7 +50,21 @@ export function getNativeAspectRatioValue( ): number { const cropW = cropRegion?.width ?? 1; const cropH = cropRegion?.height ?? 1; - return (videoWidth * cropW) / (videoHeight * cropH); + if ( + !Number.isFinite(videoWidth) || + !Number.isFinite(videoHeight) || + !Number.isFinite(cropW) || + !Number.isFinite(cropH) || + videoWidth <= 0 || + videoHeight <= 0 || + cropW <= 0 || + cropH <= 0 + ) { + return NATIVE_ASPECT_RATIO_FALLBACK; + } + + const ratio = (videoWidth * cropW) / (videoHeight * cropH); + return Number.isFinite(ratio) && ratio > 0 ? ratio : NATIVE_ASPECT_RATIO_FALLBACK; } export function getAspectRatioDimensions( @@ -72,6 +88,6 @@ export function isPortraitAspectRatio(aspectRatio: AspectRatio): boolean { } export function formatAspectRatioForCSS(aspectRatio: AspectRatio, nativeRatio?: number): string { - if (aspectRatio === "native") return String(nativeRatio ?? 16 / 9); + if (aspectRatio === "native") return String(nativeRatio ?? NATIVE_ASPECT_RATIO_FALLBACK); return aspectRatio.replace(":", "/"); }