|
- 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);
- });
|