Files
RUWIO/shared/renderValidation.test.cjs
T
thandClaude Opus 4.8 518d43855b Add desktop/server MP4 export pipeline, hardening, and render tests (v1.3.0)
Desktop MP4 export renders frames from a hidden BrowserWindow via
capturePage, pipes PNGs to a bundled ffmpeg (libx264/yuv420p/+faststart),
and writes the file through an atomic temp-file swap. It shares scene
validation (shared/renderValidation.cjs) and the render.html renderer with
the standalone Puppeteer server, which is hardened in parallel.

Render worker & main (electron/):
- abortable render loop with an inactivity watchdog and cancel plumbing
- atomic temp-file + rename so a cancel/failure never truncates a
  pre-existing target (#7); stdin EPIPE / early-exit / backpressure-hang
  handled (#8)
- HiDPI frames normalized to canvas size (#10); odd dimensions padded to
  even (#13, also server); backgroundThrottling disabled (#12)
- reject canvases larger than the display (#11); single-render guard;
  isolated render session that blocks network requests (#5)

Tests:
- Electron harnesses (scripts/verify-*.cjs) for capture, export, cancel,
  timeout, assets, background, capture-fidelity, resolution, odd-dims,
  aggregated by verify-desktop-render.cjs
- server smoke + e2e and shared validation unit tests
- headless hook (src/devtools/headless.ts) to run the in-app suite under
  browser/electron hosts; pixel-diff util + capture fixtures (test/)
- dev_docs plans: integration, fixes (#7-#13), and the e2e/capture strategy

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 20:41:25 +02:00

63 lines
2.0 KiB
JavaScript

const test = require('node:test')
const assert = require('node:assert/strict')
const { validateRenderRequest, playbackRange } = require('./renderValidation.cjs')
function scene(overrides = {}) {
return {
canvas: { width: 640, height: 360 },
duration: 2,
fps: 30,
layers: [
{
keyframes: [
{ time: 0, props: {} },
{ time: 1, props: {} },
],
},
],
assets: [],
...overrides,
}
}
test('accepts a normal render request', () => {
const result = validateRenderRequest(scene(), { fps: 30, crf: 18, preset: 'fast' })
assert.equal(result.ok, true)
assert.equal(result.options.totalFrames, 30)
})
test('rejects invalid dimensions', () => {
const result = validateRenderRequest(scene({ canvas: { width: 0, height: 360 } }))
assert.equal(result.ok, false)
assert.match(result.error, /canvas/)
})
test('rejects invalid fps and frame count', () => {
assert.equal(validateRenderRequest(scene(), { fps: 0 }).ok, false)
assert.equal(validateRenderRequest(scene({ duration: 61 }), { fps: 60 }).ok, false)
})
test('rejects invalid duration', () => {
const result = validateRenderRequest(scene({ duration: 0 }))
assert.equal(result.ok, false)
assert.match(result.error, /duration/)
})
test('rejects too many layers and keyframes', () => {
const layers = Array.from({ length: 501 }, () => ({ keyframes: [] }))
assert.equal(validateRenderRequest(scene({ layers })).ok, false)
const keyframes = Array.from({ length: 5001 }, (_, i) => ({ time: i / 60, props: {} }))
assert.equal(validateRenderRequest(scene({ layers: [{ keyframes }] })).ok, false)
})
test('rejects invalid crf and preset', () => {
assert.equal(validateRenderRequest(scene(), { crf: 52 }).ok, false)
assert.equal(validateRenderRequest(scene(), { preset: 'turbo' }).ok, false)
})
test('computes playback range from in/out or last keyframe', () => {
assert.deepEqual(playbackRange(scene({ inTime: 0.25, outTime: 1.5 })), { start: 0.25, end: 1.5 })
assert.deepEqual(playbackRange(scene()), { start: 0, end: 1 })
})