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