Files
render/test/slides.test.ts
zeekay ee42369c45 Render worker for Hanzo Studio
A Node 24 process that claims render jobs from Studio's render lane
(/v1/studio/render), renders one output, asks the lane where it goes,
PUTs it there, and completes the job with the measured result.

Five ops: render.html (exact PNG/JPEG/WebP or one-page PDF),
render.document (PDF, DOCX, Markdown), render.slides (PPTX with notes,
or a 1920x1080 PDF per slide), render.composition (@hanzo/frames), and
video.edit (ffmpeg: trim, fit, join, libass captions, overlays, mixed
audio).

Every page runs in a fresh browser context behind request interception
and a per-job egress proxy, which frames' Chrome and Node go through
too; ffmpeg opens only local files through a demuxer whitelist.

The image is node:24-bookworm-slim with Debian's chromium-headless-shell
(amd64 and arm64), ffmpeg and fonts, built for both platforms by
hanzoai/ci.
2026-10-16 23:29:04 -07:00

45 lines
2.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict'
import { after, before, test } from 'node:test'
import { close } from '../src/browser.ts'
import { PPTX, zipNames } from '../src/measure.ts'
import { boxes, fake, perform, png } from './fake.ts'
import type { Fake } from './fake.ts'
let f: Fake
before(async () => {
f = await fake()
f.files.set('lib_hero.png', { body: png(400, 300, [20, 160, 90]), type: 'image/png' })
})
after(async () => { await close(); await f.close() })
const IR = {
slides: [
{ layout: 'title', title: 'Render', subtitle: 'Hanzo Studio', notes: 'Open with the result.' },
{ layout: 'content', title: 'What it does', body: 'Five ops, one worker.', bullets: ['HTML to **pixels**', 'Documents', 'Slides'] },
{ layout: 'image', title: 'A picture', image: 'lib_hero', body: 'Caption' },
{ layout: 'split', title: 'Side by side', bullets: ['Left text'], image: 'lib_hero' },
{ layout: 'quote', body: 'Minimal code, one way.', subtitle: 'The house rules' },
],
}
const input = (format: string) => ({ op: 'render.slides', with: { ir: IR, title: 'Deck', format }, inputs: { lib_hero: f.input('lib_hero.png') } })
test('render.slides pptx has one slide part per slide, with notes', async () => {
const { result, body } = await perform(f, input('pptx'))
assert.equal(result.mime, PPTX)
assert.equal(result.pages, 5)
const names = zipNames(body)
assert.deepEqual(names.filter((n) => /^ppt\/slides\/slide\d+\.xml$/.test(n)).sort(), [1, 2, 3, 4, 5].map((i) => `ppt/slides/slide${i}.xml`))
assert.ok(names.some((n) => /^ppt\/notesSlides\/notesSlide\d+\.xml$/.test(n)), 'speaker notes')
assert.ok(names.some((n) => /^ppt\/media\/.+\.png$/.test(n)), 'image embedded')
})
test('render.slides pdf is one 1920 × 1080 page per slide', async () => {
const { result, body } = await perform(f, input('pdf'))
assert.equal(result.mime, 'application/pdf')
assert.equal(result.pages, 5)
const b = boxes(body)
assert.equal(b.length, 5)
for (const box of b) assert.ok(Math.abs(box[2] - 1440) < 1 && Math.abs(box[3] - 810) < 1, `MediaBox ${box}`)
})