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