UNPKG

17.2 kBJavaScriptView Raw
1goog.require('ol.array');
2goog.require('ol.has');
3// avoid importing anything that results in an instanceof check
4// since these extensions are global, instanceof checks fail with modules
5
6(function(global) {
7
8 // show generated maps for rendering tests
9 var showMap = (global.location.search.indexOf('generate') >= 0);
10
11 // show a diff when rendering tests fail
12 var showDiff = (global.location.search.indexOf('showdiff') >= 0);
13
14 /**
15 * The default tolerance for image comparisons.
16 */
17 global.IMAGE_TOLERANCE = 1.5;
18
19 function afterLoad(type, path, next) {
20 var client = new XMLHttpRequest();
21 client.open('GET', path, true);
22 client.onload = function() {
23 var data;
24 if (type === 'xml') {
25 data = client.responseXML;
26 } else {
27 data = client.responseText;
28 }
29 if (!data) {
30 throw new Error(path + ' loading failed: ' + client.status);
31 }
32 next(data);
33 };
34 client.send();
35 }
36
37 /**
38 * @param {string} path Relative path to file (e.g. 'spec/ol/foo.json').
39 * @param {function(Object)} next Function to call with response object on
40 * success. On failure, an error is thrown with the reason.
41 */
42 global.afterLoadJson = function(path, next) {
43 afterLoad('json', path, next);
44 };
45
46
47 /**
48 * @param {string} path Relative path to file (e.g. 'spec/ol/foo.txt').
49 * @param {function(string)} next Function to call with response text on
50 * success. On failure, an error is thrown with the reason.
51 */
52 global.afterLoadText = function(path, next) {
53 afterLoad('text', path, next);
54 };
55
56
57 /**
58 * @param {string} path Relative path to file (e.g. 'spec/ol/foo.xml').
59 * @param {function(Document)} next Function to call with response xml on
60 * success. On failure, an error is thrown with the reason.
61 */
62 global.afterLoadXml = function(path, next) {
63 afterLoad('xml', path, next);
64 };
65
66
67 // extensions to expect.js
68 var expect = global.expect;
69
70
71 /**
72 * Assert value is within some tolerance of a number.
73 * @param {Number} n Number.
74 * @param {Number} tol Tolerance.
75 * @return {expect.Assertion} The assertion.
76 */
77 expect.Assertion.prototype.roughlyEqual = function(n, tol) {
78 this.assert(
79 Math.abs(this.obj - n) <= tol,
80 function() {
81 return 'expected ' + expect.stringify(this.obj) + ' to be within ' +
82 tol + ' of ' + n;
83 },
84 function() {
85 return 'expected ' + expect.stringify(this.obj) +
86 ' not to be within ' + tol + ' of ' + n;
87 });
88 return this;
89 };
90
91
92 /**
93 * Assert that a sinon spy was called.
94 * @return {expect.Assertion} The assertion.
95 */
96 expect.Assertion.prototype.called = function() {
97 this.assert(
98 this.obj.called,
99 function() {
100 return 'expected ' + expect.stringify(this.obj) + ' to be called';
101 },
102 function() {
103 return 'expected ' + expect.stringify(this.obj) + ' not to be called';
104 });
105 return this;
106 };
107
108
109 function getChildNodes(node, options) {
110 // check whitespace
111 if (options && options.includeWhiteSpace) {
112 return node.childNodes;
113 } else {
114 var nodes = [];
115 for (var i = 0, ii = node.childNodes.length; i < ii; i++) {
116 var child = node.childNodes[i];
117 if (child.nodeType == 1) {
118 // element node, add it
119 nodes.push(child);
120 } else if (child.nodeType == 3) {
121 // text node, add if non empty
122 if (child.nodeValue &&
123 child.nodeValue.replace(/^\s*(.*?)\s*$/, '$1') !== '') {
124 nodes.push(child);
125 }
126 }
127 }
128 if (options && options.ignoreElementOrder) {
129 nodes.sort(function(a, b) {
130 return a.nodeName > b.nodeName ? 1 : a.nodeName < b.nodeName ? -1 : 0;
131 });
132 }
133 return nodes;
134 }
135 }
136
137 function assertElementNodesEqual(node1, node2, options, errors) {
138 var testPrefix = (options && options.prefix === true);
139 if (node1.nodeType !== node2.nodeType) {
140 errors.push('nodeType test failed for: ' + node1.nodeName + ' | ' +
141 node2.nodeName + ' | expected ' + node1.nodeType + ' to equal ' +
142 node2.nodeType);
143 }
144 if (testPrefix) {
145 if (node1.nodeName !== node2.nodeName) {
146 errors.push('nodeName test failed for: ' + node1.nodeName + ' | ' +
147 node2.nodeName + ' | expected ' + node1.nodeName + ' to equal ' +
148 node2.nodeName);
149 }
150 } else {
151 var n1 = node1.nodeName.split(':').pop();
152 var n2 = node2.nodeName.split(':').pop();
153 if (n1 !== n2) {
154 errors.push('nodeName test failed for: ' + node1.nodeName + ' | ' +
155 node2.nodeName + ' | expected ' + n1 + ' to equal ' + n2);
156 }
157 }
158 // for text nodes compare value
159 if (node1.nodeType === 3) {
160 var nv1 = node1.nodeValue.replace(/\s/g, '');
161 var nv2 = node2.nodeValue.replace(/\s/g, '');
162 if (nv1 !== nv2) {
163 errors.push('nodeValue test failed | expected ' + nv1 + ' to equal ' +
164 nv2);
165 }
166 } else if (node1.nodeType === 1) {
167 // for element type nodes compare namespace, attributes, and children
168 // test namespace alias and uri
169 if (node1.prefix || node2.prefix) {
170 if (testPrefix) {
171 if (node1.prefix !== node2.prefix) {
172 errors.push('Prefix test failed for: ' + node1.nodeName +
173 ' | expected ' + node1.prefix + ' to equal ' + node2.prefix);
174 }
175 }
176 }
177 if (node1.namespaceURI || node2.namespaceURI) {
178 if (node1.namespaceURI !== node2.namespaceURI) {
179 errors.push('namespaceURI test failed for: ' + node1.nodeName +
180 ' | expected ' + node1.namespaceURI + ' to equal ' +
181 node2.namespaceURI);
182 }
183 }
184 // compare attributes - disregard xmlns given namespace handling above
185 var node1AttrLen = 0;
186 var node1Attr = {};
187 var node2AttrLen = 0;
188 var node2Attr = {};
189 var ga, ea, gn, en;
190 var i, ii;
191 if (node1.attributes) {
192 for (i = 0, ii = node1.attributes.length; i < ii; ++i) {
193 ga = node1.attributes[i];
194 if (ga.specified === undefined || ga.specified === true) {
195 if (ga.name.split(':').shift() != 'xmlns') {
196 gn = testPrefix ? ga.name : ga.name.split(':').pop();
197 node1Attr[gn] = ga;
198 ++node1AttrLen;
199 }
200 }
201 }
202 }
203 if (node2.attributes) {
204 for (i = 0, ii = node2.attributes.length; i < ii; ++i) {
205 ea = node2.attributes[i];
206 if (ea.specified === undefined || ea.specified === true) {
207 if (ea.name.split(':').shift() != 'xmlns') {
208 en = testPrefix ? ea.name : ea.name.split(':').pop();
209 node2Attr[en] = ea;
210 ++node2AttrLen;
211 }
212 }
213 }
214 }
215 if (node1AttrLen !== node2AttrLen) {
216 errors.push('Number of attributes test failed for: ' + node1.nodeName +
217 ' | expected ' + node1AttrLen + ' to equal ' + node2AttrLen);
218 }
219 for (var name in node1Attr) {
220 if (node2Attr[name] === undefined) {
221 errors.push('Attribute name ' + node1Attr[name].name +
222 ' expected for element ' + node1.nodeName);
223 break;
224 }
225 // test attribute namespace
226 // we do not care about the difference between an empty string and
227 // null for namespaceURI some tests will fail in IE9 otherwise
228 // see also
229 // http://msdn.microsoft.com/en-us/library/ff460650(v=vs.85).aspx
230 if ((node1Attr[name].namespaceURI || null) !==
231 (node2Attr[name].namespaceURI || null)) {
232 errors.push('namespaceURI attribute test failed for: ' +
233 node1.nodeName + ' | expected ' + node1Attr[name].namespaceURI +
234 ' to equal ' + node2Attr[name].namespaceURI);
235 }
236 if (node1Attr[name].value !== node2Attr[name].value) {
237 errors.push('Attribute value test failed for: ' + node1.nodeName +
238 ' | expected ' + node1Attr[name].value + ' to equal ' +
239 node2Attr[name].value);
240 }
241 }
242 // compare children
243 var node1ChildNodes = getChildNodes(node1, options);
244 var node2ChildNodes = getChildNodes(node2, options);
245 if (node1ChildNodes.length !== node2ChildNodes.length) {
246 // check if all child nodes are text, they could be split up in
247 // 4096 chunks
248 // if so, ignore the childnode count error
249 var allText = true;
250 var c, cc;
251 for (c = 0, cc = node1ChildNodes.length; c < cc; ++c) {
252 if (node1ChildNodes[c].nodeType !== 3) {
253 allText = false;
254 break;
255 }
256 }
257 for (c = 0, cc = node2ChildNodes.length; c < cc; ++c) {
258 if (node2ChildNodes[c].nodeType !== 3) {
259 allText = false;
260 break;
261 }
262 }
263 if (!allText) {
264 errors.push('Number of childNodes test failed for: ' +
265 node1.nodeName + ' | expected ' + node1ChildNodes.length +
266 ' to equal ' + node2ChildNodes.length);
267 }
268 }
269 // only compare if they are equal
270 if (node1ChildNodes.length === node2ChildNodes.length) {
271 for (var j = 0, jj = node1ChildNodes.length; j < jj; ++j) {
272 assertElementNodesEqual(
273 node1ChildNodes[j], node2ChildNodes[j], options, errors);
274 }
275 }
276 }
277 }
278
279
280 /**
281 * Checks if the XML document sort of equals another XML document.
282 * @param {Object} obj The other object.
283 * @param {{includeWhiteSpace: (boolean|undefined),
284 * ignoreElementOrder: (boolean|undefined)}=} options The options.
285 * @return {expect.Assertion} The assertion.
286 */
287 expect.Assertion.prototype.xmleql = function(obj, options) {
288 if (obj && obj.nodeType == 9) {
289 obj = obj.documentElement;
290 }
291 if (this.obj && this.obj.nodeType == 9) {
292 this.obj = this.obj.documentElement;
293 }
294 var errors = [];
295 assertElementNodesEqual(obj, this.obj, options, errors);
296 var result = (errors.length === 0);
297 this.assert(
298 !!result,
299 function() {
300 return 'expected ' + expect.stringify(this.obj) +
301 ' to sort of equal ' + expect.stringify(obj) + '\n' +
302 errors.join('\n');
303 },
304 function() {
305 return 'expected ' + expect.stringify(this.obj) +
306 ' to sort of not equal ' + expect.stringify(obj) + '\n' +
307 errors.join('\n');
308 });
309 return this;
310 };
311
312
313 /**
314 * Checks if the array sort of equals another array.
315 * @param {Object} obj The other object.
316 * @return {expect.Assertion} The assertion.
317 */
318 expect.Assertion.prototype.arreql = function(obj) {
319 this.assert(
320 ol.array.equals(this.obj, obj),
321 function() {
322 return 'expected ' + expect.stringify(this.obj) +
323 ' to sort of equal ' + expect.stringify(obj);
324 },
325 function() {
326 return 'expected ' + expect.stringify(this.obj) +
327 ' to sort of not equal ' + expect.stringify(obj);
328 });
329 return this;
330 };
331
332
333 /**
334 * Checks if the array sort of equals another array (allows NaNs to be equal).
335 * @param {Object} obj The other object.
336 * @return {expect.Assertion} The assertion.
337 */
338 expect.Assertion.prototype.arreqlNaN = function(obj) {
339 function compare(a, i) {
340 var b = obj[i];
341 return a === b || (typeof a === 'number' && typeof b === 'number' &&
342 isNaN(a) && isNaN(b));
343 }
344 this.assert(
345 this.obj.length === obj.length && this.obj.every(compare),
346 function() {
347 return 'expected ' + expect.stringify(this.obj) +
348 ' to sort of equal ' + expect.stringify(obj);
349 },
350 function() {
351 return 'expected ' + expect.stringify(this.obj) +
352 ' to sort of not equal ' + expect.stringify(obj);
353 });
354 return this;
355 };
356
357 global.createMapDiv = function(width, height) {
358 var target = document.createElement('div');
359 var style = target.style;
360 style.position = 'absolute';
361 style.left = '-1000px';
362 style.top = '-1000px';
363 style.width = width + 'px';
364 style.height = height + 'px';
365 document.body.appendChild(target);
366
367 return target;
368 };
369
370 global.disposeMap = function(map) {
371 var target = map.getTarget();
372 map.setTarget(null);
373 if (target && target.parentNode) {
374 target.parentNode.removeChild(target);
375 }
376 map.dispose();
377 };
378
379 global.assertWebGL = function(map) {
380 if (!ol.has.WEBGL) {
381 expect().fail('No WebGL support!');
382 }
383 };
384
385 function resembleCanvas(canvas, referenceImage, tolerance, done) {
386 if (showMap) {
387 var wrapper = document.createElement('div');
388 wrapper.style.width = canvas.width + 'px';
389 wrapper.style.height = canvas.height + 'px';
390 wrapper.appendChild(canvas);
391 document.body.appendChild(wrapper);
392 document.body.appendChild(document.createTextNode(referenceImage));
393 }
394 var width = canvas.width;
395 var height = canvas.height;
396 var image = new Image();
397 image.addEventListener('load', function() {
398 expect(image.width).to.be(width);
399 expect(image.height).to.be(height);
400 var referenceCanvas = document.createElement('CANVAS');
401 referenceCanvas.width = image.width;
402 referenceCanvas.height = image.height;
403 var referenceContext = referenceCanvas.getContext('2d');
404 referenceContext.drawImage(image, 0, 0, image.width, image.height);
405 var context = canvas.getContext('2d');
406 var output = context.createImageData(canvas.width, canvas.height);
407 var mismatchPx = pixelmatch(
408 context.getImageData(0, 0, width, height).data,
409 referenceContext.getImageData(0, 0, width, height).data,
410 output.data, width, height);
411 var mismatchPct = mismatchPx / (width * height) * 100;
412 if (showDiff && mismatchPct > tolerance) {
413 var diffCanvas = document.createElement('canvas');
414 diffCanvas.width = width;
415 diffCanvas.height = height;
416 diffCanvas.getContext('2d').putImageData(output, 0, 0);
417 document.body.appendChild(diffCanvas);
418 }
419 expect(mismatchPct).to.be.below(tolerance);
420 done();
421 });
422 image.addEventListener('error', function() {
423 expect().fail('Reference image could not be loaded');
424 done();
425 });
426 image.src = referenceImage;
427 }
428 global.resembleCanvas = resembleCanvas;
429
430 /**
431 * Assert that the given map resembles a reference image.
432 *
433 * @param {ol.PluggableMap} map A map using the canvas renderer.
434 * @param {string} referenceImage Path to the reference image.
435 * @param {number} tolerance The accepted mismatch tolerance.
436 * @param {function} done A callback to indicate that the test is done.
437 */
438 global.expectResemble = function(map, referenceImage, tolerance, done) {
439 map.render();
440 map.on('postcompose', function(event) {
441 if (event.frameState.animate) {
442 // make sure the tile-queue is empty
443 return;
444 }
445
446 var canvas;
447 if (event.glContext) {
448 var webglCanvas = event.glContext.getCanvas();
449 expect(webglCanvas).to.be.a(HTMLCanvasElement);
450
451 // draw the WebGL canvas on a new canvas, because we can not create
452 // a 2d context for that canvas because there is already a webgl context.
453 canvas = document.createElement('canvas');
454 canvas.width = webglCanvas.width;
455 canvas.height = webglCanvas.height;
456 canvas.getContext('2d').drawImage(webglCanvas, 0, 0,
457 webglCanvas.width, webglCanvas.height);
458 } else {
459 canvas = event.context.canvas;
460 }
461 expect(canvas).to.be.a(HTMLCanvasElement);
462
463 resembleCanvas(canvas, referenceImage, tolerance, done);
464 });
465 };
466
467 var features = {
468 ArrayBuffer: 'ArrayBuffer' in global,
469 'ArrayBuffer.isView': 'ArrayBuffer' in global && !!ArrayBuffer.isView,
470 FileReader: 'FileReader' in global,
471 Uint8ClampedArray: ('Uint8ClampedArray' in global),
472 WebGL: false
473 };
474
475 /**
476 * Allow tests to be skipped where certain features are not available. The
477 * provided key must be in the above `features` lookup. Keys should
478 * correspond to the feature that is required, but can be any string.
479 * @param {string} key The required feature name.
480 * @return {Object} An object with a `describe` function that will run tests
481 * if the required feature is available and skip them otherwise.
482 */
483 global.where = function(key) {
484 if (!(key in features)) {
485 throw new Error('where() called with unknown key: ' + key);
486 }
487 return {
488 describe: features[key] ? global.describe : global.xdescribe,
489 it: features[key] ? global.it : global.xit
490 };
491 };
492
493 // throw if anybody appends a div to the body and doesn't remove it
494 afterEach(function() {
495 var garbage = document.body.getElementsByTagName('div');
496 if (garbage.length) {
497 throw new Error('Found extra <div> elements in the body');
498 }
499 });
500
501})(window);