// avoid importing anything that results in an instanceof check // since these extensions are global, instanceof checks fail with modules (function (global) { function afterLoad(type, path, next) { const client = new XMLHttpRequest(); client.open('GET', path, true); client.onload = function () { let 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 const expect = global.expect; /** * Assert value is within some tolerance of a number. * @param {number} n Number. * @param {number} tol Tolerance. * @return {expect.Assertion} The assertion. */ 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; }; function getChildNodes(node, options) { // check whitespace if (options && options.includeWhiteSpace) { return node.childNodes; } else { const nodes = []; for (let i = 0, ii = node.childNodes.length; i < ii; i++) { const 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); } } } if (options && options.ignoreElementOrder) { nodes.sort(function (a, b) { return a.nodeName > b.nodeName ? 1 : a.nodeName < b.nodeName ? -1 : 0; }); } return nodes; } } function assertElementNodesEqual(node1, node2, options, errors) { const 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 { const n1 = node1.nodeName.split(':').pop(); const 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) { const nv1 = node1.nodeValue.replace(/\s/g, ''); const nv2 = node2.nodeValue.replace(/\s/g, ''); if (nv1 !== nv2) { errors.push( 'nodeValue test failed | expected ' + nv1 + ' to equal ' + nv2 ); } } else if (node1.nodeType === 1) { // for element type nodes compare namespace, attributes, and children // 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 let node1AttrLen = 0; const node1Attr = {}; let node2AttrLen = 0; const node2Attr = {}; let ga, ea, gn, en; let 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 ); } for (const name in node1Attr) { if (node2Attr[name] === undefined) { errors.push( 'Attribute name ' + node1Attr[name].name + ' expected for element ' + node1.nodeName ); break; } // 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 // https://docs.microsoft.com/en-us/openspecs/ie_standards/ms-dom2c/d6ad7f24-25f4-4ab0-a36b-32ddc08f413c 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 const node1ChildNodes = getChildNodes(node1, options); const 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 let allText = true; let 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 (let j = 0, jj = node1ChildNodes.length; j < jj; ++j) { assertElementNodesEqual( node1ChildNodes[j], node2ChildNodes[j], options, errors ); } } } } /** * @typedef {Object} XMLEqlOptions * @property {boolean} [includeWhiteSpace] IncludeWhiteSpace. * @property {boolean} [ignoreElementOrder] IgnoreElementOrder. */ /** * Checks if the XML document sort of equals another XML document. * @param {Object} obj The other object. * @param {XMLEqlOptions} [options] The options. * @return {expect.Assertion} The assertion. */ 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; } const errors = []; assertElementNodesEqual(obj, this.obj, options, errors); const 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; }; global.createMapDiv = function (width, height) { const target = document.createElement('div'); const style = target.style; style.position = 'absolute'; style.left = '-1000px'; style.top = '-1000px'; style.width = width + 'px'; style.height = height + 'px'; document.body.appendChild(target); return target; }; global.disposeMap = function (map) { const target = map.getTarget(); map.setTarget(null); if (target && target.parentNode) { target.parentNode.removeChild(target); } map.dispose(); }; const features = { ArrayBuffer: 'ArrayBuffer' in global, 'ArrayBuffer.isView': 'ArrayBuffer' in global && !!ArrayBuffer.isView, FileReader: 'FileReader' in global, Uint8ClampedArray: 'Uint8ClampedArray' in global, WebGL: false, }; /** * Allow tests to be skipped where certain features are not available. The * provided key must be in the above `features` lookup. Keys should * correspond to the feature that is required, but can be any string. * @param {string} key The required feature name. * @return {Object} An object with a `describe` function that will run tests * if the required feature is available and skip them otherwise. */ global.where = function (key) { if (!(key in features)) { throw new Error('where() called with unknown key: ' + key); } return { describe: features[key] ? global.describe : global.xdescribe, it: features[key] ? global.it : global.xit, }; }; // throw if anybody appends a div to the body and doesn't remove it afterEach(function () { const garbage = document.body.getElementsByTagName('div'); if (garbage.length) { throw new Error('Found extra
elements in the body'); } }); })(window);