mirror of
https://github.com/novnc/noVNC.git
synced 2026-05-26 23:19:41 +00:00
File: display.js Tests Added: True (preliminary) Changes: - De-crockford-ified the file NOTE: the tests included for display.js cover basic functionality, but are by no means nearly as comprehensive as the ones presented for rfb.js.
333 lines
15 KiB
JavaScript
333 lines
15 KiB
JavaScript
// requires local modules: util, base64, display
|
|
/* jshint expr: true */
|
|
var expect = chai.expect;
|
|
|
|
chai.use(function (_chai, utils) {
|
|
_chai.Assertion.addMethod('displayed', function (target_data) {
|
|
var obj = this._obj;
|
|
var data_cl = obj._drawCtx.getImageData(0, 0, obj._viewportLoc.w, obj._viewportLoc.h).data;
|
|
// NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray, so work around that
|
|
var data = new Uint8Array(data_cl);
|
|
this.assert(utils.eql(data, target_data),
|
|
"expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}",
|
|
"expected #{this} not to have displayed the image #{act}",
|
|
target_data,
|
|
data);
|
|
});
|
|
});
|
|
|
|
describe('Display/Canvas Helper', function () {
|
|
var checked_data = [
|
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
|
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
|
|
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
|
];
|
|
checked_data = new Uint8Array(checked_data);
|
|
|
|
var basic_data = [0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255];
|
|
basic_data = new Uint8Array(basic_data);
|
|
|
|
function make_image_canvas (input_data) {
|
|
var canvas = document.createElement('canvas');
|
|
canvas.width = 4;
|
|
canvas.height = 4;
|
|
var ctx = canvas.getContext('2d');
|
|
var data = ctx.createImageData(4, 4);
|
|
for (var i = 0; i < checked_data.length; i++) { data.data[i] = input_data[i]; }
|
|
ctx.putImageData(data, 0, 0);
|
|
return canvas;
|
|
}
|
|
|
|
describe('viewport handling', function () {
|
|
var display;
|
|
beforeEach(function () {
|
|
display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
|
|
display.resize(5, 5);
|
|
display.viewportChange(1, 1, 3, 3);
|
|
display.getCleanDirtyReset();
|
|
});
|
|
|
|
it('should take viewport location into consideration when drawing images', function () {
|
|
display.resize(4, 4);
|
|
display.viewportChange(0, 0, 2, 2);
|
|
display.drawImage(make_image_canvas(basic_data), 1, 1);
|
|
|
|
var expected = new Uint8Array(16);
|
|
var i;
|
|
for (i = 0; i < 8; i++) { expected[i] = basic_data[i]; }
|
|
for (i = 8; i < 16; i++) { expected[i] = 0; }
|
|
expect(display).to.have.displayed(expected);
|
|
});
|
|
|
|
it('should redraw the left side when shifted left', function () {
|
|
display.viewportChange(-1, 0, 3, 3);
|
|
var cdr = display.getCleanDirtyReset();
|
|
expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 1, w: 2, h: 3 });
|
|
expect(cdr.dirtyBoxes).to.have.length(1);
|
|
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 0, y: 1, w: 2, h: 3 });
|
|
});
|
|
|
|
it('should redraw the right side when shifted right', function () {
|
|
display.viewportChange(1, 0, 3, 3);
|
|
var cdr = display.getCleanDirtyReset();
|
|
expect(cdr.cleanBox).to.deep.equal({ x: 2, y: 1, w: 2, h: 3 });
|
|
expect(cdr.dirtyBoxes).to.have.length(1);
|
|
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 4, y: 1, w: 1, h: 3 });
|
|
});
|
|
|
|
it('should redraw the top part when shifted up', function () {
|
|
display.viewportChange(0, -1, 3, 3);
|
|
var cdr = display.getCleanDirtyReset();
|
|
expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 1, w: 3, h: 2 });
|
|
expect(cdr.dirtyBoxes).to.have.length(1);
|
|
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 1, y: 0, w: 3, h: 1 });
|
|
});
|
|
|
|
it('should redraw the bottom part when shifted down', function () {
|
|
display.viewportChange(0, 1, 3, 3);
|
|
var cdr = display.getCleanDirtyReset();
|
|
expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 2, w: 3, h: 2 });
|
|
expect(cdr.dirtyBoxes).to.have.length(1);
|
|
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 1, y: 4, w: 3, h: 1 });
|
|
});
|
|
|
|
it('should reset the entire viewport to being clean after calculating the clean/dirty boxes', function () {
|
|
display.viewportChange(0, 1, 3, 3);
|
|
var cdr1 = display.getCleanDirtyReset();
|
|
var cdr2 = display.getCleanDirtyReset();
|
|
expect(cdr1).to.not.deep.equal(cdr2);
|
|
expect(cdr2.cleanBox).to.deep.equal({ x: 1, y: 2, w: 3, h: 3 });
|
|
expect(cdr2.dirtyBoxes).to.be.empty;
|
|
});
|
|
|
|
it('should simply mark the whole display area as dirty if not using viewports', function () {
|
|
display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: false });
|
|
display.resize(5, 5);
|
|
var cdr = display.getCleanDirtyReset();
|
|
expect(cdr.cleanBox).to.deep.equal({ x: 0, y: 0, w: 0, h: 0 });
|
|
expect(cdr.dirtyBoxes).to.have.length(1);
|
|
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 0, y: 0, w: 5, h: 5 });
|
|
});
|
|
});
|
|
|
|
describe('resizing', function () {
|
|
var display;
|
|
beforeEach(function () {
|
|
display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
|
|
display.resize(4, 3);
|
|
});
|
|
|
|
it('should change the size of the logical canvas', function () {
|
|
display.resize(5, 7);
|
|
expect(display._fb_width).to.equal(5);
|
|
expect(display._fb_height).to.equal(7);
|
|
});
|
|
|
|
it('should update the viewport dimensions', function () {
|
|
sinon.spy(display, 'viewportChange');
|
|
display.resize(2, 2);
|
|
expect(display.viewportChange).to.have.been.calledOnce;
|
|
});
|
|
});
|
|
|
|
describe('drawing', function () {
|
|
|
|
// TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the
|
|
// basic cases
|
|
function drawing_tests (pref_js) {
|
|
var display;
|
|
beforeEach(function () {
|
|
display = new Display({ target: document.createElement('canvas'), prefer_js: pref_js });
|
|
display.resize(4, 4);
|
|
});
|
|
|
|
it('should clear the screen on #clear without a logo set', function () {
|
|
display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);
|
|
display._logo = null;
|
|
display.clear();
|
|
display.resize(4, 4);
|
|
var empty = [];
|
|
for (var i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; }
|
|
expect(display).to.have.displayed(new Uint8Array(empty));
|
|
});
|
|
|
|
it('should draw the logo on #clear with a logo set', function (done) {
|
|
display._logo = { width: 4, height: 4, data: make_image_canvas(checked_data).toDataURL() };
|
|
display._drawCtx._act_drawImg = display._drawCtx.drawImage;
|
|
display._drawCtx.drawImage = function (img, x, y) {
|
|
this._act_drawImg(img, x, y);
|
|
expect(display).to.have.displayed(checked_data);
|
|
done();
|
|
};
|
|
display.clear();
|
|
expect(display._fb_width).to.equal(4);
|
|
expect(display._fb_height).to.equal(4);
|
|
});
|
|
|
|
it('should support filling a rectangle with particular color via #fillRect', function () {
|
|
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
|
|
display.fillRect(0, 0, 2, 2, [0xff, 0, 0]);
|
|
display.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
|
|
expect(display).to.have.displayed(checked_data);
|
|
});
|
|
|
|
it('should support copying an portion of the canvas via #copyImage', function () {
|
|
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
|
|
display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]);
|
|
display.copyImage(0, 0, 2, 2, 2, 2);
|
|
expect(display).to.have.displayed(checked_data);
|
|
});
|
|
|
|
it('should support drawing tile data with a background color and sub tiles', function () {
|
|
display.startTile(0, 0, 4, 4, [0, 0xff, 0]);
|
|
display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
|
|
display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
|
|
display.finishTile();
|
|
expect(display).to.have.displayed(checked_data);
|
|
});
|
|
|
|
it('should support drawing BGRX blit images with true color via #blitImage', function () {
|
|
var data = [];
|
|
for (var i = 0; i < 16; i++) {
|
|
data[i * 4] = checked_data[i * 4 + 2];
|
|
data[i * 4 + 1] = checked_data[i * 4 + 1];
|
|
data[i * 4 + 2] = checked_data[i * 4];
|
|
data[i * 4 + 3] = checked_data[i * 4 + 3];
|
|
}
|
|
display.blitImage(0, 0, 4, 4, data, 0);
|
|
expect(display).to.have.displayed(checked_data);
|
|
});
|
|
|
|
it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
|
|
var data = [];
|
|
for (var i = 0; i < 16; i++) {
|
|
data[i * 3] = checked_data[i * 4];
|
|
data[i * 3 + 1] = checked_data[i * 4 + 1];
|
|
data[i * 3 + 2] = checked_data[i * 4 + 2];
|
|
}
|
|
display.blitRgbImage(0, 0, 4, 4, data, 0);
|
|
expect(display).to.have.displayed(checked_data);
|
|
});
|
|
|
|
it('should support drawing blit images from a data URL via #blitStringImage', function (done) {
|
|
var img_url = make_image_canvas(checked_data).toDataURL();
|
|
display._drawCtx._act_drawImg = display._drawCtx.drawImage;
|
|
display._drawCtx.drawImage = function (img, x, y) {
|
|
this._act_drawImg(img, x, y);
|
|
expect(display).to.have.displayed(checked_data);
|
|
done();
|
|
};
|
|
display.blitStringImage(img_url, 0, 0);
|
|
});
|
|
|
|
it('should support drawing solid colors with color maps', function () {
|
|
display._true_color = false;
|
|
display.set_colourMap({ 0: [0xff, 0, 0], 1: [0, 0xff, 0] });
|
|
display.fillRect(0, 0, 4, 4, [1]);
|
|
display.fillRect(0, 0, 2, 2, [0]);
|
|
display.fillRect(2, 2, 2, 2, [0]);
|
|
expect(display).to.have.displayed(checked_data);
|
|
});
|
|
|
|
it('should support drawing blit images with color maps', function () {
|
|
display._true_color = false;
|
|
display.set_colourMap({ 1: [0xff, 0, 0], 0: [0, 0xff, 0] });
|
|
var data = [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1].map(function (elem) { return [elem]; });
|
|
display.blitImage(0, 0, 4, 4, data, 0);
|
|
expect(display).to.have.displayed(checked_data);
|
|
});
|
|
|
|
it('should support drawing an image object via #drawImage', function () {
|
|
var img = make_image_canvas(checked_data);
|
|
display.drawImage(img, 0, 0);
|
|
expect(display).to.have.displayed(checked_data);
|
|
});
|
|
}
|
|
|
|
describe('(prefering native methods)', function () { drawing_tests.call(this, false); });
|
|
describe('(prefering JavaScript)', function () { drawing_tests.call(this, true); });
|
|
});
|
|
|
|
describe('the render queue processor', function () {
|
|
var display;
|
|
beforeEach(function () {
|
|
display = new Display({ target: document.createElement('canvas'), prefer_js: false });
|
|
display.resize(4, 4);
|
|
sinon.spy(display, '_scan_renderQ');
|
|
this.old_requestAnimFrame = window.requestAnimFrame;
|
|
window.requestAnimFrame = function (cb) {
|
|
this.next_frame_cb = cb;
|
|
}.bind(this);
|
|
this.next_frame = function () { this.next_frame_cb(); };
|
|
});
|
|
|
|
afterEach(function () {
|
|
window.requestAnimFrame = this.old_requestAnimFrame;
|
|
});
|
|
|
|
it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
|
|
display.renderQ_push({ type: 'noop' }); // does nothing
|
|
expect(display._scan_renderQ).to.have.been.calledOnce;
|
|
});
|
|
|
|
it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
|
|
display._renderQ.length = 2;
|
|
display.renderQ_push({ type: 'noop' });
|
|
expect(display._scan_renderQ).to.not.have.been.called;
|
|
});
|
|
|
|
it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
|
|
var img = { complete: false };
|
|
display._renderQ = [{ type: 'img', x: 3, y: 4, img: img },
|
|
{ type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];
|
|
display.drawImage = sinon.spy();
|
|
display.fillRect = sinon.spy();
|
|
|
|
display._scan_renderQ();
|
|
expect(display.drawImage).to.not.have.been.called;
|
|
expect(display.fillRect).to.not.have.been.called;
|
|
|
|
display._renderQ[0].img.complete = true;
|
|
this.next_frame();
|
|
expect(display.drawImage).to.have.been.calledOnce;
|
|
expect(display.fillRect).to.have.been.calledOnce;
|
|
});
|
|
|
|
it('should draw a blit image on type "blit"', function () {
|
|
display.blitImage = sinon.spy();
|
|
display.renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
|
|
expect(display.blitImage).to.have.been.calledOnce;
|
|
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
|
|
});
|
|
|
|
it('should draw a blit RGB image on type "blitRgb"', function () {
|
|
display.blitRgbImage = sinon.spy();
|
|
display.renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
|
|
expect(display.blitRgbImage).to.have.been.calledOnce;
|
|
expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
|
|
});
|
|
|
|
it('should copy a region on type "copy"', function () {
|
|
display.copyImage = sinon.spy();
|
|
display.renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 });
|
|
expect(display.copyImage).to.have.been.calledOnce;
|
|
expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6);
|
|
});
|
|
|
|
it('should fill a rect with a given color on type "fill"', function () {
|
|
display.fillRect = sinon.spy();
|
|
display.renderQ_push({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});
|
|
expect(display.fillRect).to.have.been.calledOnce;
|
|
expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]);
|
|
});
|
|
|
|
it('should draw an image from an image object on type "img" (if complete)', function () {
|
|
display.drawImage = sinon.spy();
|
|
display.renderQ_push({ type: 'img', x: 3, y: 4, img: { complete: true } });
|
|
expect(display.drawImage).to.have.been.calledOnce;
|
|
expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
|
|
});
|
|
});
|
|
});
|