diff --git a/packages/studio/src/components/renders/RenderQueue.test.tsx b/packages/studio/src/components/renders/RenderQueue.test.tsx new file mode 100644 index 000000000..a714193fc --- /dev/null +++ b/packages/studio/src/components/renders/RenderQueue.test.tsx @@ -0,0 +1,62 @@ +// @vitest-environment happy-dom + +import { act } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { RenderQueue } from "./RenderQueue"; + +Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }); + +let root: Root | null = null; + +afterEach(() => { + if (root) act(() => root?.unmount()); + root = null; + document.body.innerHTML = ""; +}); + +function mountRenderQueue(onStartRender: ReturnType) { + const host = document.createElement("div"); + document.body.append(host); + root = createRoot(host); + act(() => { + root?.render( + , + ); + }); + return host; +} + +describe("RenderQueue resolution submission", () => { + it("submits the canonical landscape 4K preset selected by the user", () => { + const onStartRender = vi.fn(); + const host = mountRenderQueue(onStartRender); + const resolutionSelect = [...host.querySelectorAll("select")].find((select) => + [...select.options].some((option) => option.textContent?.startsWith("4K")), + ); + if (!resolutionSelect) throw new Error("resolution selector did not render"); + + act(() => { + resolutionSelect.value = "4k"; + resolutionSelect.dispatchEvent(new Event("change", { bubbles: true })); + }); + + const exportButton = [...host.querySelectorAll("button")].find( + (button) => button.textContent === "Export", + ); + if (!exportButton) throw new Error("export button did not render"); + act(() => { + exportButton.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + + expect(onStartRender).toHaveBeenCalledWith("mp4", "standard", "landscape-4k", 30); + }); +}); diff --git a/packages/studio/src/components/renders/RenderQueue.tsx b/packages/studio/src/components/renders/RenderQueue.tsx index 71c142ad4..46f1958bc 100644 --- a/packages/studio/src/components/renders/RenderQueue.tsx +++ b/packages/studio/src/components/renders/RenderQueue.tsx @@ -1,4 +1,5 @@ import { memo, useState, useRef, useEffect, useId } from "react"; +import { CANVAS_DIMENSIONS } from "@hyperframes/parsers"; import { RenderQueueItem } from "./RenderQueueItem"; import { Button } from "../ui/Button"; import type { RenderJob, ResolutionPreset } from "./useRenderQueue"; @@ -53,17 +54,6 @@ const SCALE_LABEL: Record = { "4k": "4K", }; -// Mirrors `CANVAS_DIMENSIONS` in @hyperframes/core. Studio can't import from -// the core barrel (it transitively pulls in node:fs) and the values are stable. -const CANVAS_DIMENSIONS: Record = { - landscape: { width: 1920, height: 1080 }, - portrait: { width: 1080, height: 1920 }, - "landscape-4k": { width: 3840, height: 2160 }, - "portrait-4k": { width: 2160, height: 3840 }, - square: { width: 1080, height: 1080 }, - "square-4k": { width: 2160, height: 2160 }, -}; - type CompAspect = "landscape" | "portrait" | "square"; function compAspect(dims: CompositionDimensions | null | undefined): CompAspect { @@ -254,7 +244,7 @@ function FormatExportButton({ const persisted = getPersistedRenderSettings(); const [format, setFormat] = useState<"mp4" | "webm" | "mov">(persisted.format); const [quality, setQuality] = useState<"draft" | "standard" | "high">(persisted.quality); - const [resolution, setResolution] = useState("auto"); + const [resolution, setResolution] = useState("auto"); const [fps, setFps] = useState<24 | 30 | 60>(persisted.fps); // MOV (ProRes) is a fixed-quality codec — quality selector has no effect. @@ -290,7 +280,7 @@ function FormatExportButton({ Resolution