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