]> git.proxmox.com Git - mirror_novnc.git/blob - tests/test.display.js
feat: add French localization strings
[mirror_novnc.git] / tests / test.display.js
1 const expect = chai.expect;
2
3 import Base64 from '../core/base64.js';
4 import Display from '../core/display.js';
5
6 describe('Display/Canvas Helper', function () {
7 const checkedData = new Uint8ClampedArray([
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
14 const basicData = new Uint8ClampedArray([0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255]);
15
16 function makeImageCanvas(inputData, width, height) {
17 const canvas = document.createElement('canvas');
18 canvas.width = width;
19 canvas.height = height;
20 const ctx = canvas.getContext('2d');
21 const data = new ImageData(inputData, width, height);
22 ctx.putImageData(data, 0, 0);
23 return canvas;
24 }
25
26 function makeImagePng(inputData, width, height) {
27 const canvas = makeImageCanvas(inputData, width, height);
28 const url = canvas.toDataURL();
29 const data = url.split(",")[1];
30 return Base64.decode(data);
31 }
32
33 describe('viewport handling', function () {
34 let display;
35 beforeEach(function () {
36 display = new Display(document.createElement('canvas'));
37 display.clipViewport = true;
38 display.resize(5, 5);
39 display.viewportChangeSize(3, 3);
40 display.viewportChangePos(1, 1);
41 });
42
43 it('should take viewport location into consideration when drawing images', function () {
44 display.resize(4, 4);
45 display.viewportChangeSize(2, 2);
46 display.drawImage(makeImageCanvas(basicData, 4, 1), 1, 1);
47 display.flip();
48
49 const expected = new Uint8Array(16);
50 for (let i = 0; i < 8; i++) { expected[i] = basicData[i]; }
51 for (let i = 8; i < 16; i++) { expected[i] = 0; }
52 expect(display).to.have.displayed(expected);
53 });
54
55 it('should resize the target canvas when resizing the viewport', function () {
56 display.viewportChangeSize(2, 2);
57 expect(display._target.width).to.equal(2);
58 expect(display._target.height).to.equal(2);
59 });
60
61 it('should move the viewport if necessary', function () {
62 display.viewportChangeSize(5, 5);
63 expect(display.absX(0)).to.equal(0);
64 expect(display.absY(0)).to.equal(0);
65 expect(display._target.width).to.equal(5);
66 expect(display._target.height).to.equal(5);
67 });
68
69 it('should limit the viewport to the framebuffer size', function () {
70 display.viewportChangeSize(6, 6);
71 expect(display._target.width).to.equal(5);
72 expect(display._target.height).to.equal(5);
73 });
74
75 it('should redraw when moving the viewport', function () {
76 display.flip = sinon.spy();
77 display.viewportChangePos(-1, 1);
78 expect(display.flip).to.have.been.calledOnce;
79 });
80
81 it('should redraw when resizing the viewport', function () {
82 display.flip = sinon.spy();
83 display.viewportChangeSize(2, 2);
84 expect(display.flip).to.have.been.calledOnce;
85 });
86
87 it('should show the entire framebuffer when disabling the viewport', function () {
88 display.clipViewport = false;
89 expect(display.absX(0)).to.equal(0);
90 expect(display.absY(0)).to.equal(0);
91 expect(display._target.width).to.equal(5);
92 expect(display._target.height).to.equal(5);
93 });
94
95 it('should ignore viewport changes when the viewport is disabled', function () {
96 display.clipViewport = false;
97 display.viewportChangeSize(2, 2);
98 display.viewportChangePos(1, 1);
99 expect(display.absX(0)).to.equal(0);
100 expect(display.absY(0)).to.equal(0);
101 expect(display._target.width).to.equal(5);
102 expect(display._target.height).to.equal(5);
103 });
104
105 it('should show the entire framebuffer just after enabling the viewport', function () {
106 display.clipViewport = false;
107 display.clipViewport = true;
108 expect(display.absX(0)).to.equal(0);
109 expect(display.absY(0)).to.equal(0);
110 expect(display._target.width).to.equal(5);
111 expect(display._target.height).to.equal(5);
112 });
113 });
114
115 describe('resizing', function () {
116 let display;
117 beforeEach(function () {
118 display = new Display(document.createElement('canvas'));
119 display.clipViewport = false;
120 display.resize(4, 4);
121 });
122
123 it('should change the size of the logical canvas', function () {
124 display.resize(5, 7);
125 expect(display._fbWidth).to.equal(5);
126 expect(display._fbHeight).to.equal(7);
127 });
128
129 it('should keep the framebuffer data', function () {
130 display.fillRect(0, 0, 4, 4, [0xff, 0, 0]);
131 display.resize(2, 2);
132 display.flip();
133 const expected = [];
134 for (let i = 0; i < 4 * 2*2; i += 4) {
135 expected[i] = 0xff;
136 expected[i+1] = expected[i+2] = 0;
137 expected[i+3] = 0xff;
138 }
139 expect(display).to.have.displayed(new Uint8Array(expected));
140 });
141
142 describe('viewport', function () {
143 beforeEach(function () {
144 display.clipViewport = true;
145 display.viewportChangeSize(3, 3);
146 display.viewportChangePos(1, 1);
147 });
148
149 it('should keep the viewport position and size if possible', function () {
150 display.resize(6, 6);
151 expect(display.absX(0)).to.equal(1);
152 expect(display.absY(0)).to.equal(1);
153 expect(display._target.width).to.equal(3);
154 expect(display._target.height).to.equal(3);
155 });
156
157 it('should move the viewport if necessary', function () {
158 display.resize(3, 3);
159 expect(display.absX(0)).to.equal(0);
160 expect(display.absY(0)).to.equal(0);
161 expect(display._target.width).to.equal(3);
162 expect(display._target.height).to.equal(3);
163 });
164
165 it('should shrink the viewport if necessary', function () {
166 display.resize(2, 2);
167 expect(display.absX(0)).to.equal(0);
168 expect(display.absY(0)).to.equal(0);
169 expect(display._target.width).to.equal(2);
170 expect(display._target.height).to.equal(2);
171 });
172 });
173 });
174
175 describe('rescaling', function () {
176 let display;
177 let canvas;
178
179 beforeEach(function () {
180 canvas = document.createElement('canvas');
181 display = new Display(canvas);
182 display.clipViewport = true;
183 display.resize(4, 4);
184 display.viewportChangeSize(3, 3);
185 display.viewportChangePos(1, 1);
186 document.body.appendChild(canvas);
187 });
188
189 afterEach(function () {
190 document.body.removeChild(canvas);
191 });
192
193 it('should not change the bitmap size of the canvas', function () {
194 display.scale = 2.0;
195 expect(canvas.width).to.equal(3);
196 expect(canvas.height).to.equal(3);
197 });
198
199 it('should change the effective rendered size of the canvas', function () {
200 display.scale = 2.0;
201 expect(canvas.clientWidth).to.equal(6);
202 expect(canvas.clientHeight).to.equal(6);
203 });
204
205 it('should not change when resizing', function () {
206 display.scale = 2.0;
207 display.resize(5, 5);
208 expect(display.scale).to.equal(2.0);
209 expect(canvas.width).to.equal(3);
210 expect(canvas.height).to.equal(3);
211 expect(canvas.clientWidth).to.equal(6);
212 expect(canvas.clientHeight).to.equal(6);
213 });
214 });
215
216 describe('autoscaling', function () {
217 let display;
218 let canvas;
219
220 beforeEach(function () {
221 canvas = document.createElement('canvas');
222 display = new Display(canvas);
223 display.clipViewport = true;
224 display.resize(4, 3);
225 display.viewportChangeSize(4, 3);
226 document.body.appendChild(canvas);
227 });
228
229 afterEach(function () {
230 document.body.removeChild(canvas);
231 });
232
233 it('should preserve aspect ratio while autoscaling', function () {
234 display.autoscale(16, 9);
235 expect(canvas.clientWidth / canvas.clientHeight).to.equal(4 / 3);
236 });
237
238 it('should use width to determine scale when the current aspect ratio is wider than the target', function () {
239 display.autoscale(9, 16);
240 expect(display.absX(9)).to.equal(4);
241 expect(display.absY(18)).to.equal(8);
242 expect(canvas.clientWidth).to.equal(9);
243 expect(canvas.clientHeight).to.equal(7); // round 9 / (4 / 3)
244 });
245
246 it('should use height to determine scale when the current aspect ratio is taller than the target', function () {
247 display.autoscale(16, 9);
248 expect(display.absX(9)).to.equal(3);
249 expect(display.absY(18)).to.equal(6);
250 expect(canvas.clientWidth).to.equal(12); // 16 * (4 / 3)
251 expect(canvas.clientHeight).to.equal(9);
252
253 });
254
255 it('should not change the bitmap size of the canvas', function () {
256 display.autoscale(16, 9);
257 expect(canvas.width).to.equal(4);
258 expect(canvas.height).to.equal(3);
259 });
260 });
261
262 describe('drawing', function () {
263
264 // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the
265 // basic cases
266 let display;
267 beforeEach(function () {
268 display = new Display(document.createElement('canvas'));
269 display.resize(4, 4);
270 });
271
272 it('should not draw directly on the target canvas', function () {
273 display.fillRect(0, 0, 4, 4, [0xff, 0, 0]);
274 display.flip();
275 display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
276 const expected = [];
277 for (let i = 0; i < 4 * display._fbWidth * display._fbHeight; i += 4) {
278 expected[i] = 0xff;
279 expected[i+1] = expected[i+2] = 0;
280 expected[i+3] = 0xff;
281 }
282 expect(display).to.have.displayed(new Uint8Array(expected));
283 });
284
285 it('should support filling a rectangle with particular color via #fillRect', function () {
286 display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
287 display.fillRect(0, 0, 2, 2, [0, 0, 0xff]);
288 display.fillRect(2, 2, 2, 2, [0, 0, 0xff]);
289 display.flip();
290 expect(display).to.have.displayed(checkedData);
291 });
292
293 it('should support copying an portion of the canvas via #copyImage', function () {
294 display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
295 display.fillRect(0, 0, 2, 2, [0, 0, 0xff]);
296 display.copyImage(0, 0, 2, 2, 2, 2);
297 display.flip();
298 expect(display).to.have.displayed(checkedData);
299 });
300
301 it('should support drawing images via #imageRect', function (done) {
302 display.imageRect(0, 0, 4, 4, "image/png", makeImagePng(checkedData, 4, 4));
303 display.flip();
304 display.onflush = () => {
305 expect(display).to.have.displayed(checkedData);
306 done();
307 };
308 display.flush();
309 });
310
311 it('should support blit images with true color via #blitImage', function () {
312 display.blitImage(0, 0, 4, 4, checkedData, 0);
313 display.flip();
314 expect(display).to.have.displayed(checkedData);
315 });
316
317 it('should support drawing an image object via #drawImage', function () {
318 const img = makeImageCanvas(checkedData, 4, 4);
319 display.drawImage(img, 0, 0);
320 display.flip();
321 expect(display).to.have.displayed(checkedData);
322 });
323 });
324
325 describe('the render queue processor', function () {
326 let display;
327 beforeEach(function () {
328 display = new Display(document.createElement('canvas'));
329 display.resize(4, 4);
330 sinon.spy(display, '_scanRenderQ');
331 });
332
333 it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
334 display._renderQPush({ type: 'noop' }); // does nothing
335 expect(display._scanRenderQ).to.have.been.calledOnce;
336 });
337
338 it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
339 display._renderQ.length = 2;
340 display._renderQPush({ type: 'noop' });
341 expect(display._scanRenderQ).to.not.have.been.called;
342 });
343
344 it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
345 const img = { complete: false, width: 4, height: 4, addEventListener: sinon.spy() };
346 display._renderQ = [{ type: 'img', x: 3, y: 4, width: 4, height: 4, img: img },
347 { type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];
348 display.drawImage = sinon.spy();
349 display.fillRect = sinon.spy();
350
351 display._scanRenderQ();
352 expect(display.drawImage).to.not.have.been.called;
353 expect(display.fillRect).to.not.have.been.called;
354 expect(img.addEventListener).to.have.been.calledOnce;
355
356 display._renderQ[0].img.complete = true;
357 display._scanRenderQ();
358 expect(display.drawImage).to.have.been.calledOnce;
359 expect(display.fillRect).to.have.been.calledOnce;
360 expect(img.addEventListener).to.have.been.calledOnce;
361 });
362
363 it('should call callback when queue is flushed', function () {
364 display.onflush = sinon.spy();
365 display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
366 expect(display.onflush).to.not.have.been.called;
367 display.flush();
368 expect(display.onflush).to.have.been.calledOnce;
369 });
370
371 it('should draw a blit image on type "blit"', function () {
372 display.blitImage = sinon.spy();
373 display._renderQPush({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
374 expect(display.blitImage).to.have.been.calledOnce;
375 expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
376 });
377
378 it('should copy a region on type "copy"', function () {
379 display.copyImage = sinon.spy();
380 display._renderQPush({ type: 'copy', x: 3, y: 4, width: 5, height: 6, oldX: 7, oldY: 8 });
381 expect(display.copyImage).to.have.been.calledOnce;
382 expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6);
383 });
384
385 it('should fill a rect with a given color on type "fill"', function () {
386 display.fillRect = sinon.spy();
387 display._renderQPush({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});
388 expect(display.fillRect).to.have.been.calledOnce;
389 expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]);
390 });
391
392 it('should draw an image from an image object on type "img" (if complete)', function () {
393 display.drawImage = sinon.spy();
394 display._renderQPush({ type: 'img', x: 3, y: 4, img: { complete: true } });
395 expect(display.drawImage).to.have.been.calledOnce;
396 expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
397 });
398 });
399 });