// FIXME remove afterLoadXml as it uses the wrong XML parser on IE9 // helper functions for async testing (function(global) { function afterLoad(type, path, next) { var client = new XMLHttpRequest(); client.open('GET', path, true); client.onload = function() { var data; if (type === 'xml') { data = client.responseXML; } else { data = client.responseText; } if (!data) { throw new Error(path + ' loading failed: ' + client.status); } next(data); }; client.send(); } /** * @param {string} path Relative path to file (e.g. 'spec/ol/foo.json'). * @param {function(Object)} next Function to call with response object on * success. On failure, an error is thrown with the reason. */ global.afterLoadJson = function(path, next) { afterLoad('json', path, next); }; /** * @param {string} path Relative path to file (e.g. 'spec/ol/foo.txt'). * @param {function(string)} next Function to call with response text on * success. On failure, an error is thrown with the reason. */ global.afterLoadText = function(path, next) { afterLoad('text', path, next); }; /** * @param {string} path Relative path to file (e.g. 'spec/ol/foo.xml'). * @param {function(Document)} next Function to call with response xml on * success. On failure, an error is thrown with the reason. */ global.afterLoadXml = function(path, next) { afterLoad('xml', path, next); }; // extensions to expect.js var expect = global.expect; /** * Assert value is within some tolerance of a number. * @param {Number} n Number. * @param {Number} tol Tolerance. */ expect.Assertion.prototype.roughlyEqual = function(n, tol) { this.assert( Math.abs(this.obj - n) <= tol, function() { return 'expected ' + expect.stringify(this.obj) + ' to be within ' + tol + ' of ' + n; }, function() { return 'expected ' + expect.stringify(this.obj) + ' not to be within ' + tol + ' of ' + n; }); return this; }; /** * Assert that a sinon spy was called. */ expect.Assertion.prototype.called = function() { this.assert( this.obj.called, function() { return 'expected ' + expect.stringify(this.obj) + ' to be called'; }, function() { return 'expected ' + expect.stringify(this.obj) + ' not to be called'; }); return this; }; function getChildNodes(node, options) { // check whitespace if (options && options.includeWhiteSpace) { return node.childNodes; } else { var nodes = []; for (var i = 0, ii = node.childNodes.length; i < ii; i++) { var child = node.childNodes[i]; if (child.nodeType == 1) { // element node, add it nodes.push(child); } else if (child.nodeType == 3) { // text node, add if non empty if (child.nodeValue && child.nodeValue.replace(/^\s*(.*?)\s*$/, '$1') !== '') { nodes.push(child); } } } return nodes; } } function assertElementNodesEqual(node1, node2, options, errors) { var testPrefix = (options && options.prefix === true); if (node1.nodeType !== node2.nodeType) { errors.push('nodeType test failed for: ' + node1.nodeName + ' | ' + node2.nodeName + ' | expected ' + node1.nodeType + ' to equal ' + node2.nodeType); } if (testPrefix) { if (node1.nodeName !== node2.nodeName) { errors.push('nodeName test failed for: ' + node1.nodeName + ' | ' + node2.nodeName + ' | expected ' + node1.nodeName + ' to equal ' + node2.nodeName); } } else { var n1 = node1.nodeName.split(':').pop(); var n2 = node2.nodeName.split(':').pop(); if (n1 !== n2) { errors.push('nodeName test failed for: ' + node1.nodeName + ' | ' + node2.nodeName + ' | expected ' + n1 + ' to equal ' + n2); } } // for text nodes compare value if (node1.nodeType === 3) { var nv1 = node1.nodeValue.replace(/\s/g, ''); var nv2 = node2.nodeValue.replace(/\s/g, ''); if (nv1 !== nv2) { errors.push('nodeValue test failed | expected ' + nv1 + ' to equal ' + nv2); } } // for element type nodes compare namespace, attributes, and children else if (node1.nodeType === 1) { // test namespace alias and uri if (node1.prefix || node2.prefix) { if (testPrefix) { if (node1.prefix !== node2.prefix) { errors.push('Prefix test failed for: ' + node1.nodeName + ' | expected ' + node1.prefix + ' to equal ' + node2.prefix); } } } if (node1.namespaceURI || node2.namespaceURI) { if (node1.namespaceURI !== node2.namespaceURI) { errors.push('namespaceURI test failed for: ' + node1.nodeName + ' | expected ' + node1.namespaceURI + ' to equal ' + node2.namespaceURI); } } // compare attributes - disregard xmlns given namespace handling above var node1AttrLen = 0; var node1Attr = {}; var node2AttrLen = 0; var node2Attr = {}; var ga, ea, gn, en; var i, ii; if (node1.attributes) { for (i = 0, ii = node1.attributes.length; i < ii; ++i) { ga = node1.attributes[i]; if (ga.specified === undefined || ga.specified === true) { if (ga.name.split(':').shift() != 'xmlns') { gn = testPrefix ? ga.name : ga.name.split(':').pop(); node1Attr[gn] = ga; ++node1AttrLen; } } } } if (node2.attributes) { for (i = 0, ii = node2.attributes.length; i < ii; ++i) { ea = node2.attributes[i]; if (ea.specified === undefined || ea.specified === true) { if (ea.name.split(':').shift() != 'xmlns') { en = testPrefix ? ea.name : ea.name.split(':').pop(); node2Attr[en] = ea; ++node2AttrLen; } } } } if (node1AttrLen !== node2AttrLen) { errors.push('Number of attributes test failed for: ' + node1.nodeName + ' | expected ' + node1AttrLen + ' to equal ' + node2AttrLen); } var gv, ev; for (var name in node1Attr) { if (node2Attr[name] === undefined) { errors.push('Attribute name ' + node1Attr[name].name + ' expected for element ' + node1.nodeName); } // test attribute namespace // we do not care about the difference between an empty string and // null for namespaceURI some tests will fail in IE9 otherwise // see also // http://msdn.microsoft.com/en-us/library/ff460650(v=vs.85).aspx if ((node1Attr[name].namespaceURI || null) !== (node2Attr[name].namespaceURI || null)) { errors.push('namespaceURI attribute test failed for: ' + node1.nodeName + ' | expected ' + node1Attr[name].namespaceURI + ' to equal ' + node2Attr[name].namespaceURI); } if (node1Attr[name].value !== node2Attr[name].value) { errors.push('Attribute value test failed for: ' + node1.nodeName + ' | expected ' + node1Attr[name].value + ' to equal ' + node2Attr[name].value); } } // compare children var node1ChildNodes = getChildNodes(node1, options); var node2ChildNodes = getChildNodes(node2, options); if (node1ChildNodes.length !== node2ChildNodes.length) { // check if all child nodes are text, they could be split up in // 4096 chunks // if so, ignore the childnode count error var allText = true; var c, cc; for (c = 0, cc = node1ChildNodes.length; c < cc; ++c) { if (node1ChildNodes[c].nodeType !== 3) { allText = false; break; } } for (c = 0, cc = node2ChildNodes.length; c < cc; ++c) { if (node2ChildNodes[c].nodeType !== 3) { allText = false; break; } } if (!allText) { errors.push('Number of childNodes test failed for: ' + node1.nodeName + ' | expected ' + node1ChildNodes.length + ' to equal ' + node2ChildNodes.length); } } // only compare if they are equal if (node1ChildNodes.length === node2ChildNodes.length) { for (var j = 0, jj = node1ChildNodes.length; j < jj; ++j) { assertElementNodesEqual( node1ChildNodes[j], node2ChildNodes[j], options, errors); } } } } /** * Checks if the XML document sort of equals another XML document. */ expect.Assertion.prototype.xmleql = function(obj, options) { if (obj && obj.nodeType == 9) { obj = obj.documentElement; } if (this.obj && this.obj.nodeType == 9) { this.obj = this.obj.documentElement; } var errors = []; assertElementNodesEqual(obj, this.obj, options, errors); var result = (errors.length === 0); this.assert( !!result, function() { return 'expected ' + expect.stringify(this.obj) + ' to sort of equal ' + expect.stringify(obj) + '\n' + errors.join('\n'); }, function() { return 'expected ' + expect.stringify(this.obj) + ' to sort of not equal ' + expect.stringify(obj) + '\n' + errors.join('\n'); }); return this; }; /** * Checks if the array sort of equals another array. */ expect.Assertion.prototype.arreql = function(obj) { this.assert( goog.array.equals(this.obj, obj), function() { return 'expected ' + expect.stringify(this.obj) + ' to sort of equal ' + expect.stringify(obj); }, function() { return 'expected ' + expect.stringify(this.obj) + ' to sort of not equal ' + expect.stringify(obj); }); return this; }; /** * Checks if the array sort of equals another array (allows NaNs to be equal). */ expect.Assertion.prototype.arreqlNaN = function(obj) { function compare(a, b) { return a === b || (typeof a === 'number' && typeof b === 'number' && isNaN(a) && isNaN(b)); } this.assert( goog.array.equals(this.obj, obj, compare), function() { return 'expected ' + expect.stringify(this.obj) + ' to sort of equal ' + expect.stringify(obj); }, function() { return 'expected ' + expect.stringify(this.obj) + ' to sort of not equal ' + expect.stringify(obj); }); return this; }; })(this);