]> git.proxmox.com Git - mirror_novnc.git/blame - tests/test.display.js
Prefer const/let over var
[mirror_novnc.git] / tests / test.display.js
CommitLineData
2b5f94fa 1const expect = chai.expect;
1e13775b 2
dfae3209
SR
3import Base64 from '../core/base64.js';
4import Display from '../core/display.js';
dfae3209 5
0aaf59c2 6import sinon from '../vendor/sinon.js';
dfae3209 7
1e13775b 8describe('Display/Canvas Helper', function () {
2b5f94fa 9 const checked_data = new Uint8Array([
1e13775b
SR
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
2b5f94fa 14 ]);
1e13775b 15
2b5f94fa 16 const basic_data = new Uint8Array([0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255]);
1e13775b
SR
17
18 function make_image_canvas (input_data) {
2b5f94fa 19 const canvas = document.createElement('canvas');
1e13775b
SR
20 canvas.width = 4;
21 canvas.height = 4;
2b5f94fa
JD
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]; }
1e13775b
SR
25 ctx.putImageData(data, 0, 0);
26 return canvas;
27 }
28
74e39051 29 function make_image_png (input_data) {
2b5f94fa
JD
30 const canvas = make_image_canvas(input_data);
31 const url = canvas.toDataURL();
32 const data = url.split(",")[1];
74e39051
PO
33 return Base64.decode(data);
34 }
35
1e13775b 36 describe('viewport handling', function () {
2b5f94fa 37 let display;
1e13775b 38 beforeEach(function () {
747b4623 39 display = new Display(document.createElement('canvas'));
0460e5fd 40 display.clipViewport = true;
1e13775b 41 display.resize(5, 5);
636be753 42 display.viewportChangeSize(3, 3);
43 display.viewportChangePos(1, 1);
1e13775b
SR
44 });
45
46 it('should take viewport location into consideration when drawing images', function () {
e549ae07 47 display.resize(4, 4);
636be753 48 display.viewportChangeSize(2, 2);
1e13775b 49 display.drawImage(make_image_canvas(basic_data), 1, 1);
2ba767a7 50 display.flip();
1e13775b 51
2b5f94fa
JD
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; }
1e13775b
SR
55 expect(display).to.have.displayed(expected);
56 });
57
3f781f2a 58 it('should resize the target canvas when resizing the viewport', function() {
2ba767a7 59 display.viewportChangeSize(2, 2);
3f781f2a
PO
60 expect(display._target.width).to.equal(2);
61 expect(display._target.height).to.equal(2);
1e13775b
SR
62 });
63
adf345fd
PO
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
2ba767a7
PO
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;
1e13775b
SR
82 });
83
2ba767a7
PO
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;
1e13775b 88 });
fdedbafb 89
adf345fd 90 it('should show the entire framebuffer when disabling the viewport', function() {
0460e5fd 91 display.clipViewport = false;
adf345fd
PO
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() {
0460e5fd 99 display.clipViewport = false;
adf345fd
PO
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() {
0460e5fd
PO
109 display.clipViewport = false;
110 display.clipViewport = true;
adf345fd
PO
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 });
fdedbafb 116 });
117
1e13775b 118 describe('resizing', function () {
2b5f94fa 119 let display;
1e13775b 120 beforeEach(function () {
747b4623 121 display = new Display(document.createElement('canvas'));
0460e5fd 122 display.clipViewport = false;
adf345fd 123 display.resize(4, 4);
1e13775b
SR
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
2ba767a7 132 it('should keep the framebuffer data', function () {
adf345fd 133 display.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
2ba767a7
PO
134 display.resize(2, 2);
135 display.flip();
2b5f94fa
JD
136 const expected = [];
137 for (let i = 0; i < 4 * 2*2; i += 4) {
2ba767a7
PO
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 });
adf345fd
PO
144
145 describe('viewport', function () {
146 beforeEach(function () {
0460e5fd 147 display.clipViewport = true;
adf345fd
PO
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 });
1e13775b
SR
176 });
177
72747869 178 describe('rescaling', function () {
2b5f94fa
JD
179 let display;
180 let canvas;
72747869
SR
181
182 beforeEach(function () {
3d7bb020 183 canvas = document.createElement('canvas');
747b4623 184 display = new Display(canvas);
0460e5fd 185 display.clipViewport = true;
adf345fd
PO
186 display.resize(4, 4);
187 display.viewportChangeSize(3, 3);
188 display.viewportChangePos(1, 1);
72747869
SR
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 () {
747b4623 197 display.scale = 2.0;
adf345fd 198 expect(canvas.width).to.equal(3);
72747869
SR
199 expect(canvas.height).to.equal(3);
200 });
201
202 it('should change the effective rendered size of the canvas', function () {
747b4623 203 display.scale = 2.0;
adf345fd
PO
204 expect(canvas.clientWidth).to.equal(6);
205 expect(canvas.clientHeight).to.equal(6);
206 });
207
208 it('should not change when resizing', function () {
747b4623 209 display.scale = 2.0;
adf345fd 210 display.resize(5, 5);
747b4623 211 expect(display.scale).to.equal(2.0);
adf345fd
PO
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);
72747869
SR
216 });
217 });
218
219 describe('autoscaling', function () {
2b5f94fa
JD
220 let display;
221 let canvas;
72747869
SR
222
223 beforeEach(function () {
3d7bb020 224 canvas = document.createElement('canvas');
747b4623 225 display = new Display(canvas);
0460e5fd 226 display.clipViewport = true;
72747869 227 display.resize(4, 3);
72747869
SR
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 () {
280676c7
SM
241 display.autoscale(9, 16);
242 expect(display.absX(9)).to.equal(4);
243 expect(display.absY(18)).to.equal(8);
72747869
SR
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 () {
280676c7
SM
249 display.autoscale(16, 9);
250 expect(display.absX(9)).to.equal(3);
251 expect(display.absY(18)).to.equal(6);
72747869
SR
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 });
72747869
SR
262 });
263
1e13775b
SR
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
2b5f94fa 268 let display;
134ec26e
PO
269 beforeEach(function () {
270 display = new Display(document.createElement('canvas'));
271 display.resize(4, 4);
272 });
1e13775b 273
134ec26e
PO
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);
2b5f94fa
JD
279 const empty = [];
280 for (let i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; }
134ec26e
PO
281 expect(display).to.have.displayed(new Uint8Array(empty));
282 });
2ba767a7 283
134ec26e
PO
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();
747b4623 287 display.onflush = function () {
1e13775b 288 expect(display).to.have.displayed(checked_data);
134ec26e
PO
289 expect(display._fb_width).to.equal(4);
290 expect(display._fb_height).to.equal(4);
291 done();
747b4623 292 };
134ec26e
PO
293 display.flush();
294 });
1e13775b 295
134ec26e
PO
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]);
2b5f94fa
JD
300 const expected = [];
301 for (let i = 0; i < 4 * display._fb_width * display._fb_height; i += 4) {
134ec26e
PO
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 });
1e13775b 308
134ec26e
PO
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 });
74e39051 316
134ec26e
PO
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 });
1e13775b 324
134ec26e
PO
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();
747b4623 328 display.onflush = function () {
1e13775b 329 expect(display).to.have.displayed(checked_data);
134ec26e 330 done();
747b4623 331 };
134ec26e
PO
332 display.flush();
333 });
1e13775b 334
134ec26e
PO
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 });
1e13775b 343
1f9d0cb1
PO
344 // We have a special cache for 16x16 tiles that we need to test
345 it('should support drawing a 16x16 tile', function () {
2b5f94fa 346 const large_checked_data = new Uint8Array(16*16*4);
1f9d0cb1
PO
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)) {
af4deba8
SR
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);
1f9d0cb1
PO
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
134ec26e 370 it('should support drawing BGRX blit images with true color via #blitImage', function () {
2b5f94fa
JD
371 const data = [];
372 for (let i = 0; i < 16; i++) {
134ec26e
PO
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 });
1e13775b 382
134ec26e 383 it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
2b5f94fa
JD
384 const data = [];
385 for (let i = 0; i < 16; i++) {
134ec26e
PO
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 () {
2b5f94fa 396 const img = make_image_canvas(checked_data);
134ec26e
PO
397 display.drawImage(img, 0, 0);
398 display.flip();
399 expect(display).to.have.displayed(checked_data);
400 });
1e13775b
SR
401 });
402
403 describe('the render queue processor', function () {
2b5f94fa 404 let display;
1e13775b 405 beforeEach(function () {
134ec26e 406 display = new Display(document.createElement('canvas'));
1e13775b
SR
407 display.resize(4, 4);
408 sinon.spy(display, '_scan_renderQ');
1e13775b
SR
409 });
410
411 afterEach(function () {
e4fef7be 412 window.requestAnimationFrame = this.old_requestAnimationFrame;
1e13775b
SR
413 });
414
415 it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
1578fa68 416 display._renderQ_push({ type: 'noop' }); // does nothing
1e13775b
SR
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;
1578fa68 422 display._renderQ_push({ type: 'noop' });
1e13775b
SR
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 () {
2b5f94fa 427 const img = { complete: false, addEventListener: sinon.spy() }
1e13775b
SR
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;
bb6965f2 436 expect(img.addEventListener).to.have.been.calledOnce;
1e13775b
SR
437
438 display._renderQ[0].img.complete = true;
bb6965f2 439 display._scan_renderQ();
1e13775b
SR
440 expect(display.drawImage).to.have.been.calledOnce;
441 expect(display.fillRect).to.have.been.calledOnce;
bb6965f2 442 expect(img.addEventListener).to.have.been.calledOnce;
1e13775b
SR
443 });
444
3181a032 445 it('should call callback when queue is flushed', function () {
747b4623 446 display.onflush = sinon.spy();
3181a032 447 display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
747b4623 448 expect(display.onflush).to.not.have.been.called;
3181a032 449 display.flush();
747b4623 450 expect(display.onflush).to.have.been.calledOnce;
3181a032
PO
451 });
452
1e13775b
SR
453 it('should draw a blit image on type "blit"', function () {
454 display.blitImage = sinon.spy();
1578fa68 455 display._renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
1e13775b
SR
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();
1578fa68 462 display._renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
1e13775b
SR
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();
1578fa68 469 display._renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 });
1e13775b
SR
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();
1578fa68 476 display._renderQ_push({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});
1e13775b
SR
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();
1578fa68 483 display._renderQ_push({ type: 'img', x: 3, y: 4, img: { complete: true } });
1e13775b
SR
484 expect(display.drawImage).to.have.been.calledOnce;
485 expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
486 });
487 });
488});