import assert from 'node:assert/strict'; import test from 'node:test'; import { captureModelCover } from '../../unreal_tran/src/model-cover-capture.js'; function fixture({ width = 1600, height = 900, drawError, renderError, encodeError, blob } = {}) { const events = []; const selectionHelper = { visible: true }; const transformHelper = { visible: false }; const grid = { visible: true }; const originalVisibility = [true, false, true]; const visibility = () => [selectionHelper.visible, transformHelper.visible, grid.visible]; const scene = {}; const camera = Object.freeze({ position: Object.freeze({ x: 1, y: 2, z: 3 }) }); const modelPivot = {}; const source = { width, height }; let renders = 0; const renderer = { domElement: source, render(actualScene, actualCamera) { assert.equal(actualScene, scene); assert.equal(actualCamera, camera); events.push({ operation: 'render', visibility: visibility() }); renders += 1; if (renders === 1 && renderError) throw renderError; }, }; const editor = { renderer, scene, camera, modelPivot, selectionHelper, grid, transform: { getHelper: () => transformHelper }, selected: { name: 'selected-model-node' }, }; const canvas = { width: 0, height: 0, getContext(kind) { assert.equal(kind, '2d'); return { drawImage(...args) { events.push({ operation: 'copy', visibility: visibility() }); assert.deepEqual(args, [source, 0, 0, canvas.width, canvas.height]); if (drawError) throw drawError; }, }; }, toBlob(callback, type, quality) { events.push({ operation: 'encode', visibility: visibility(), 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; } }; return { editor, canvas, document, events, visibility, originalVisibility }; } test('copies the helper-free frame synchronously, restores the view, then encodes JPEG', async () => { const f = fixture(); const selected = f.editor.selected; const pending = captureModelCover(f.editor, { document: f.document }); // No await has occurred: copying and restoration must already be complete. assert.deepEqual(f.events.map(event => event.operation), ['render', 'copy', 'render', 'encode']); assert.deepEqual(f.events[0].visibility, [false, false, false]); assert.deepEqual(f.events[1].visibility, [false, false, false]); assert.deepEqual(f.events[2].visibility, f.originalVisibility); assert.deepEqual(f.events[3].visibility, f.originalVisibility); assert.equal(f.events[3].type, 'image/jpeg'); assert.equal(f.events[3].quality, 0.85); assert.equal(f.editor.selected, selected); assert.deepEqual(f.editor.camera.position, { x: 1, y: 2, z: 3 }); 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(`bounds a ${width} x ${height} view at ${maxWidth ?? 640} pixels without stretching`, async () => { const f = fixture({ width, height }); await captureModelCover(f.editor, { document: f.document, maxWidth }); assert.deepEqual([f.canvas.width, f.canvas.height], expected); }); } for (const failure of ['drawError', 'renderError']) { test(`restores every helper and redraws the original viewport after ${failure}`, async () => { const error = new Error(failure); const f = fixture({ [failure]: error }); await assert.rejects(captureModelCover(f.editor, { document: f.document }), actual => actual === error); assert.deepEqual(f.visibility(), f.originalVisibility); assert.equal(f.events.at(-1).operation, 'render'); assert.deepEqual(f.events.at(-1).visibility, f.originalVisibility); assert.equal(f.events.some(event => event.operation === 'encode'), false); }); } test('restores a helper referenced in more than one editor slot exactly once', async () => { const f = fixture(); f.editor.transform.getHelper = () => f.editor.selectionHelper; await captureModelCover(f.editor, { document: f.document }); assert.equal(f.editor.selectionHelper.visible, true); assert.equal(f.editor.grid.visible, true); }); for (const blob of [null, new Blob([])]) { test(`rejects ${blob === null ? 'missing' : 'empty'} encoded images after restoring the viewport`, async () => { const f = fixture({ blob }); await assert.rejects(captureModelCover(f.editor, { document: f.document }), /empty image/); assert.deepEqual(f.visibility(), f.originalVisibility); }); } test('propagates encoder errors with the original viewport already restored', async () => { const error = new Error('encoding failed'); const f = fixture({ encodeError: error }); await assert.rejects(captureModelCover(f.editor, { document: f.document }), actual => actual === error); assert.deepEqual(f.visibility(), f.originalVisibility); }); test('rejects unready viewports and invalid sizes before touching editor helpers', async () => { for (const change of [ 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; }, f => { f.editor.modelPivot = null; }, ]) { const f = fixture(); change(f); await assert.rejects(captureModelCover(f.editor, { document: f.document }), /not ready/); assert.deepEqual(f.visibility(), f.originalVisibility); assert.equal(f.events.length, 0); } const f = fixture(); await assert.rejects(captureModelCover(f.editor, { document: f.document, maxWidth: 0 }), /positive/); assert.deepEqual(f.visibility(), f.originalVisibility); assert.equal(f.events.length, 0); });