|
- import assert from 'node:assert/strict'
- import { readFile } from 'node:fs/promises'
- import test from 'node:test'
- import ts from 'typescript'
-
- const sourceUrl = new URL('../../unreal_tran_web/src/features/scene-legacy/sceneCoverCapture.ts', import.meta.url)
- const source = await readFile(sourceUrl, 'utf8')
- const output = ts.transpileModule(source, {
- compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 },
- reportDiagnostics: true,
- })
- assert.deepEqual(output.diagnostics, [])
- const { captureSceneCover } = await import(`data:text/javascript;base64,${Buffer.from(output.outputText).toString('base64')}`)
-
- function fixture({ width = 1600, height = 900, renderError, drawError, encodeError, blob } = {}) {
- const events = []
- const transformHelper = { visible: true }
- const grid = { visible: false }
- const boundsHelper = { visible: true }
- const materials = [
- { original: { id: 'single-original' }, current: { id: 'single-highlighted' } },
- { original: [{ id: 'array-original-1' }, { id: 'array-original-2' }], current: [{ id: 'array-highlighted-1' }, { id: 'array-highlighted-2' }] },
- ]
- const entries = materials.map(({ original, current }) => ({ child: { material: current }, original, highlighted: current }))
- const sourceCanvas = { width, height }
- const scene = { children: [transformHelper, grid, boundsHelper, ...entries.map(entry => entry.child)] }
- const camera = Object.freeze({ position: Object.freeze({ x: 1, y: 2, z: 3 }) })
- const selected = entries[0].child
- const visibility = () => [transformHelper.visible, grid.visible, boundsHelper.visible]
- const snapshot = operation => ({ operation, visibility: visibility(), materials: entries.map(entry => entry.child.material) })
- let renders = 0
- const neverCalled = () => assert.fail('capture must not reset, detach, or dispose selection')
- const editor = {
- scene, camera, destroyed: false, selected,
- controls: { target: Object.freeze({ x: 0, y: 0, z: 0 }), update: neverCalled },
- transform: { getHelper: () => transformHelper, detach: neverCalled },
- grid,
- selectionHighlight: {
- helper: boundsHelper, materialEntries: entries, object: selected,
- setObject: neverCalled, clear: neverCalled, update: neverCalled,
- },
- renderer: {
- domElement: sourceCanvas,
- render(actualScene, actualCamera) {
- assert.equal(actualScene, scene)
- assert.equal(actualCamera, camera)
- events.push(snapshot('render'))
- renders += 1
- if (renders === 1 && renderError) throw renderError
- },
- },
- }
- const canvas = {
- width: 0, height: 0,
- getContext(kind) {
- assert.equal(kind, '2d')
- return {
- drawImage(...args) {
- assert.deepEqual(args, [sourceCanvas, 0, 0, canvas.width, canvas.height])
- events.push(snapshot('copy'))
- if (drawError) throw drawError
- },
- }
- },
- toBlob(callback, type, quality) {
- events.push({ ...snapshot('encode'), type, quality })
- if (encodeError) throw encodeError
- queueMicrotask(() => callback(blob === undefined ? new Blob(['jpeg'], { type }) : blob))
- },
- }
- const document = { createElement: tag => { assert.equal(tag, 'canvas'); return canvas } }
- const assertRestored = () => {
- assert.deepEqual(visibility(), [true, false, true])
- entries.forEach((entry, index) => {
- assert.equal(entry.child.material, materials[index].current)
- assert.equal(entry.original, materials[index].original)
- assert.equal(entry.highlighted, materials[index].current)
- })
- assert.equal(editor.selectionHighlight.materialEntries, entries)
- assert.equal(editor.selectionHighlight.object, selected)
- assert.equal(editor.selected, selected)
- assert.equal(scene.children.length, 5)
- assert.deepEqual(camera.position, { x: 1, y: 2, z: 3 })
- assert.deepEqual(editor.controls.target, { x: 0, y: 0, z: 0 })
- }
- return { editor, document, events, canvas, materials, entries, assertRestored }
- }
-
- test('copies synchronously without bounds, gizmo, grid or selection glow, then restores before JPEG encoding', async () => {
- const f = fixture()
- const pending = captureSceneCover(f.editor, { document: f.document })
- assert.deepEqual(f.events.map(event => event.operation), ['render', 'copy', 'render', 'encode'])
- for (const event of f.events.slice(0, 2)) {
- assert.deepEqual(event.visibility, [false, false, false])
- event.materials.forEach((material, index) => assert.equal(material, f.materials[index].original))
- }
- for (const event of f.events.slice(2)) {
- assert.deepEqual(event.visibility, [true, false, true])
- event.materials.forEach((material, index) => assert.equal(material, f.materials[index].current))
- }
- f.assertRestored()
- assert.equal(f.events[3].type, 'image/jpeg')
- assert.equal(f.events[3].quality, 0.85)
- const result = await pending
- assert.equal(result.type, 'image/jpeg')
- assert.equal(result.size, 4)
- })
-
- for (const { width, height, maxWidth, expected } of [
- { width: 1600, height: 900, expected: [640, 360] },
- { width: 900, height: 1600, expected: [360, 640] },
- { width: 320, height: 180, expected: [320, 180] },
- { width: 1600, height: 900, maxWidth: 320, expected: [320, 180] },
- ]) {
- test(`preserves the ${width} x ${height} ratio within ${maxWidth ?? 640}px and never upscales`, async () => {
- const f = fixture({ width, height })
- await captureSceneCover(f.editor, { document: f.document, maxWidth })
- assert.deepEqual([f.canvas.width, f.canvas.height], expected)
- f.assertRestored()
- })
- }
-
- for (const failure of ['drawError', 'renderError', 'encodeError']) {
- test(`restores helper visibility, scalar/array materials and selection after ${failure}`, async () => {
- const error = new Error(failure)
- const f = fixture({ [failure]: error })
- await assert.rejects(captureSceneCover(f.editor, { document: f.document }), actual => actual === error)
- f.assertRestored()
- assert.equal(f.events.filter(event => event.operation === 'render').length, 2)
- if (failure !== 'encodeError') assert.equal(f.events.some(event => event.operation === 'encode'), false)
- })
- }
-
- test('restores the actual material reference even if it differs from the recorded highlighted clone', async () => {
- const f = fixture()
- const replacement = { id: 'material-changed-after-selection' }
- f.entries[0].child.material = replacement
- await captureSceneCover(f.editor, { document: f.document })
- assert.equal(f.entries[0].child.material, replacement)
- assert.equal(f.events[0].materials[0], f.materials[0].original)
- })
-
- test('supports a ready scene without selection helpers', async () => {
- const f = fixture()
- delete f.editor.transform
- delete f.editor.grid
- delete f.editor.selectionHighlight
- assert.equal((await captureSceneCover(f.editor, { document: f.document })).type, 'image/jpeg')
- })
-
- test('deduplicates shared helper objects before recording visibility', async () => {
- const f = fixture()
- f.editor.transform.getHelper = () => f.editor.selectionHighlight.helper
- await captureSceneCover(f.editor, { document: f.document })
- f.assertRestored()
- })
-
- for (const blob of [null, new Blob([])]) {
- test(`rejects ${blob === null ? 'missing' : 'empty'} encoded images without disturbing the editor`, async () => {
- const f = fixture({ blob })
- await assert.rejects(captureSceneCover(f.editor, { document: f.document }), /编码结果为空/)
- f.assertRestored()
- })
- }
-
- test('refuses an unready or destroyed renderer before changing any scene state', async () => {
- for (const change of [
- f => { f.editor.destroyed = true },
- f => { f.editor.renderer = null },
- f => { f.editor.renderer.domElement.width = 0 },
- f => { f.editor.renderer.domElement.height = Number.NaN },
- f => { f.editor.scene = null },
- f => { f.editor.camera = null },
- ]) {
- const f = fixture()
- change(f)
- await assert.rejects(captureSceneCover(f.editor, { document: f.document }), /视口尚未准备好/)
- f.assertRestored()
- assert.equal(f.events.length, 0)
- }
- })
-
- test('refuses invalid dimensions and unavailable 2D canvas before changing scene state', async () => {
- const f = fixture()
- await assert.rejects(captureSceneCover(f.editor, { document: f.document, maxWidth: 0 }), /正数像素/)
- f.canvas.getContext = () => null
- await assert.rejects(captureSceneCover(f.editor, { document: f.document }), /不支持场景封面编码/)
- f.assertRestored()
- assert.equal(f.events.length, 0)
- })
|