]> git.proxmox.com Git - mirror_novnc.git/blob - tests/test.display.js
* Don't check specific html elements from the display code (Fixes #446)
[mirror_novnc.git] / tests / test.display.js
1 // requires local modules: util, base64, display
2 // requires test modules: assertions
3 /* jshint expr: true */
4 var expect = chai.expect;
5
6 describe('Display/Canvas Helper', function () {
7 var checked_data = [
8 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
9 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
10 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
11 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
12 ];
13 checked_data = new Uint8Array(checked_data);
14
15 var basic_data = [0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255];
16 basic_data = new Uint8Array(basic_data);
17
18 function make_image_canvas (input_data) {
19 var canvas = document.createElement('canvas');
20 canvas.width = 4;
21 canvas.height = 4;
22 var ctx = canvas.getContext('2d');
23 var data = ctx.createImageData(4, 4);
24 for (var 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 describe('checking for cursor uri support', function () {
30 beforeEach(function () {
31 this._old_change_cursor = Display.changeCursor;
32 });
33
34 it('should disable cursor URIs if there is no support', function () {
35 Display.changeCursor = function(target) {
36 target.style.cursor = undefined;
37 };
38 var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false });
39 expect(display._cursor_uri).to.be.false;
40 });
41
42 it('should enable cursor URIs if there is support', function () {
43 Display.changeCursor = function(target) {
44 target.style.cursor = 'pointer';
45 };
46 var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false });
47 expect(display._cursor_uri).to.be.true;
48 });
49
50 it('respect the cursor_uri option if there is support', function () {
51 Display.changeCursor = function(target) {
52 target.style.cursor = 'pointer';
53 };
54 var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false, cursor_uri: false });
55 expect(display._cursor_uri).to.be.false;
56 });
57
58 afterEach(function () {
59 Display.changeCursor = this._old_change_cursor;
60 });
61 });
62
63 describe('viewport handling', function () {
64 var display;
65 beforeEach(function () {
66 display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
67 display.resize(5, 5);
68 display.viewportChangeSize(3, 3);
69 display.viewportChangePos(1, 1);
70 display.getCleanDirtyReset();
71 });
72
73 it('should take viewport location into consideration when drawing images', function () {
74 display.set_width(4);
75 display.set_height(4);
76 display.viewportChangeSize(2, 2);
77 display.drawImage(make_image_canvas(basic_data), 1, 1);
78
79 var expected = new Uint8Array(16);
80 var i;
81 for (i = 0; i < 8; i++) { expected[i] = basic_data[i]; }
82 for (i = 8; i < 16; i++) { expected[i] = 0; }
83 expect(display).to.have.displayed(expected);
84 });
85
86 it('should redraw the left side when shifted left', function () {
87 display.viewportChangePos(-1, 0);
88 var cdr = display.getCleanDirtyReset();
89 expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 1, w: 2, h: 3 });
90 expect(cdr.dirtyBoxes).to.have.length(1);
91 expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 0, y: 1, w: 2, h: 3 });
92 });
93
94 it('should redraw the right side when shifted right', function () {
95 display.viewportChangePos(1, 0);
96 var cdr = display.getCleanDirtyReset();
97 expect(cdr.cleanBox).to.deep.equal({ x: 2, y: 1, w: 2, h: 3 });
98 expect(cdr.dirtyBoxes).to.have.length(1);
99 expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 4, y: 1, w: 1, h: 3 });
100 });
101
102 it('should redraw the top part when shifted up', function () {
103 display.viewportChangePos(0, -1);
104 var cdr = display.getCleanDirtyReset();
105 expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 1, w: 3, h: 2 });
106 expect(cdr.dirtyBoxes).to.have.length(1);
107 expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 1, y: 0, w: 3, h: 1 });
108 });
109
110 it('should redraw the bottom part when shifted down', function () {
111 display.viewportChangePos(0, 1);
112 var cdr = display.getCleanDirtyReset();
113 expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 2, w: 3, h: 2 });
114 expect(cdr.dirtyBoxes).to.have.length(1);
115 expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 1, y: 4, w: 3, h: 1 });
116 });
117
118 it('should reset the entire viewport to being clean after calculating the clean/dirty boxes', function () {
119 display.viewportChangePos(0, 1);
120 var cdr1 = display.getCleanDirtyReset();
121 var cdr2 = display.getCleanDirtyReset();
122 expect(cdr1).to.not.deep.equal(cdr2);
123 expect(cdr2.cleanBox).to.deep.equal({ x: 1, y: 2, w: 3, h: 3 });
124 expect(cdr2.dirtyBoxes).to.be.empty;
125 });
126
127 it('should simply mark the whole display area as dirty if not using viewports', function () {
128 display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: false });
129 display.resize(5, 5);
130 var cdr = display.getCleanDirtyReset();
131 expect(cdr.cleanBox).to.deep.equal({ x: 0, y: 0, w: 0, h: 0 });
132 expect(cdr.dirtyBoxes).to.have.length(1);
133 expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 0, y: 0, w: 5, h: 5 });
134 });
135 });
136
137 describe('clipping', function () {
138 var display;
139 beforeEach(function () {
140 display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
141 display.resize(4, 3);
142 });
143
144 it('should report true when no max-size and framebuffer > viewport', function () {
145 display.viewportChangeSize(2,2);
146 var clipping = display.clippingDisplay();
147 expect(clipping).to.be.true;
148 });
149
150 it('should report false when no max-size and framebuffer = viewport', function () {
151 var clipping = display.clippingDisplay();
152 expect(clipping).to.be.false;
153 });
154
155 it('should report true when viewport > max-size and framebuffer > viewport', function () {
156 display.viewportChangeSize(2,2);
157 display.set_maxWidth(1);
158 display.set_maxHeight(2);
159 var clipping = display.clippingDisplay();
160 expect(clipping).to.be.true;
161 });
162
163 it('should report true when viewport > max-size and framebuffer = viewport', function () {
164 display.set_maxWidth(1);
165 display.set_maxHeight(2);
166 var clipping = display.clippingDisplay();
167 expect(clipping).to.be.true;
168 });
169 });
170
171 describe('resizing', function () {
172 var display;
173 beforeEach(function () {
174 display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
175 display.resize(4, 3);
176 });
177
178 it('should change the size of the logical canvas', function () {
179 display.resize(5, 7);
180 expect(display._fb_width).to.equal(5);
181 expect(display._fb_height).to.equal(7);
182 });
183
184 it('should update the viewport dimensions', function () {
185 sinon.spy(display, 'viewportChangeSize');
186 display.resize(2, 2);
187 expect(display.viewportChangeSize).to.have.been.calledOnce;
188 });
189 });
190
191 describe('rescaling', function () {
192 var display;
193 var canvas;
194
195 beforeEach(function () {
196 display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
197 display.resize(4, 3);
198 canvas = display.get_target();
199 document.body.appendChild(canvas);
200 });
201
202 afterEach(function () {
203 document.body.removeChild(canvas);
204 });
205
206 it('should not change the bitmap size of the canvas', function () {
207 display.set_scale(0.5);
208 expect(canvas.width).to.equal(4);
209 expect(canvas.height).to.equal(3);
210 });
211
212 it('should change the effective rendered size of the canvas', function () {
213 display.set_scale(0.5);
214 expect(canvas.clientWidth).to.equal(2);
215 expect(canvas.clientHeight).to.equal(2);
216 });
217 });
218
219 describe('autoscaling', function () {
220 var display;
221 var canvas;
222
223 beforeEach(function () {
224 display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
225 display.resize(4, 3);
226 canvas = display.get_target();
227 document.body.appendChild(canvas);
228 });
229
230 afterEach(function () {
231 document.body.removeChild(canvas);
232 });
233
234 it('should preserve aspect ratio while autoscaling', function () {
235 display.autoscale(16, 9);
236 expect(canvas.clientWidth / canvas.clientHeight).to.equal(4 / 3);
237 });
238
239 it('should use width to determine scale when the current aspect ratio is wider than the target', function () {
240 expect(display.autoscale(9, 16)).to.equal(9 / 4);
241 expect(canvas.clientWidth).to.equal(9);
242 expect(canvas.clientHeight).to.equal(7); // round 9 / (4 / 3)
243 });
244
245 it('should use height to determine scale when the current aspect ratio is taller than the target', function () {
246 expect(display.autoscale(16, 9)).to.equal(3); // 9 / 3
247 expect(canvas.clientWidth).to.equal(12); // 16 * (4 / 3)
248 expect(canvas.clientHeight).to.equal(9);
249
250 });
251
252 it('should not change the bitmap size of the canvas', function () {
253 display.autoscale(16, 9);
254 expect(canvas.width).to.equal(4);
255 expect(canvas.height).to.equal(3);
256 });
257
258 it('should not upscale when downscaleOnly is true', function () {
259 expect(display.autoscale(2, 2, true)).to.equal(0.5);
260 expect(canvas.clientWidth).to.equal(2);
261 expect(canvas.clientHeight).to.equal(2);
262
263 expect(display.autoscale(16, 9, true)).to.equal(1.0);
264 expect(canvas.clientWidth).to.equal(4);
265 expect(canvas.clientHeight).to.equal(3);
266 });
267 });
268
269 describe('drawing', function () {
270
271 // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the
272 // basic cases
273 function drawing_tests (pref_js) {
274 var display;
275 beforeEach(function () {
276 display = new Display({ target: document.createElement('canvas'), prefer_js: pref_js });
277 display.resize(4, 4);
278 });
279
280 it('should clear the screen on #clear without a logo set', function () {
281 display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);
282 display._logo = null;
283 display.clear();
284 display.resize(4, 4);
285 var empty = [];
286 for (var i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; }
287 expect(display).to.have.displayed(new Uint8Array(empty));
288 });
289
290 it('should draw the logo on #clear with a logo set', function (done) {
291 display._logo = { width: 4, height: 4, data: make_image_canvas(checked_data).toDataURL() };
292 display._drawCtx._act_drawImg = display._drawCtx.drawImage;
293 display._drawCtx.drawImage = function (img, x, y) {
294 this._act_drawImg(img, x, y);
295 expect(display).to.have.displayed(checked_data);
296 done();
297 };
298 display.clear();
299 expect(display._fb_width).to.equal(4);
300 expect(display._fb_height).to.equal(4);
301 });
302
303 it('should support filling a rectangle with particular color via #fillRect', function () {
304 display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
305 display.fillRect(0, 0, 2, 2, [0xff, 0, 0]);
306 display.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
307 expect(display).to.have.displayed(checked_data);
308 });
309
310 it('should support copying an portion of the canvas via #copyImage', function () {
311 display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
312 display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]);
313 display.copyImage(0, 0, 2, 2, 2, 2);
314 expect(display).to.have.displayed(checked_data);
315 });
316
317 it('should support drawing tile data with a background color and sub tiles', function () {
318 display.startTile(0, 0, 4, 4, [0, 0xff, 0]);
319 display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
320 display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
321 display.finishTile();
322 expect(display).to.have.displayed(checked_data);
323 });
324
325 it('should support drawing BGRX blit images with true color via #blitImage', function () {
326 var data = [];
327 for (var i = 0; i < 16; i++) {
328 data[i * 4] = checked_data[i * 4 + 2];
329 data[i * 4 + 1] = checked_data[i * 4 + 1];
330 data[i * 4 + 2] = checked_data[i * 4];
331 data[i * 4 + 3] = checked_data[i * 4 + 3];
332 }
333 display.blitImage(0, 0, 4, 4, data, 0);
334 expect(display).to.have.displayed(checked_data);
335 });
336
337 it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
338 var data = [];
339 for (var i = 0; i < 16; i++) {
340 data[i * 3] = checked_data[i * 4];
341 data[i * 3 + 1] = checked_data[i * 4 + 1];
342 data[i * 3 + 2] = checked_data[i * 4 + 2];
343 }
344 display.blitRgbImage(0, 0, 4, 4, data, 0);
345 expect(display).to.have.displayed(checked_data);
346 });
347
348 it('should support drawing blit images from a data URL via #blitStringImage', function (done) {
349 var img_url = make_image_canvas(checked_data).toDataURL();
350 display._drawCtx._act_drawImg = display._drawCtx.drawImage;
351 display._drawCtx.drawImage = function (img, x, y) {
352 this._act_drawImg(img, x, y);
353 expect(display).to.have.displayed(checked_data);
354 done();
355 };
356 display.blitStringImage(img_url, 0, 0);
357 });
358
359 it('should support drawing solid colors with color maps', function () {
360 display._true_color = false;
361 display.set_colourMap({ 0: [0xff, 0, 0], 1: [0, 0xff, 0] });
362 display.fillRect(0, 0, 4, 4, [1]);
363 display.fillRect(0, 0, 2, 2, [0]);
364 display.fillRect(2, 2, 2, 2, [0]);
365 expect(display).to.have.displayed(checked_data);
366 });
367
368 it('should support drawing blit images with color maps', function () {
369 display._true_color = false;
370 display.set_colourMap({ 1: [0xff, 0, 0], 0: [0, 0xff, 0] });
371 var data = [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1].map(function (elem) { return [elem]; });
372 display.blitImage(0, 0, 4, 4, data, 0);
373 expect(display).to.have.displayed(checked_data);
374 });
375
376 it('should support drawing an image object via #drawImage', function () {
377 var img = make_image_canvas(checked_data);
378 display.drawImage(img, 0, 0);
379 expect(display).to.have.displayed(checked_data);
380 });
381 }
382
383 describe('(prefering native methods)', function () { drawing_tests.call(this, false); });
384 describe('(prefering JavaScript)', function () { drawing_tests.call(this, true); });
385 });
386
387 describe('the render queue processor', function () {
388 var display;
389 beforeEach(function () {
390 display = new Display({ target: document.createElement('canvas'), prefer_js: false });
391 display.resize(4, 4);
392 sinon.spy(display, '_scan_renderQ');
393 this.old_requestAnimFrame = window.requestAnimFrame;
394 window.requestAnimFrame = function (cb) {
395 this.next_frame_cb = cb;
396 }.bind(this);
397 this.next_frame = function () { this.next_frame_cb(); };
398 });
399
400 afterEach(function () {
401 window.requestAnimFrame = this.old_requestAnimFrame;
402 });
403
404 it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
405 display.renderQ_push({ type: 'noop' }); // does nothing
406 expect(display._scan_renderQ).to.have.been.calledOnce;
407 });
408
409 it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
410 display._renderQ.length = 2;
411 display.renderQ_push({ type: 'noop' });
412 expect(display._scan_renderQ).to.not.have.been.called;
413 });
414
415 it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
416 var img = { complete: false };
417 display._renderQ = [{ type: 'img', x: 3, y: 4, img: img },
418 { type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];
419 display.drawImage = sinon.spy();
420 display.fillRect = sinon.spy();
421
422 display._scan_renderQ();
423 expect(display.drawImage).to.not.have.been.called;
424 expect(display.fillRect).to.not.have.been.called;
425
426 display._renderQ[0].img.complete = true;
427 this.next_frame();
428 expect(display.drawImage).to.have.been.calledOnce;
429 expect(display.fillRect).to.have.been.calledOnce;
430 });
431
432 it('should draw a blit image on type "blit"', function () {
433 display.blitImage = sinon.spy();
434 display.renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
435 expect(display.blitImage).to.have.been.calledOnce;
436 expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
437 });
438
439 it('should draw a blit RGB image on type "blitRgb"', function () {
440 display.blitRgbImage = sinon.spy();
441 display.renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
442 expect(display.blitRgbImage).to.have.been.calledOnce;
443 expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
444 });
445
446 it('should copy a region on type "copy"', function () {
447 display.copyImage = sinon.spy();
448 display.renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 });
449 expect(display.copyImage).to.have.been.calledOnce;
450 expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6);
451 });
452
453 it('should fill a rect with a given color on type "fill"', function () {
454 display.fillRect = sinon.spy();
455 display.renderQ_push({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});
456 expect(display.fillRect).to.have.been.calledOnce;
457 expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]);
458 });
459
460 it('should draw an image from an image object on type "img" (if complete)', function () {
461 display.drawImage = sinon.spy();
462 display.renderQ_push({ type: 'img', x: 3, y: 4, img: { complete: true } });
463 expect(display.drawImage).to.have.been.calledOnce;
464 expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
465 });
466 });
467 });