]> git.proxmox.com Git - mirror_novnc.git/blob - tests/test.display.js
Use fat arrow functions `const foo = () => { ... };` for callbacks
[mirror_novnc.git] / tests / test.display.js
1 const expect = chai.expect;
2
3 import Base64 from '../core/base64.js';
4 import Display from '../core/display.js';
5
6 import sinon from '../vendor/sinon.js';
7
8 describe('Display/Canvas Helper', function () {
9 const checked_data = new Uint8Array([
10 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
11 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
12 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
13 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
14 ]);
15
16 const basic_data = new Uint8Array([0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255]);
17
18 function make_image_canvas (input_data) {
19 const canvas = document.createElement('canvas');
20 canvas.width = 4;
21 canvas.height = 4;
22 const ctx = canvas.getContext('2d');
23 const data = ctx.createImageData(4, 4);
24 for (let i = 0; i < checked_data.length; i++) { data.data[i] = input_data[i]; }
25 ctx.putImageData(data, 0, 0);
26 return canvas;
27 }
28
29 function make_image_png (input_data) {
30 const canvas = make_image_canvas(input_data);
31 const url = canvas.toDataURL();
32 const data = url.split(",")[1];
33 return Base64.decode(data);
34 }
35
36 describe('viewport handling', function () {
37 let display;
38 beforeEach(function () {
39 display = new Display(document.createElement('canvas'));
40 display.clipViewport = true;
41 display.resize(5, 5);
42 display.viewportChangeSize(3, 3);
43 display.viewportChangePos(1, 1);
44 });
45
46 it('should take viewport location into consideration when drawing images', function () {
47 display.resize(4, 4);
48 display.viewportChangeSize(2, 2);
49 display.drawImage(make_image_canvas(basic_data), 1, 1);
50 display.flip();
51
52 const expected = new Uint8Array(16);
53 for (let i = 0; i < 8; i++) { expected[i] = basic_data[i]; }
54 for (let i = 8; i < 16; i++) { expected[i] = 0; }
55 expect(display).to.have.displayed(expected);
56 });
57
58 it('should resize the target canvas when resizing the viewport', function() {
59 display.viewportChangeSize(2, 2);
60 expect(display._target.width).to.equal(2);
61 expect(display._target.height).to.equal(2);
62 });
63
64 it('should move the viewport if necessary', function() {
65 display.viewportChangeSize(5, 5);
66 expect(display.absX(0)).to.equal(0);
67 expect(display.absY(0)).to.equal(0);
68 expect(display._target.width).to.equal(5);
69 expect(display._target.height).to.equal(5);
70 });
71
72 it('should limit the viewport to the framebuffer size', function() {
73 display.viewportChangeSize(6, 6);
74 expect(display._target.width).to.equal(5);
75 expect(display._target.height).to.equal(5);
76 });
77
78 it('should redraw when moving the viewport', function () {
79 display.flip = sinon.spy();
80 display.viewportChangePos(-1, 1);
81 expect(display.flip).to.have.been.calledOnce;
82 });
83
84 it('should redraw when resizing the viewport', function () {
85 display.flip = sinon.spy();
86 display.viewportChangeSize(2, 2);
87 expect(display.flip).to.have.been.calledOnce;
88 });
89
90 it('should show the entire framebuffer when disabling the viewport', function() {
91 display.clipViewport = false;
92 expect(display.absX(0)).to.equal(0);
93 expect(display.absY(0)).to.equal(0);
94 expect(display._target.width).to.equal(5);
95 expect(display._target.height).to.equal(5);
96 });
97
98 it('should ignore viewport changes when the viewport is disabled', function() {
99 display.clipViewport = false;
100 display.viewportChangeSize(2, 2);
101 display.viewportChangePos(1, 1);
102 expect(display.absX(0)).to.equal(0);
103 expect(display.absY(0)).to.equal(0);
104 expect(display._target.width).to.equal(5);
105 expect(display._target.height).to.equal(5);
106 });
107
108 it('should show the entire framebuffer just after enabling the viewport', function() {
109 display.clipViewport = false;
110 display.clipViewport = true;
111 expect(display.absX(0)).to.equal(0);
112 expect(display.absY(0)).to.equal(0);
113 expect(display._target.width).to.equal(5);
114 expect(display._target.height).to.equal(5);
115 });
116 });
117
118 describe('resizing', function () {
119 let display;
120 beforeEach(function () {
121 display = new Display(document.createElement('canvas'));
122 display.clipViewport = false;
123 display.resize(4, 4);
124 });
125
126 it('should change the size of the logical canvas', function () {
127 display.resize(5, 7);
128 expect(display._fb_width).to.equal(5);
129 expect(display._fb_height).to.equal(7);
130 });
131
132 it('should keep the framebuffer data', function () {
133 display.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
134 display.resize(2, 2);
135 display.flip();
136 const expected = [];
137 for (let i = 0; i < 4 * 2*2; i += 4) {
138 expected[i] = 0xff;
139 expected[i+1] = expected[i+2] = 0;
140 expected[i+3] = 0xff;
141 }
142 expect(display).to.have.displayed(new Uint8Array(expected));
143 });
144
145 describe('viewport', function () {
146 beforeEach(function () {
147 display.clipViewport = true;
148 display.viewportChangeSize(3, 3);
149 display.viewportChangePos(1, 1);
150 });
151
152 it('should keep the viewport position and size if possible', function () {
153 display.resize(6, 6);
154 expect(display.absX(0)).to.equal(1);
155 expect(display.absY(0)).to.equal(1);
156 expect(display._target.width).to.equal(3);
157 expect(display._target.height).to.equal(3);
158 });
159
160 it('should move the viewport if necessary', function () {
161 display.resize(3, 3);
162 expect(display.absX(0)).to.equal(0);
163 expect(display.absY(0)).to.equal(0);
164 expect(display._target.width).to.equal(3);
165 expect(display._target.height).to.equal(3);
166 });
167
168 it('should shrink the viewport if necessary', function () {
169 display.resize(2, 2);
170 expect(display.absX(0)).to.equal(0);
171 expect(display.absY(0)).to.equal(0);
172 expect(display._target.width).to.equal(2);
173 expect(display._target.height).to.equal(2);
174 });
175 });
176 });
177
178 describe('rescaling', function () {
179 let display;
180 let canvas;
181
182 beforeEach(function () {
183 canvas = document.createElement('canvas');
184 display = new Display(canvas);
185 display.clipViewport = true;
186 display.resize(4, 4);
187 display.viewportChangeSize(3, 3);
188 display.viewportChangePos(1, 1);
189 document.body.appendChild(canvas);
190 });
191
192 afterEach(function () {
193 document.body.removeChild(canvas);
194 });
195
196 it('should not change the bitmap size of the canvas', function () {
197 display.scale = 2.0;
198 expect(canvas.width).to.equal(3);
199 expect(canvas.height).to.equal(3);
200 });
201
202 it('should change the effective rendered size of the canvas', function () {
203 display.scale = 2.0;
204 expect(canvas.clientWidth).to.equal(6);
205 expect(canvas.clientHeight).to.equal(6);
206 });
207
208 it('should not change when resizing', function () {
209 display.scale = 2.0;
210 display.resize(5, 5);
211 expect(display.scale).to.equal(2.0);
212 expect(canvas.width).to.equal(3);
213 expect(canvas.height).to.equal(3);
214 expect(canvas.clientWidth).to.equal(6);
215 expect(canvas.clientHeight).to.equal(6);
216 });
217 });
218
219 describe('autoscaling', function () {
220 let display;
221 let canvas;
222
223 beforeEach(function () {
224 canvas = document.createElement('canvas');
225 display = new Display(canvas);
226 display.clipViewport = true;
227 display.resize(4, 3);
228 document.body.appendChild(canvas);
229 });
230
231 afterEach(function () {
232 document.body.removeChild(canvas);
233 });
234
235 it('should preserve aspect ratio while autoscaling', function () {
236 display.autoscale(16, 9);
237 expect(canvas.clientWidth / canvas.clientHeight).to.equal(4 / 3);
238 });
239
240 it('should use width to determine scale when the current aspect ratio is wider than the target', function () {
241 display.autoscale(9, 16);
242 expect(display.absX(9)).to.equal(4);
243 expect(display.absY(18)).to.equal(8);
244 expect(canvas.clientWidth).to.equal(9);
245 expect(canvas.clientHeight).to.equal(7); // round 9 / (4 / 3)
246 });
247
248 it('should use height to determine scale when the current aspect ratio is taller than the target', function () {
249 display.autoscale(16, 9);
250 expect(display.absX(9)).to.equal(3);
251 expect(display.absY(18)).to.equal(6);
252 expect(canvas.clientWidth).to.equal(12); // 16 * (4 / 3)
253 expect(canvas.clientHeight).to.equal(9);
254
255 });
256
257 it('should not change the bitmap size of the canvas', function () {
258 display.autoscale(16, 9);
259 expect(canvas.width).to.equal(4);
260 expect(canvas.height).to.equal(3);
261 });
262 });
263
264 describe('drawing', function () {
265
266 // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the
267 // basic cases
268 let display;
269 beforeEach(function () {
270 display = new Display(document.createElement('canvas'));
271 display.resize(4, 4);
272 });
273
274 it('should clear the screen on #clear without a logo set', function () {
275 display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);
276 display._logo = null;
277 display.clear();
278 display.resize(4, 4);
279 const empty = [];
280 for (let i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; }
281 expect(display).to.have.displayed(new Uint8Array(empty));
282 });
283
284 it('should draw the logo on #clear with a logo set', function (done) {
285 display._logo = { width: 4, height: 4, type: "image/png", data: make_image_png(checked_data) };
286 display.clear();
287 display.onflush = () => {
288 expect(display).to.have.displayed(checked_data);
289 expect(display._fb_width).to.equal(4);
290 expect(display._fb_height).to.equal(4);
291 done();
292 };
293 display.flush();
294 });
295
296 it('should not draw directly on the target canvas', function () {
297 display.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
298 display.flip();
299 display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
300 const expected = [];
301 for (let i = 0; i < 4 * display._fb_width * display._fb_height; i += 4) {
302 expected[i] = 0xff;
303 expected[i+1] = expected[i+2] = 0;
304 expected[i+3] = 0xff;
305 }
306 expect(display).to.have.displayed(new Uint8Array(expected));
307 });
308
309 it('should support filling a rectangle with particular color via #fillRect', function () {
310 display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
311 display.fillRect(0, 0, 2, 2, [0xff, 0, 0]);
312 display.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
313 display.flip();
314 expect(display).to.have.displayed(checked_data);
315 });
316
317 it('should support copying an portion of the canvas via #copyImage', function () {
318 display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
319 display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]);
320 display.copyImage(0, 0, 2, 2, 2, 2);
321 display.flip();
322 expect(display).to.have.displayed(checked_data);
323 });
324
325 it('should support drawing images via #imageRect', function (done) {
326 display.imageRect(0, 0, "image/png", make_image_png(checked_data));
327 display.flip();
328 display.onflush = () => {
329 expect(display).to.have.displayed(checked_data);
330 done();
331 };
332 display.flush();
333 });
334
335 it('should support drawing tile data with a background color and sub tiles', function () {
336 display.startTile(0, 0, 4, 4, [0, 0xff, 0]);
337 display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
338 display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
339 display.finishTile();
340 display.flip();
341 expect(display).to.have.displayed(checked_data);
342 });
343
344 // We have a special cache for 16x16 tiles that we need to test
345 it('should support drawing a 16x16 tile', function () {
346 const large_checked_data = new Uint8Array(16*16*4);
347 display.resize(16, 16);
348
349 for (let y = 0;y < 16;y++) {
350 for (let x = 0;x < 16;x++) {
351 let pixel;
352 if ((x < 4) && (y < 4)) {
353 // NB: of course IE11 doesn't support #slice on ArrayBufferViews...
354 pixel = Array.prototype.slice.call(checked_data, (y*4+x)*4, (y*4+x+1)*4);
355 } else {
356 pixel = [0, 0xff, 0, 255];
357 }
358 large_checked_data.set(pixel, (y*16+x)*4);
359 }
360 }
361
362 display.startTile(0, 0, 16, 16, [0, 0xff, 0]);
363 display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
364 display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
365 display.finishTile();
366 display.flip();
367 expect(display).to.have.displayed(large_checked_data);
368 });
369
370 it('should support drawing BGRX blit images with true color via #blitImage', function () {
371 const data = [];
372 for (let i = 0; i < 16; i++) {
373 data[i * 4] = checked_data[i * 4 + 2];
374 data[i * 4 + 1] = checked_data[i * 4 + 1];
375 data[i * 4 + 2] = checked_data[i * 4];
376 data[i * 4 + 3] = checked_data[i * 4 + 3];
377 }
378 display.blitImage(0, 0, 4, 4, data, 0);
379 display.flip();
380 expect(display).to.have.displayed(checked_data);
381 });
382
383 it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
384 const data = [];
385 for (let i = 0; i < 16; i++) {
386 data[i * 3] = checked_data[i * 4];
387 data[i * 3 + 1] = checked_data[i * 4 + 1];
388 data[i * 3 + 2] = checked_data[i * 4 + 2];
389 }
390 display.blitRgbImage(0, 0, 4, 4, data, 0);
391 display.flip();
392 expect(display).to.have.displayed(checked_data);
393 });
394
395 it('should support drawing an image object via #drawImage', function () {
396 const img = make_image_canvas(checked_data);
397 display.drawImage(img, 0, 0);
398 display.flip();
399 expect(display).to.have.displayed(checked_data);
400 });
401 });
402
403 describe('the render queue processor', function () {
404 let display;
405 beforeEach(function () {
406 display = new Display(document.createElement('canvas'));
407 display.resize(4, 4);
408 sinon.spy(display, '_scan_renderQ');
409 });
410
411 afterEach(function () {
412 window.requestAnimationFrame = this.old_requestAnimationFrame;
413 });
414
415 it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
416 display._renderQ_push({ type: 'noop' }); // does nothing
417 expect(display._scan_renderQ).to.have.been.calledOnce;
418 });
419
420 it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
421 display._renderQ.length = 2;
422 display._renderQ_push({ type: 'noop' });
423 expect(display._scan_renderQ).to.not.have.been.called;
424 });
425
426 it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
427 const img = { complete: false, addEventListener: sinon.spy() }
428 display._renderQ = [{ type: 'img', x: 3, y: 4, img: img },
429 { type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];
430 display.drawImage = sinon.spy();
431 display.fillRect = sinon.spy();
432
433 display._scan_renderQ();
434 expect(display.drawImage).to.not.have.been.called;
435 expect(display.fillRect).to.not.have.been.called;
436 expect(img.addEventListener).to.have.been.calledOnce;
437
438 display._renderQ[0].img.complete = true;
439 display._scan_renderQ();
440 expect(display.drawImage).to.have.been.calledOnce;
441 expect(display.fillRect).to.have.been.calledOnce;
442 expect(img.addEventListener).to.have.been.calledOnce;
443 });
444
445 it('should call callback when queue is flushed', function () {
446 display.onflush = sinon.spy();
447 display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
448 expect(display.onflush).to.not.have.been.called;
449 display.flush();
450 expect(display.onflush).to.have.been.calledOnce;
451 });
452
453 it('should draw a blit image on type "blit"', function () {
454 display.blitImage = sinon.spy();
455 display._renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
456 expect(display.blitImage).to.have.been.calledOnce;
457 expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
458 });
459
460 it('should draw a blit RGB image on type "blitRgb"', function () {
461 display.blitRgbImage = sinon.spy();
462 display._renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
463 expect(display.blitRgbImage).to.have.been.calledOnce;
464 expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
465 });
466
467 it('should copy a region on type "copy"', function () {
468 display.copyImage = sinon.spy();
469 display._renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 });
470 expect(display.copyImage).to.have.been.calledOnce;
471 expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6);
472 });
473
474 it('should fill a rect with a given color on type "fill"', function () {
475 display.fillRect = sinon.spy();
476 display._renderQ_push({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});
477 expect(display.fillRect).to.have.been.calledOnce;
478 expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]);
479 });
480
481 it('should draw an image from an image object on type "img" (if complete)', function () {
482 display.drawImage = sinon.spy();
483 display._renderQ_push({ type: 'img', x: 3, y: 4, img: { complete: true } });
484 expect(display.drawImage).to.have.been.calledOnce;
485 expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
486 });
487 });
488 });