diff --git a/CHANGELOG.md b/CHANGELOG.md index 9be924222..ca1e13cbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.5](https://github.com/xmldom/xmldom/compare/0.8.4...0.8.5) + +### Fixed + +- fix: Restore ES5 compatibility [`#452`](https://github.com/xmldom/xmldom/pull/452) / [`#453`](https://github.com/xmldom/xmldom/issues/453) + +Thank you, [@fengxinming](https://github.com/fengxinming), for your contributions + + ## [0.8.4](https://github.com/xmldom/xmldom/compare/0.8.3...0.8.4) ### Fixed diff --git a/lib/conventions.js b/lib/conventions.js index 0db33da67..953f0effe 100644 --- a/lib/conventions.js +++ b/lib/conventions.js @@ -1,5 +1,37 @@ 'use strict' +/** + * Ponyfill for `Array.prototype.find` which is only available in ES6 runtimes. + * + * Works with anything that has a `length` property and index access properties, including NodeList. + * + * @template {unknown} T + * @param {Array | ({length:number, [number]: T})} list + * @param {function (item: T, index: number, list:Array | ({length:number, [number]: T})):boolean} predicate + * @param {Partial>?} ac `Array.prototype` by default, + * allows injecting a custom implementation in tests + * @returns {T | undefined} + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find + * @see https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.find + */ +function find(list, predicate, ac) { + if (ac === undefined) { + ac = Array.prototype; + } + if (list && typeof ac.find === 'function') { + return ac.find.call(list, predicate); + } + for (var i = 0; i < list.length; i++) { + if (Object.prototype.hasOwnProperty.call(list, i)) { + var item = list[i]; + if (predicate.call(undefined, item, i, list)) { + return item; + } + } + } +} + /** * "Shallow freezes" an object to render it immutable. * Uses `Object.freeze` if available, @@ -165,6 +197,7 @@ var NAMESPACE = freeze({ }) exports.assign = assign; +exports.find = find; exports.freeze = freeze; exports.MIME_TYPE = MIME_TYPE; exports.NAMESPACE = NAMESPACE; diff --git a/lib/dom.js b/lib/dom.js index fd1518d57..b9bec2b72 100644 --- a/lib/dom.js +++ b/lib/dom.js @@ -1,5 +1,6 @@ var conventions = require("./conventions"); +var find = conventions.find; var NAMESPACE = conventions.NAMESPACE; /** @@ -176,14 +177,6 @@ NodeList.prototype = { } return buf.join(''); }, - /** - * @private - * @param {function (Node):boolean} predicate - * @returns {Node | undefined} - */ - find: function (predicate) { - return Array.prototype.find.call(this, predicate); - }, /** * @private * @param {function (Node):boolean} predicate @@ -748,10 +741,10 @@ function isTextNode(node) { */ function isElementInsertionPossible(doc, child) { var parentChildNodes = doc.childNodes || []; - if (parentChildNodes.find(isElementNode) || isDocTypeNode(child)) { + if (find(parentChildNodes, isElementNode) || isDocTypeNode(child)) { return false; } - var docTypeNode = parentChildNodes.find(isDocTypeNode); + var docTypeNode = find(parentChildNodes, isDocTypeNode); return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child)); } /** @@ -786,8 +779,8 @@ function _insertBefore(parent, node, child) { var nodeChildNodes = node.childNodes || []; if (parent.nodeType === Node.DOCUMENT_NODE) { if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { - let nodeChildElements = nodeChildNodes.filter(isElementNode); - if (nodeChildElements.length > 1 || nodeChildNodes.find(isTextNode)) { + var nodeChildElements = nodeChildNodes.filter(isElementNode); + if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) { throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment'); } if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) { @@ -795,15 +788,15 @@ function _insertBefore(parent, node, child) { } } if (isElementNode(node)) { - if (parentChildNodes.find(isElementNode) || !isElementInsertionPossible(parent, child)) { + if (find(parentChildNodes, isElementNode) || !isElementInsertionPossible(parent, child)) { throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype'); } } if (isDocTypeNode(node)) { - if (parentChildNodes.find(isDocTypeNode)) { + if (find(parentChildNodes, isDocTypeNode)) { throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed'); } - let parentElementChild = parentChildNodes.find(isElementNode); + var parentElementChild = find(parentChildNodes, isElementNode); if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) { throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element'); } diff --git a/package.json b/package.json index 0366ac951..43fc636bb 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,9 @@ "stryker": "stryker run", "stryker:dry-run": "stryker run -m '' --reporters progress", "test": "jest", + "testrelease": "npm test && npm run lint", "version": "./changelog-has-version.sh", - "release": "np --no-yarn" + "release": "np --no-yarn --test-script testrelease" }, "engines": { "node": ">=10.0.0" diff --git a/test/conventions/find.test.js b/test/conventions/find.test.js new file mode 100644 index 000000000..76b5db285 --- /dev/null +++ b/test/conventions/find.test.js @@ -0,0 +1,24 @@ +'use strict'; + +const { find } = require('../../lib/conventions'); + +describe('find', () => { + it('should work in node without pasing an ArrayConstructor', () => { + const predicate = jest.fn((item) => item === 'b'); + const list = ['a', 'b', 'c']; + + expect(find(list, predicate)).toBe('b'); + expect(predicate).toHaveBeenCalledTimes(2); + expect(predicate).toHaveBeenCalledWith('a', 0, list); + expect(predicate).toHaveBeenCalledWith('b', 1, list); + }); + it('should work when ArrayConstructor does not provide find', () => { + const predicate = jest.fn((item) => item === 'b'); + const list = ['a', 'b', 'c']; + + expect(find(list, predicate, {})).toBe('b'); + expect(predicate).toHaveBeenCalledTimes(2); + expect(predicate).toHaveBeenCalledWith('a', 0, list); + expect(predicate).toHaveBeenCalledWith('b', 1, list); + }); +});