Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Restore ES5 compatibility #452

Merged
merged 1 commit into from Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 33 additions & 0 deletions 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<T> | ({length:number, [number]: T})} list
* @param {function (item: T, index: number, list:Array<T> | ({length:number, [number]: T})):boolean} predicate
* @param {Partial<Pick<ArrayConstructor['prototype'], 'find'>>?} 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,
Expand Down Expand Up @@ -330,6 +362,7 @@ var NAMESPACE = freeze({
});

exports.assign = assign;
exports.find = find;
exports.freeze = freeze;
exports.HTML_BOOLEAN_ATTRIBUTES = HTML_BOOLEAN_ATTRIBUTES;
exports.HTML_RAW_TEXT_ELEMENTS = HTML_RAW_TEXT_ELEMENTS;
Expand Down
23 changes: 8 additions & 15 deletions lib/dom.js
@@ -1,6 +1,7 @@
'use strict';

var conventions = require('./conventions');
var find = conventions.find;
var isHTMLRawTextElement = conventions.isHTMLRawTextElement;
var isHTMLVoidElement = conventions.isHTMLVoidElement;
var MIME_TYPE = conventions.MIME_TYPE;
Expand Down Expand Up @@ -180,14 +181,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
Expand Down Expand Up @@ -832,10 +825,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));
}
/**
Expand Down Expand Up @@ -870,24 +863,24 @@ 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)) {
throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
}
}
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');
}
Expand Down
24 changes: 24 additions & 0 deletions 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);
});
});