test.display.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. // requires local modules: util, base64, display
  2. // requires test modules: assertions
  3. /* jshint expr: true */
  4. var expect = chai.expect;
  5. describe('Display/Canvas Helper', function () {
  6. var checked_data = [
  7. 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
  8. 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
  9. 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
  10. 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
  11. ];
  12. checked_data = new Uint8Array(checked_data);
  13. var basic_data = [0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255];
  14. basic_data = new Uint8Array(basic_data);
  15. function make_image_canvas (input_data) {
  16. var canvas = document.createElement('canvas');
  17. canvas.width = 4;
  18. canvas.height = 4;
  19. var ctx = canvas.getContext('2d');
  20. var data = ctx.createImageData(4, 4);
  21. for (var i = 0; i < checked_data.length; i++) { data.data[i] = input_data[i]; }
  22. ctx.putImageData(data, 0, 0);
  23. return canvas;
  24. }
  25. describe('checking for cursor uri support', function () {
  26. beforeEach(function () {
  27. this._old_change_cursor = Display.changeCursor;
  28. });
  29. it('should disable cursor URIs if there is no support', function () {
  30. Display.changeCursor = function(target) {
  31. target.style.cursor = undefined;
  32. };
  33. var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false });
  34. expect(display._cursor_uri).to.be.false;
  35. });
  36. it('should enable cursor URIs if there is support', function () {
  37. Display.changeCursor = function(target) {
  38. target.style.cursor = 'pointer';
  39. };
  40. var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false });
  41. expect(display._cursor_uri).to.be.true;
  42. });
  43. it('respect the cursor_uri option if there is support', function () {
  44. Display.changeCursor = function(target) {
  45. target.style.cursor = 'pointer';
  46. };
  47. var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false, cursor_uri: false });
  48. expect(display._cursor_uri).to.be.false;
  49. });
  50. afterEach(function () {
  51. Display.changeCursor = this._old_change_cursor;
  52. });
  53. });
  54. describe('viewport handling', function () {
  55. var display;
  56. beforeEach(function () {
  57. display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
  58. display.resize(5, 5);
  59. display.viewportChangeSize(3, 3);
  60. display.viewportChangePos(1, 1);
  61. display.getCleanDirtyReset();
  62. });
  63. it('should take viewport location into consideration when drawing images', function () {
  64. display.set_width(4);
  65. display.set_height(4);
  66. display.viewportChangeSize(2, 2);
  67. display.drawImage(make_image_canvas(basic_data), 1, 1);
  68. var expected = new Uint8Array(16);
  69. var i;
  70. for (i = 0; i < 8; i++) { expected[i] = basic_data[i]; }
  71. for (i = 8; i < 16; i++) { expected[i] = 0; }
  72. expect(display).to.have.displayed(expected);
  73. });
  74. it('should redraw the left side when shifted left', function () {
  75. display.viewportChangePos(-1, 0);
  76. var cdr = display.getCleanDirtyReset();
  77. expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 1, w: 2, h: 3 });
  78. expect(cdr.dirtyBoxes).to.have.length(1);
  79. expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 0, y: 1, w: 2, h: 3 });
  80. });
  81. it('should redraw the right side when shifted right', function () {
  82. display.viewportChangePos(1, 0);
  83. var cdr = display.getCleanDirtyReset();
  84. expect(cdr.cleanBox).to.deep.equal({ x: 2, y: 1, w: 2, h: 3 });
  85. expect(cdr.dirtyBoxes).to.have.length(1);
  86. expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 4, y: 1, w: 1, h: 3 });
  87. });
  88. it('should redraw the top part when shifted up', function () {
  89. display.viewportChangePos(0, -1);
  90. var cdr = display.getCleanDirtyReset();
  91. expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 1, w: 3, h: 2 });
  92. expect(cdr.dirtyBoxes).to.have.length(1);
  93. expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 1, y: 0, w: 3, h: 1 });
  94. });
  95. it('should redraw the bottom part when shifted down', function () {
  96. display.viewportChangePos(0, 1);
  97. var cdr = display.getCleanDirtyReset();
  98. expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 2, w: 3, h: 2 });
  99. expect(cdr.dirtyBoxes).to.have.length(1);
  100. expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 1, y: 4, w: 3, h: 1 });
  101. });
  102. it('should reset the entire viewport to being clean after calculating the clean/dirty boxes', function () {
  103. display.viewportChangePos(0, 1);
  104. var cdr1 = display.getCleanDirtyReset();
  105. var cdr2 = display.getCleanDirtyReset();
  106. expect(cdr1).to.not.deep.equal(cdr2);
  107. expect(cdr2.cleanBox).to.deep.equal({ x: 1, y: 2, w: 3, h: 3 });
  108. expect(cdr2.dirtyBoxes).to.be.empty;
  109. });
  110. it('should simply mark the whole display area as dirty if not using viewports', function () {
  111. display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: false });
  112. display.resize(5, 5);
  113. var cdr = display.getCleanDirtyReset();
  114. expect(cdr.cleanBox).to.deep.equal({ x: 0, y: 0, w: 0, h: 0 });
  115. expect(cdr.dirtyBoxes).to.have.length(1);
  116. expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 0, y: 0, w: 5, h: 5 });
  117. });
  118. });
  119. describe('resizing', function () {
  120. var display;
  121. beforeEach(function () {
  122. display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
  123. display.resize(4, 3);
  124. });
  125. it('should change the size of the logical canvas', function () {
  126. display.resize(5, 7);
  127. expect(display._fb_width).to.equal(5);
  128. expect(display._fb_height).to.equal(7);
  129. });
  130. it('should update the viewport dimensions', function () {
  131. sinon.spy(display, 'viewportChangeSize');
  132. display.resize(2, 2);
  133. expect(display.viewportChangeSize).to.have.been.calledOnce;
  134. });
  135. });
  136. describe('drawing', function () {
  137. // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the
  138. // basic cases
  139. function drawing_tests (pref_js) {
  140. var display;
  141. beforeEach(function () {
  142. display = new Display({ target: document.createElement('canvas'), prefer_js: pref_js });
  143. display.resize(4, 4);
  144. });
  145. it('should clear the screen on #clear without a logo set', function () {
  146. display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);
  147. display._logo = null;
  148. display.clear();
  149. display.resize(4, 4);
  150. var empty = [];
  151. for (var i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; }
  152. expect(display).to.have.displayed(new Uint8Array(empty));
  153. });
  154. it('should draw the logo on #clear with a logo set', function (done) {
  155. display._logo = { width: 4, height: 4, data: make_image_canvas(checked_data).toDataURL() };
  156. display._drawCtx._act_drawImg = display._drawCtx.drawImage;
  157. display._drawCtx.drawImage = function (img, x, y) {
  158. this._act_drawImg(img, x, y);
  159. expect(display).to.have.displayed(checked_data);
  160. done();
  161. };
  162. display.clear();
  163. expect(display._fb_width).to.equal(4);
  164. expect(display._fb_height).to.equal(4);
  165. });
  166. it('should support filling a rectangle with particular color via #fillRect', function () {
  167. display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
  168. display.fillRect(0, 0, 2, 2, [0xff, 0, 0]);
  169. display.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
  170. expect(display).to.have.displayed(checked_data);
  171. });
  172. it('should support copying an portion of the canvas via #copyImage', function () {
  173. display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
  174. display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]);
  175. display.copyImage(0, 0, 2, 2, 2, 2);
  176. expect(display).to.have.displayed(checked_data);
  177. });
  178. it('should support drawing tile data with a background color and sub tiles', function () {
  179. display.startTile(0, 0, 4, 4, [0, 0xff, 0]);
  180. display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
  181. display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
  182. display.finishTile();
  183. expect(display).to.have.displayed(checked_data);
  184. });
  185. it('should support drawing BGRX blit images with true color via #blitImage', function () {
  186. var data = [];
  187. for (var i = 0; i < 16; i++) {
  188. data[i * 4] = checked_data[i * 4 + 2];
  189. data[i * 4 + 1] = checked_data[i * 4 + 1];
  190. data[i * 4 + 2] = checked_data[i * 4];
  191. data[i * 4 + 3] = checked_data[i * 4 + 3];
  192. }
  193. display.blitImage(0, 0, 4, 4, data, 0);
  194. expect(display).to.have.displayed(checked_data);
  195. });
  196. it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
  197. var data = [];
  198. for (var i = 0; i < 16; i++) {
  199. data[i * 3] = checked_data[i * 4];
  200. data[i * 3 + 1] = checked_data[i * 4 + 1];
  201. data[i * 3 + 2] = checked_data[i * 4 + 2];
  202. }
  203. display.blitRgbImage(0, 0, 4, 4, data, 0);
  204. expect(display).to.have.displayed(checked_data);
  205. });
  206. it('should support drawing blit images from a data URL via #blitStringImage', function (done) {
  207. var img_url = make_image_canvas(checked_data).toDataURL();
  208. display._drawCtx._act_drawImg = display._drawCtx.drawImage;
  209. display._drawCtx.drawImage = function (img, x, y) {
  210. this._act_drawImg(img, x, y);
  211. expect(display).to.have.displayed(checked_data);
  212. done();
  213. };
  214. display.blitStringImage(img_url, 0, 0);
  215. });
  216. it('should support drawing solid colors with color maps', function () {
  217. display._true_color = false;
  218. display.set_colourMap({ 0: [0xff, 0, 0], 1: [0, 0xff, 0] });
  219. display.fillRect(0, 0, 4, 4, [1]);
  220. display.fillRect(0, 0, 2, 2, [0]);
  221. display.fillRect(2, 2, 2, 2, [0]);
  222. expect(display).to.have.displayed(checked_data);
  223. });
  224. it('should support drawing blit images with color maps', function () {
  225. display._true_color = false;
  226. display.set_colourMap({ 1: [0xff, 0, 0], 0: [0, 0xff, 0] });
  227. var data = [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1].map(function (elem) { return [elem]; });
  228. display.blitImage(0, 0, 4, 4, data, 0);
  229. expect(display).to.have.displayed(checked_data);
  230. });
  231. it('should support drawing an image object via #drawImage', function () {
  232. var img = make_image_canvas(checked_data);
  233. display.drawImage(img, 0, 0);
  234. expect(display).to.have.displayed(checked_data);
  235. });
  236. }
  237. describe('(prefering native methods)', function () { drawing_tests.call(this, false); });
  238. describe('(prefering JavaScript)', function () { drawing_tests.call(this, true); });
  239. });
  240. describe('the render queue processor', function () {
  241. var display;
  242. beforeEach(function () {
  243. display = new Display({ target: document.createElement('canvas'), prefer_js: false });
  244. display.resize(4, 4);
  245. sinon.spy(display, '_scan_renderQ');
  246. this.old_requestAnimFrame = window.requestAnimFrame;
  247. window.requestAnimFrame = function (cb) {
  248. this.next_frame_cb = cb;
  249. }.bind(this);
  250. this.next_frame = function () { this.next_frame_cb(); };
  251. });
  252. afterEach(function () {
  253. window.requestAnimFrame = this.old_requestAnimFrame;
  254. });
  255. it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
  256. display.renderQ_push({ type: 'noop' }); // does nothing
  257. expect(display._scan_renderQ).to.have.been.calledOnce;
  258. });
  259. it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
  260. display._renderQ.length = 2;
  261. display.renderQ_push({ type: 'noop' });
  262. expect(display._scan_renderQ).to.not.have.been.called;
  263. });
  264. it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
  265. var img = { complete: false };
  266. display._renderQ = [{ type: 'img', x: 3, y: 4, img: img },
  267. { type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];
  268. display.drawImage = sinon.spy();
  269. display.fillRect = sinon.spy();
  270. display._scan_renderQ();
  271. expect(display.drawImage).to.not.have.been.called;
  272. expect(display.fillRect).to.not.have.been.called;
  273. display._renderQ[0].img.complete = true;
  274. this.next_frame();
  275. expect(display.drawImage).to.have.been.calledOnce;
  276. expect(display.fillRect).to.have.been.calledOnce;
  277. });
  278. it('should draw a blit image on type "blit"', function () {
  279. display.blitImage = sinon.spy();
  280. display.renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
  281. expect(display.blitImage).to.have.been.calledOnce;
  282. expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
  283. });
  284. it('should draw a blit RGB image on type "blitRgb"', function () {
  285. display.blitRgbImage = sinon.spy();
  286. display.renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
  287. expect(display.blitRgbImage).to.have.been.calledOnce;
  288. expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
  289. });
  290. it('should copy a region on type "copy"', function () {
  291. display.copyImage = sinon.spy();
  292. display.renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 });
  293. expect(display.copyImage).to.have.been.calledOnce;
  294. expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6);
  295. });
  296. it('should fill a rect with a given color on type "fill"', function () {
  297. display.fillRect = sinon.spy();
  298. display.renderQ_push({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});
  299. expect(display.fillRect).to.have.been.calledOnce;
  300. expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]);
  301. });
  302. it('should draw an image from an image object on type "img" (if complete)', function () {
  303. display.drawImage = sinon.spy();
  304. display.renderQ_push({ type: 'img', x: 3, y: 4, img: { complete: true } });
  305. expect(display.drawImage).to.have.been.calledOnce;
  306. expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
  307. });
  308. });
  309. });