Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 

142 rindas
6.0 KiB

  1. import assert from 'node:assert/strict';
  2. import test from 'node:test';
  3. import { captureModelCover } from '../../unreal_tran/src/model-cover-capture.js';
  4. function fixture({ width = 1600, height = 900, drawError, renderError, encodeError, blob } = {}) {
  5. const events = [];
  6. const selectionHelper = { visible: true };
  7. const transformHelper = { visible: false };
  8. const grid = { visible: true };
  9. const originalVisibility = [true, false, true];
  10. const visibility = () => [selectionHelper.visible, transformHelper.visible, grid.visible];
  11. const scene = {};
  12. const camera = Object.freeze({ position: Object.freeze({ x: 1, y: 2, z: 3 }) });
  13. const modelPivot = {};
  14. const source = { width, height };
  15. let renders = 0;
  16. const renderer = {
  17. domElement: source,
  18. render(actualScene, actualCamera) {
  19. assert.equal(actualScene, scene);
  20. assert.equal(actualCamera, camera);
  21. events.push({ operation: 'render', visibility: visibility() });
  22. renders += 1;
  23. if (renders === 1 && renderError) throw renderError;
  24. },
  25. };
  26. const editor = {
  27. renderer, scene, camera, modelPivot, selectionHelper, grid,
  28. transform: { getHelper: () => transformHelper },
  29. selected: { name: 'selected-model-node' },
  30. };
  31. const canvas = {
  32. width: 0,
  33. height: 0,
  34. getContext(kind) {
  35. assert.equal(kind, '2d');
  36. return {
  37. drawImage(...args) {
  38. events.push({ operation: 'copy', visibility: visibility() });
  39. assert.deepEqual(args, [source, 0, 0, canvas.width, canvas.height]);
  40. if (drawError) throw drawError;
  41. },
  42. };
  43. },
  44. toBlob(callback, type, quality) {
  45. events.push({ operation: 'encode', visibility: visibility(), type, quality });
  46. if (encodeError) throw encodeError;
  47. queueMicrotask(() => callback(blob === undefined ? new Blob(['jpeg'], { type }) : blob));
  48. },
  49. };
  50. const document = { createElement: tag => { assert.equal(tag, 'canvas'); return canvas; } };
  51. return { editor, canvas, document, events, visibility, originalVisibility };
  52. }
  53. test('copies the helper-free frame synchronously, restores the view, then encodes JPEG', async () => {
  54. const f = fixture();
  55. const selected = f.editor.selected;
  56. const pending = captureModelCover(f.editor, { document: f.document });
  57. // No await has occurred: copying and restoration must already be complete.
  58. assert.deepEqual(f.events.map(event => event.operation), ['render', 'copy', 'render', 'encode']);
  59. assert.deepEqual(f.events[0].visibility, [false, false, false]);
  60. assert.deepEqual(f.events[1].visibility, [false, false, false]);
  61. assert.deepEqual(f.events[2].visibility, f.originalVisibility);
  62. assert.deepEqual(f.events[3].visibility, f.originalVisibility);
  63. assert.equal(f.events[3].type, 'image/jpeg');
  64. assert.equal(f.events[3].quality, 0.85);
  65. assert.equal(f.editor.selected, selected);
  66. assert.deepEqual(f.editor.camera.position, { x: 1, y: 2, z: 3 });
  67. const result = await pending;
  68. assert.equal(result.type, 'image/jpeg');
  69. assert.equal(result.size, 4);
  70. });
  71. for (const { width, height, maxWidth, expected } of [
  72. { width: 1600, height: 900, expected: [640, 360] },
  73. { width: 900, height: 1600, expected: [360, 640] },
  74. { width: 320, height: 180, expected: [320, 180] },
  75. { width: 1600, height: 900, maxWidth: 320, expected: [320, 180] },
  76. ]) {
  77. test(`bounds a ${width} x ${height} view at ${maxWidth ?? 640} pixels without stretching`, async () => {
  78. const f = fixture({ width, height });
  79. await captureModelCover(f.editor, { document: f.document, maxWidth });
  80. assert.deepEqual([f.canvas.width, f.canvas.height], expected);
  81. });
  82. }
  83. for (const failure of ['drawError', 'renderError']) {
  84. test(`restores every helper and redraws the original viewport after ${failure}`, async () => {
  85. const error = new Error(failure);
  86. const f = fixture({ [failure]: error });
  87. await assert.rejects(captureModelCover(f.editor, { document: f.document }), actual => actual === error);
  88. assert.deepEqual(f.visibility(), f.originalVisibility);
  89. assert.equal(f.events.at(-1).operation, 'render');
  90. assert.deepEqual(f.events.at(-1).visibility, f.originalVisibility);
  91. assert.equal(f.events.some(event => event.operation === 'encode'), false);
  92. });
  93. }
  94. test('restores a helper referenced in more than one editor slot exactly once', async () => {
  95. const f = fixture();
  96. f.editor.transform.getHelper = () => f.editor.selectionHelper;
  97. await captureModelCover(f.editor, { document: f.document });
  98. assert.equal(f.editor.selectionHelper.visible, true);
  99. assert.equal(f.editor.grid.visible, true);
  100. });
  101. for (const blob of [null, new Blob([])]) {
  102. test(`rejects ${blob === null ? 'missing' : 'empty'} encoded images after restoring the viewport`, async () => {
  103. const f = fixture({ blob });
  104. await assert.rejects(captureModelCover(f.editor, { document: f.document }), /empty image/);
  105. assert.deepEqual(f.visibility(), f.originalVisibility);
  106. });
  107. }
  108. test('propagates encoder errors with the original viewport already restored', async () => {
  109. const error = new Error('encoding failed');
  110. const f = fixture({ encodeError: error });
  111. await assert.rejects(captureModelCover(f.editor, { document: f.document }), actual => actual === error);
  112. assert.deepEqual(f.visibility(), f.originalVisibility);
  113. });
  114. test('rejects unready viewports and invalid sizes before touching editor helpers', async () => {
  115. for (const change of [
  116. f => { f.editor.renderer = null; },
  117. f => { f.editor.renderer.domElement.width = 0; },
  118. f => { f.editor.renderer.domElement.height = Number.NaN; },
  119. f => { f.editor.scene = null; },
  120. f => { f.editor.camera = null; },
  121. f => { f.editor.modelPivot = null; },
  122. ]) {
  123. const f = fixture();
  124. change(f);
  125. await assert.rejects(captureModelCover(f.editor, { document: f.document }), /not ready/);
  126. assert.deepEqual(f.visibility(), f.originalVisibility);
  127. assert.equal(f.events.length, 0);
  128. }
  129. const f = fixture();
  130. await assert.rejects(captureModelCover(f.editor, { document: f.document, maxWidth: 0 }), /positive/);
  131. assert.deepEqual(f.visibility(), f.originalVisibility);
  132. assert.equal(f.events.length, 0);
  133. });