assertions-recorder
Version:
a module for publishing all dom and track events to the assertions recorder application
1,599 lines (1,377 loc) • 64.6 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
typeof define === 'function' && define.amd ? define(factory) :
(factory());
}(this, (function () { 'use strict';
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
/**
postmate - A powerful, simple, promise-based postMessage library
@version v1.4.2
@link https://github.com/dollarshaveclub/postmate
@author Jacob Kelley <jakie8@gmail.com>
@license MIT
**/
/**
* The type of messages our frames our sending
* @type {String}
*/
var messsageType = 'application/x-postmate-v1+json';
/**
* hasOwnProperty()
* @type {Function}
* @return {Boolean}
*/
var hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* The maximum number of attempts to send a handshake request to the parent
* @type {Number}
*/
var maxHandshakeRequests = 5;
/**
* A unique message ID that is used to ensure responses are sent to the correct requests
* @type {Number}
*/
var _messageId = 0;
/**
* Increments and returns a message ID
* @return {Number} A unique ID for a message
*/
var messageId = function messageId() {
return ++_messageId;
};
/**
* Takes a URL and returns the origin
* @param {String} url The full URL being requested
* @return {String} The URLs origin
*/
var resolveOrigin = function resolveOrigin(url) {
var a = document.createElement('a');
a.href = url;
var protocol = a.protocol.length > 4 ? a.protocol : window.location.protocol;
var host = a.host.length ? a.port === '80' || a.port === '443' ? a.hostname : a.host : window.location.host;
return a.origin || protocol + "//" + host;
};
/**
* Ensures that a message is safe to interpret
* @param {Object} message The postmate message being sent
* @param {String} allowedOrigin The whitelisted origin
* @return {Boolean}
*/
var sanitize = function sanitize(message, allowedOrigin) {
if (message.origin !== allowedOrigin) return false;
if (_typeof(message.data) !== 'object') return false;
if (!('postmate' in message.data)) return false;
if (message.data.type !== messsageType) return false;
if (!{
'handshake-reply': 1,
call: 1,
emit: 1,
reply: 1,
request: 1
}[message.data.postmate]) return false;
return true;
};
/**
* Takes a model, and searches for a value by the property
* @param {Object} model The dictionary to search against
* @param {String} property A path within a dictionary (i.e. 'window.location.href')
* @param {Object} data Additional information from the get request that is
* passed to functions in the child model
* @return {Promise}
*/
var resolveValue = function resolveValue(model, property) {
var unwrappedContext = typeof model[property] === 'function' ? model[property]() : model[property];
return Postmate.Promise.resolve(unwrappedContext);
};
/**
* Composes an API to be used by the parent
* @param {Object} info Information on the consumer
*/
var ParentAPI =
/*#__PURE__*/
function () {
function ParentAPI(info) {
var _this = this;
this.parent = info.parent;
this.frame = info.frame;
this.child = info.child;
this.childOrigin = info.childOrigin;
this.events = {};
this.listener = function (e) {
var _ref = ((e || {}).data || {}).value || {},
data = _ref.data,
name = _ref.name;
if (e.data.postmate === 'emit') {
if (name in _this.events) {
_this.events[name].call(_this, data);
}
}
};
this.parent.addEventListener('message', this.listener, false);
}
var _proto = ParentAPI.prototype;
_proto.get = function get(property) {
var _this2 = this;
return new Postmate.Promise(function (resolve) {
// Extract data from response and kill listeners
var uid = messageId();
var transact = function transact(e) {
if (e.data.uid === uid && e.data.postmate === 'reply') {
_this2.parent.removeEventListener('message', transact, false);
resolve(e.data.value);
}
}; // Prepare for response from Child...
_this2.parent.addEventListener('message', transact, false); // Then ask child for information
_this2.child.postMessage({
postmate: 'request',
type: messsageType,
property: property,
uid: uid
}, _this2.childOrigin);
});
};
_proto.call = function call(property, data) {
// Send information to the child
this.child.postMessage({
postmate: 'call',
type: messsageType,
property: property,
data: data
}, this.childOrigin);
};
_proto.on = function on(eventName, callback) {
this.events[eventName] = callback;
};
_proto.destroy = function destroy() {
window.removeEventListener('message', this.listener, false);
this.frame.parentNode.removeChild(this.frame);
};
return ParentAPI;
}();
/**
* Composes an API to be used by the child
* @param {Object} info Information on the consumer
*/
var ChildAPI =
/*#__PURE__*/
function () {
function ChildAPI(info) {
var _this3 = this;
this.model = info.model;
this.parent = info.parent;
this.parentOrigin = info.parentOrigin;
this.child = info.child;
this.child.addEventListener('message', function (e) {
if (!sanitize(e, _this3.parentOrigin)) return;
var _e$data = e.data,
property = _e$data.property,
uid = _e$data.uid,
data = _e$data.data;
if (e.data.postmate === 'call') {
if (property in _this3.model && typeof _this3.model[property] === 'function') {
_this3.model[property].call(_this3, data);
}
return;
} // Reply to Parent
resolveValue(_this3.model, property).then(function (value) {
return e.source.postMessage({
property: property,
postmate: 'reply',
type: messsageType,
uid: uid,
value: value
}, e.origin);
});
});
}
var _proto2 = ChildAPI.prototype;
_proto2.emit = function emit(name, data) {
this.parent.postMessage({
postmate: 'emit',
type: messsageType,
value: {
name: name,
data: data
}
}, this.parentOrigin);
};
return ChildAPI;
}();
/**
* The entry point of the Parent.
* @type {Class}
*/
var Postmate =
/*#__PURE__*/
function () {
// eslint-disable-line no-undef
// Internet Explorer craps itself
/**
* Sets options related to the Parent
* @param {Object} userOptions The element to inject the frame into, and the url
* @return {Promise}
*/
function Postmate(_temp) {
var _ref2 = _temp === void 0 ? userOptions : _temp,
_ref2$container = _ref2.container,
container = _ref2$container === void 0 ? typeof container !== 'undefined' ? container : document.body : _ref2$container,
model = _ref2.model,
url = _ref2.url;
// eslint-disable-line no-undef
this.parent = window;
this.frame = document.createElement('iframe');
container.appendChild(this.frame);
this.child = this.frame.contentWindow || this.frame.contentDocument.parentWindow;
this.model = model || {};
return this.sendHandshake(url);
}
/**
* Begins the handshake strategy
* @param {String} url The URL to send a handshake request to
* @return {Promise} Promise that resolves when the handshake is complete
*/
var _proto3 = Postmate.prototype;
_proto3.sendHandshake = function sendHandshake(url) {
var _this4 = this;
var childOrigin = resolveOrigin(url);
var attempt = 0;
var responseInterval;
return new Postmate.Promise(function (resolve, reject) {
var reply = function reply(e) {
if (!sanitize(e, childOrigin)) return false;
if (e.data.postmate === 'handshake-reply') {
clearInterval(responseInterval);
_this4.parent.removeEventListener('message', reply, false);
_this4.childOrigin = e.origin;
return resolve(new ParentAPI(_this4));
} // Might need to remove since parent might be receiving different messages
return reject('Failed handshake');
};
_this4.parent.addEventListener('message', reply, false);
var doSend = function doSend() {
attempt++;
_this4.child.postMessage({
postmate: 'handshake',
type: messsageType,
model: _this4.model
}, childOrigin);
if (attempt === maxHandshakeRequests) {
clearInterval(responseInterval);
}
};
var loaded = function loaded() {
doSend();
responseInterval = setInterval(doSend, 500);
};
if (_this4.frame.attachEvent) {
_this4.frame.attachEvent('onload', loaded);
} else {
_this4.frame.onload = loaded;
}
_this4.frame.src = url;
});
};
return Postmate;
}();
/**
* The entry point of the Child
* @type {Class}
*/
Postmate.debug = false;
Postmate.Promise = function () {
try {
return window ? window.Promise : Promise;
} catch (e) {
return null;
}
}();
Postmate.Model =
/*#__PURE__*/
function () {
/**
* Initializes the child, model, parent, and responds to the Parents handshake
* @param {Object} model Hash of values, functions, or promises
* @return {Promise} The Promise that resolves when the handshake has been received
*/
function Model(model) {
this.child = window;
this.model = model;
this.parent = this.child.parent;
return this.sendHandshakeReply();
}
/**
* Responds to a handshake initiated by the Parent
* @return {Promise} Resolves an object that exposes an API for the Child
*/
var _proto4 = Model.prototype;
_proto4.sendHandshakeReply = function sendHandshakeReply() {
var _this5 = this;
return new Postmate.Promise(function (resolve, reject) {
var shake = function shake(e) {
if (!e.data.postmate) {
return;
}
if (e.data.postmate === 'handshake') {
_this5.child.removeEventListener('message', shake, false);
e.source.postMessage({
postmate: 'handshake-reply',
type: messsageType
}, e.origin);
_this5.parentOrigin = e.origin; // Extend model with the one provided by the parent
var defaults = e.data.model;
if (defaults) {
var keys = Object.keys(defaults);
for (var i = 0; i < keys.length; i++) {
if (hasOwnProperty.call(defaults, keys[i])) {
_this5.model[keys[i]] = defaults[keys[i]];
}
}
}
return resolve(new ChildAPI(_this5));
}
return reject('Handshake Reply Failed');
};
_this5.child.addEventListener('message', shake, false);
});
};
return Model;
}();
var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function unwrapExports (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var adapt_1 = createCommonjsModule(function (module, exports) {
var _typeof2 = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
Object.defineProperty(exports, "__esModule", {
value: true
});
var _typeof = typeof Symbol === "function" && _typeof2(Symbol.iterator) === "symbol" ? function (obj) {
return typeof obj === "undefined" ? "undefined" : _typeof2(obj);
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj === "undefined" ? "undefined" : _typeof2(obj);
};
var _slicedToArray = function () {
function sliceIterator(arr, i) {
var _arr = [];var _n = true;var _d = false;var _e = undefined;try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;_e = err;
} finally {
try {
if (!_n && _i["return"]) _i["return"]();
} finally {
if (_d) throw _e;
}
}return _arr;
}return function (arr, i) {
if (Array.isArray(arr)) {
return arr;
} else if (Symbol.iterator in Object(arr)) {
return sliceIterator(arr, i);
} else {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
};
}();
exports.default = adapt;
/**
* # Adapt
*
* Check and extend the environment for universal usage
*/
/**
* Modify the context based on the environment
*
* @param {HTMLELement} element - [description]
* @param {Object} options - [description]
* @return {boolean} - [description]
*/
function adapt(element, options) {
// detect environment setup
if (commonjsGlobal.document) {
return false;
}
var context = options.context;
commonjsGlobal.document = context || function () {
var root = element;
while (root.parent) {
root = root.parent;
}
return root;
}();
// https://github.com/fb55/domhandler/blob/master/index.js#L75
var ElementPrototype = Object.getPrototypeOf(commonjsGlobal.document);
// alternative descriptor to access elements with filtering invalid elements (e.g. textnodes)
if (!Object.getOwnPropertyDescriptor(ElementPrototype, 'childTags')) {
Object.defineProperty(ElementPrototype, 'childTags', {
enumerable: true,
get: function get() {
return this.children.filter(function (node) {
// https://github.com/fb55/domelementtype/blob/master/index.js#L12
return node.type === 'tag' || node.type === 'script' || node.type === 'style';
});
}
});
}
if (!Object.getOwnPropertyDescriptor(ElementPrototype, 'attributes')) {
// https://developer.mozilla.org/en-US/docs/Web/API/Element/attributes
// https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap
Object.defineProperty(ElementPrototype, 'attributes', {
enumerable: true,
get: function get() {
var attribs = this.attribs;
var attributesNames = Object.keys(attribs);
var NamedNodeMap = attributesNames.reduce(function (attributes, attributeName, index) {
attributes[index] = {
name: attributeName,
value: attribs[attributeName]
};
return attributes;
}, {});
Object.defineProperty(NamedNodeMap, 'length', {
enumerable: false,
configurable: false,
value: attributesNames.length
});
return NamedNodeMap;
}
});
}
if (!ElementPrototype.getAttribute) {
// https://docs.webplatform.org/wiki/dom/Element/getAttribute
// https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute
ElementPrototype.getAttribute = function (name) {
return this.attribs[name] || null;
};
}
if (!ElementPrototype.getElementsByTagName) {
// https://docs.webplatform.org/wiki/dom/Document/getElementsByTagName
// https://developer.mozilla.org/en-US/docs/Web/API/Element/getElementsByTagName
ElementPrototype.getElementsByTagName = function (tagName) {
var HTMLCollection = [];
traverseDescendants(this.childTags, function (descendant) {
if (descendant.name === tagName || tagName === '*') {
HTMLCollection.push(descendant);
}
});
return HTMLCollection;
};
}
if (!ElementPrototype.getElementsByClassName) {
// https://docs.webplatform.org/wiki/dom/Document/getElementsByClassName
// https://developer.mozilla.org/en-US/docs/Web/API/Element/getElementsByClassName
ElementPrototype.getElementsByClassName = function (className) {
var names = className.trim().replace(/\s+/g, ' ').split(' ');
var HTMLCollection = [];
traverseDescendants([this], function (descendant) {
var descendantClassName = descendant.attribs.class;
if (descendantClassName && names.every(function (name) {
return descendantClassName.indexOf(name) > -1;
})) {
HTMLCollection.push(descendant);
}
});
return HTMLCollection;
};
}
if (!ElementPrototype.querySelectorAll) {
// https://docs.webplatform.org/wiki/css/selectors_api/querySelectorAll
// https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll
ElementPrototype.querySelectorAll = function (selectors) {
var _this = this;
selectors = selectors.replace(/(>)(\S)/g, '$1 $2').trim(); // add space for '>' selector
// using right to left execution => https://github.com/fb55/css-select#how-does-it-work
var instructions = getInstructions(selectors);
var discover = instructions.shift();
var total = instructions.length;
return discover(this).filter(function (node) {
var step = 0;
while (step < total) {
node = instructions[step](node, _this);
if (!node) {
// hierarchy doesn't match
return false;
}
step += 1;
}
return true;
});
};
}
if (!ElementPrototype.contains) {
// https://developer.mozilla.org/en-US/docs/Web/API/Node/contains
ElementPrototype.contains = function (element) {
var inclusive = false;
traverseDescendants([this], function (descendant, done) {
if (descendant === element) {
inclusive = true;
done();
}
});
return inclusive;
};
}
return true;
}
/**
* Retrieve transformation steps
*
* @param {Array.<string>} selectors - [description]
* @return {Array.<Function>} - [description]
*/
function getInstructions(selectors) {
return selectors.split(' ').reverse().map(function (selector, step) {
var discover = step === 0;
var _selector$split = selector.split(':');
var _selector$split2 = _slicedToArray(_selector$split, 2);
var type = _selector$split2[0];
var pseudo = _selector$split2[1];
var validate = null;
var instruction = null;
(function () {
switch (true) {
// child: '>'
case />/.test(type):
instruction = function checkParent(node) {
return function (validate) {
return validate(node.parent) && node.parent;
};
};
break;
// class: '.'
case /^\./.test(type):
var names = type.substr(1).split('.');
validate = function validate(node) {
var nodeClassName = node.attribs.class;
return nodeClassName && names.every(function (name) {
return nodeClassName.indexOf(name) > -1;
});
};
instruction = function checkClass(node, root) {
if (discover) {
return node.getElementsByClassName(names.join(' '));
}
return typeof node === 'function' ? node(validate) : getAncestor(node, root, validate);
};
break;
// attribute: '[key="value"]'
case /^\[/.test(type):
var _type$replace$split = type.replace(/\[|\]|"/g, '').split('=');
var _type$replace$split2 = _slicedToArray(_type$replace$split, 2);
var attributeKey = _type$replace$split2[0];
var attributeValue = _type$replace$split2[1];
validate = function validate(node) {
var hasAttribute = Object.keys(node.attribs).indexOf(attributeKey) > -1;
if (hasAttribute) {
// regard optional attributeValue
if (!attributeValue || node.attribs[attributeKey] === attributeValue) {
return true;
}
}
return false;
};
instruction = function checkAttribute(node, root) {
if (discover) {
var _ret2 = function () {
var NodeList = [];
traverseDescendants([node], function (descendant) {
if (validate(descendant)) {
NodeList.push(descendant);
}
});
return {
v: NodeList
};
}();
if ((typeof _ret2 === 'undefined' ? 'undefined' : _typeof(_ret2)) === "object") return _ret2.v;
}
return typeof node === 'function' ? node(validate) : getAncestor(node, root, validate);
};
break;
// id: '#'
case /^#/.test(type):
var id = type.substr(1);
validate = function validate(node) {
return node.attribs.id === id;
};
instruction = function checkId(node, root) {
if (discover) {
var _ret3 = function () {
var NodeList = [];
traverseDescendants([node], function (descendant, done) {
if (validate(descendant)) {
NodeList.push(descendant);
done();
}
});
return {
v: NodeList
};
}();
if ((typeof _ret3 === 'undefined' ? 'undefined' : _typeof(_ret3)) === "object") return _ret3.v;
}
return typeof node === 'function' ? node(validate) : getAncestor(node, root, validate);
};
break;
// universal: '*'
case /\*/.test(type):
validate = function validate(node) {
return true;
};
instruction = function checkUniversal(node, root) {
if (discover) {
var _ret4 = function () {
var NodeList = [];
traverseDescendants([node], function (descendant) {
return NodeList.push(descendant);
});
return {
v: NodeList
};
}();
if ((typeof _ret4 === 'undefined' ? 'undefined' : _typeof(_ret4)) === "object") return _ret4.v;
}
return typeof node === 'function' ? node(validate) : getAncestor(node, root, validate);
};
break;
// tag: '...'
default:
validate = function validate(node) {
return node.name === type;
};
instruction = function checkTag(node, root) {
if (discover) {
var _ret5 = function () {
var NodeList = [];
traverseDescendants([node], function (descendant) {
if (validate(descendant)) {
NodeList.push(descendant);
}
});
return {
v: NodeList
};
}();
if ((typeof _ret5 === 'undefined' ? 'undefined' : _typeof(_ret5)) === "object") return _ret5.v;
}
return typeof node === 'function' ? node(validate) : getAncestor(node, root, validate);
};
}
})();
if (!pseudo) {
return instruction;
}
var rule = pseudo.match(/-(child|type)\((\d+)\)$/);
var kind = rule[1];
var index = parseInt(rule[2], 10) - 1;
var validatePseudo = function validatePseudo(node) {
if (node) {
var compareSet = node.parent.childTags;
if (kind === 'type') {
compareSet = compareSet.filter(validate);
}
var nodeIndex = compareSet.findIndex(function (child) {
return child === node;
});
if (nodeIndex === index) {
return true;
}
}
return false;
};
return function enhanceInstruction(node) {
var match = instruction(node);
if (discover) {
return match.reduce(function (NodeList, matchedNode) {
if (validatePseudo(matchedNode)) {
NodeList.push(matchedNode);
}
return NodeList;
}, []);
}
return validatePseudo(match) && match;
};
});
}
/**
* Walking recursive to invoke callbacks
*
* @param {Array.<HTMLElement>} nodes - [description]
* @param {Function} handler - [description]
*/
function traverseDescendants(nodes, handler) {
nodes.forEach(function (node) {
var progress = true;
handler(node, function () {
return progress = false;
});
if (node.childTags && progress) {
traverseDescendants(node.childTags, handler);
}
});
}
/**
* Bubble up from bottom to top
*
* @param {HTMLELement} node - [description]
* @param {HTMLELement} root - [description]
* @param {Function} validate - [description]
* @return {HTMLELement} - [description]
*/
function getAncestor(node, root, validate) {
while (node.parent) {
node = node.parent;
if (validate(node)) {
return node;
}
if (node === root) {
break;
}
}
return null;
}
module.exports = exports['default'];
});
var adapt = unwrapExports(adapt_1);
var adapt$1 = /*#__PURE__*/Object.freeze({
default: adapt,
__moduleExports: adapt_1
});
var utilities = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.convertNodeList = convertNodeList;
exports.escapeValue = escapeValue;
/**
* # Utilities
*
* Convenience helpers
*/
/**
* Create an array with the DOM nodes of the list
*
* @param {NodeList} nodes - [description]
* @return {Array.<HTMLElement>} - [description]
*/
function convertNodeList(nodes) {
var length = nodes.length;
var arr = new Array(length);
for (var i = 0; i < length; i++) {
arr[i] = nodes[i];
}
return arr;
}
/**
* Escape special characters like quotes and backslashes
*
* Description of valid characters: https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector#Notes
*
* @param {String?} value - [description]
* @return {String} - [description]
*/
function escapeValue(value) {
return value && value.replace(/['"`\\/:\?&!#$%^()[\]{|}*+;,.<=>@~]/g, '\\$&');
}
});
var utilities$1 = unwrapExports(utilities);
var utilities_1 = utilities.convertNodeList;
var utilities_2 = utilities.escapeValue;
var utilities$2 = /*#__PURE__*/Object.freeze({
default: utilities$1,
__moduleExports: utilities,
convertNodeList: utilities_1,
escapeValue: utilities_2
});
var _utilities = ( utilities$2 && utilities$1 ) || utilities$2;
var match_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = match;
var defaultIgnore = {
attribute: function attribute(attributeName) {
return ['style', 'data-reactid', 'data-react-checksum'].indexOf(attributeName) > -1;
}
};
/**
* Get the path of the element
*
* @param {HTMLElement} node - [description]
* @param {Object} options - [description]
* @return {string} - [description]
*/
/**
* # Match
*
* Retrieves selector
*/
function match(node, options) {
var _options$root = options.root;
var root = _options$root === undefined ? document : _options$root;
var _options$skip = options.skip;
var skip = _options$skip === undefined ? null : _options$skip;
var _options$priority = options.priority;
var
// TODO: refactor the detection to customize the execution order based on the attribute names
priority = _options$priority === undefined ? ['id', 'class', 'href', 'src'] : _options$priority;
var _options$ignore = options.ignore;
var ignore = _options$ignore === undefined ? {} : _options$ignore;
var path = [];
var element = node;
var length = path.length;
var skipCompare = skip && (Array.isArray(skip) ? skip : [skip]).map(function (entry) {
if (typeof entry !== 'function') {
return function (element) {
return element === entry;
};
}
return entry;
});
var skipChecks = function skipChecks(element) {
return skip && skipCompare.some(function (compare) {
return compare(element);
});
};
var ignoreClass = false;
Object.keys(ignore).forEach(function (type) {
if (type === 'class') {
ignoreClass = true;
}
var predicate = ignore[type];
if (typeof predicate === 'function') return;
if (typeof predicate === 'number') {
predicate = predicate.toString();
}
if (typeof predicate === 'string') {
predicate = new RegExp((0, _utilities.escapeValue)(predicate).replace(/\\/g, '\\\\'));
}
// check class-/attributename for regex
ignore[type] = predicate.test.bind(predicate);
});
if (ignoreClass) {
(function () {
var ignoreAttribute = ignore.attribute;
ignore.attribute = function (name, value, defaultPredicate) {
return ignore.class(value) || ignoreAttribute && ignoreAttribute(name, value, defaultPredicate);
};
})();
}
while (element !== root) {
if (skipChecks(element) !== true) {
// global
if (checkId(element, path, ignore)) break;
if (checkClassGlobal(element, path, ignore, root)) break;
if (checkAttributeGlobal(element, path, ignore, root, priority)) break;
if (checkTagGlobal(element, path, ignore, root)) break;
// local
checkClassLocal(element, path, ignore);
// define only one selector each iteration
if (path.length === length) {
checkAttributeLocal(element, path, ignore, priority);
}
if (path.length === length) {
checkTagLocal(element, path, ignore);
}
if (path.length === length) {
checkClassChild(element, path, ignore);
}
if (path.length === length) {
checkAttributeChild(element, path, ignore, priority);
}
if (path.length === length) {
checkTagChild(element, path, ignore);
}
}
element = element.parentNode;
length = path.length;
}
if (element === root) {
path.unshift('*');
}
return path.join(' ');
}
/**
* Preset 'checkClass' with global data
*
* @param {HTMLElement} element - [description]
* @param {Array.<string>} path - [description]
* @param {Object} ignore - [description]
* @return {boolean} - [description]
*/
function checkClassGlobal(element, path, ignore, root) {
return checkClass(element, path, ignore, root);
}
/**
* Preset 'checkClass' with local data
*
* @param {HTMLElement} element - [description]
* @param {Array.<string>} path - [description]
* @param {Object} ignore - [description]
* @return {boolean} - [description]
*/
function checkClassLocal(element, path, ignore) {
return checkClass(element, path, ignore, element.parentNode);
}
/**
* Preset 'checkChild' with class data
*
* @param {HTMLElement} element - [description]
* @param {Array.<string>} path - [description]
* @param {Object} ignore - [description]
* @return {boolean} - [description]
*/
function checkClassChild(element, path, ignore) {
var className = (0, _utilities.escapeValue)(element.getAttribute('class'));
if (checkIgnore(ignore.class, className)) {
return false;
}
return checkChild(element, path, '.' + className.trim().replace(/\s+/g, '.'));
}
/**
* Preset 'checkAttribute' with global data
*
* @param {HTMLElement} element - [description]
* @param {Array.<string>} path - [description]
* @param {Object} ignore - [description]
* @return {boolean} - [description]
*/
function checkAttributeGlobal(element, path, ignore, root, priority) {
return checkAttribute(element, path, ignore, root, priority);
}
/**
* Preset 'checkAttribute' with local data
*
* @param {HTMLElement} element - [description]
* @param {Array.<string>} path - [description]
* @param {Object} ignore - [description]
* @return {boolean} - [description]
*/
function checkAttributeLocal(element, path, ignore, priority) {
return checkAttribute(element, path, ignore, element.parentNode, priority);
}
/**
* Preset 'checkChild' with attribute data
*
* @param {HTMLElement} element - [description]
* @param {Array.<string>} path - [description]
* @param {Object} ignore - [description]
* @return {boolean} - [description]
*/
function checkAttributeChild(element, path, ignore, priority) {
var attributes = element.attributes;
return Object.keys(attributes).sort(orderByPriority(attributes, priority)).some(function (key) {
var attribute = attributes[key];
var attributeName = attribute.name;
var attributeValue = (0, _utilities.escapeValue)(attribute.value);
if (checkIgnore(ignore.attribute, attributeName, attributeValue, defaultIgnore.attribute)) {
return false;
}
var pattern = '[' + attributeName + '="' + attributeValue + '"]';
return checkChild(element, path, pattern);
});
}
/**
* Preset 'checkTag' with global data
*
* @param {HTMLElement} element - [description]
* @param {Array.<string>} path - [description]
* @param {Object} ignore - [description]
* @return {boolean} - [description]
*/
function checkTagGlobal(element, path, ignore, root) {
return checkTag(element, path, ignore, root);
}
/**
* Preset 'checkTag' with local data
*
* @param {HTMLElement} element - [description]
* @param {Array.<string>} path - [description]
* @param {Object} ignore - [description]
* @return {boolean} - [description]
*/
function checkTagLocal(element, path, ignore) {
return checkTag(element, path, ignore, element.parentNode);
}
/**
* Preset 'checkChild' with tag data
*
* @param {HTMLElement} element - [description]
* @param {Array.<string>} path - [description]
* @param {Object} ignore - [description]
* @return {boolean} - [description]
*/
function checkTagChild(element, path, ignore) {
var tagName = element.tagName.toLowerCase();
if (checkIgnore(ignore.tag, tagName)) {
return false;
}
return checkChild(element, path, tagName);
}
/**
* Lookup unique identifier
*
* @param {HTMLElement} element - [description]
* @param {Array.<string>} path - [description]
* @param {Object} ignore - [description]
* @return {boolean} - [description]
*/
function checkId(element, path, ignore) {
var id = (0, _utilities.escapeValue)(element.getAttribute('id'));
if (checkIgnore(ignore.id, id)) {
return false;
}
path.unshift('#' + id);
return true;
}
/**
* Lookup class identifier
*
* @param {HTMLElement} element - [description]
* @param {Array.<string>} path - [description]
* @param {Object} ignore - [description]
* @param {HTMLElement} parent - [description]
* @return {boolean} - [description]
*/
function checkClass(element, path, ignore, parent) {
var className = (0, _utilities.escapeValue)(element.getAttribute('class'));
if (checkIgnore(ignore.class, className)) {
return false;
}
var matches = parent.getElementsByClassName(className);
if (matches.length === 1) {
path.unshift('.' + className.trim().replace(/\s+/g, '.'));
return true;
}
return false;
}
/**
* Lookup attribute identifier
*
* @param {HTMLElement} element - [description]
* @param {Array.<string>} path - [description]
* @param {Object} ignore - [description]
* @param {HTMLElement} parent - [description]
* @return {boolean} - [description]
*/
function checkAttribute(element, path, ignore, parent, priority) {
var attributes = element.attributes;
return Object.keys(attributes).sort(orderByPriority(attributes, priority)).some(function (key) {
var attribute = attributes[key];
var attributeName = attribute.name;
var attributeValue = (0, _utilities.escapeValue)(attribute.value);
if (checkIgnore(ignore.attribute, attributeName, attributeValue, defaultIgnore.attribute)) {
return false;
}
var pattern = '[' + attributeName + '="' + attributeValue + '"]';
var matches = parent.querySelectorAll(pattern);
if (matches.length === 1) {
path.unshift(pattern);
return true;
}
});
}
/**
* Lookup tag identifier
*
* @param {HTMLElement} element - [description]
* @param {Array.<string>} path - [description]
* @param {HTMLElement} parent - [description]
* @param {Object} ignore - [description]
* @return {boolean} - [description]
*/
function checkTag(element, path, ignore, parent) {
var tagName = element.tagName.toLowerCase();
if (checkIgnore(ignore.tag, tagName)) {
return false;
}
var matches = parent.getElementsByTagName(tagName);
if (matches.length === 1) {
path.unshift(tagName);
return true;
}
return false;
}
/**
* Lookup child identfier
*
* Note: childTags is a custom property to use a view filter for tags on for virutal elements
*
* @param {HTMLElement} element - [description]
* @param {Array.<string>} path - [description]
* @param {String} selector - [description]
* @return {boolean} - [description]
*/
function checkChild(element, path, selector) {
var parent = element.parentNode;
var children = parent.childTags || parent.children;
for (var i = 0, l = children.length; i < l; i++) {
if (children[i] === element) {
path.unshift('> ' + selector + ':nth-child(' + (i + 1) + ')');
return true;
}
}
return false;
}
/**
* Validate with custom and default functions
*
* @param {Function} predicate - [description]
* @param {string} name - [description]
* @param {string} value - [description]
* @param {Function} defaultPredicate - [description]
* @return {boolean} - [description]
*/
function checkIgnore(predicate, name, value, defaultPredicate) {
if (!name) {
return true;
}
var check = predicate || defaultPredicate;
if (!check) {
return false;
}
return check(name, value || name, defaultPredicate);
}
/**
* Rank the attribute names by their general relevance for a website
*
* @param {Object} attributes - [description]
* @param {Array} priority - [description]
* @return {Function} - [description]
*/
function orderByPriority(attributes, priority) {
return function (curr, next) {
return priority.indexOf(attributes[curr].name) - priority.indexOf(attributes[next].name);
};
}
module.exports = exports['default'];
});
var match = unwrapExports(match_1);
var match$1 = /*#__PURE__*/Object.freeze({
default: match,
__moduleExports: match_1
});
var _adapt = ( adapt$1 && adapt ) || adapt$1;
var optimize_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = optimize;
var _adapt2 = _interopRequireDefault(_adapt);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
/**
* Apply different optimization techniques
*
* @param {string} selector - [description]
* @param {HTMLElement|Array.<HTMLElement>} element - [description]
* @param {Object} options - [description]
* @return {string} - [description]
*/
/**
* # Optimize
*
* 1.) Improve efficiency through shorter selectors by removing redundancy
* 2.) Improve robustness through selector transformation
*/
function optimize(selector, elements) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
// convert single entry and NodeList
if (!Array.isArray(elements)) {
elements = !elements.length ? [elements] : (0, _utilities.convertNodeList)(elements);
}
if (!elements.length || elements.some(function (element) {
return element.nodeType !== 1;
})) {
throw new Error('Invalid input - to compare HTMLElements its necessary to provide a reference of the selected node(s)! (missing "elements")');
}
var globalModified = (0, _adapt2.default)(elements[0], options);
// chunk parts outside of quotes (http://stackoverflow.com/a/25663729)
var path = selector.replace(/> /g, '>').split(/\s+(?=(?:(?:[^"]*"){2})*[^"]*$)/);
if (path.length < 2) {
return optimizePart('', selector, '', elements);
}
var shortened = [path.pop()];
while (path.length > 1) {
var current = path.pop();
var prePart = path.join(' ');
var postPart = shortened.join(' ');
var pattern = prePart + ' ' + postPart;
var matches = document.querySelectorAll(pattern);
if (matches.length !== elements.length) {
shortened.unshift(optimizePart(prePart, current, postPart, elements));
}
}
shortened.unshift(path[0]);
path = shortened;
// optimize start + end
path[0] = optimizePart('', path[0], path.slice(1).join(' '), elements);
path[path.length - 1] = optimizePart(path.slice(0, -1).join(' '), path[path.length - 1], '', elements);
if (globalModified) {
delete commonjsGlobal.document;
}
return path.join(' ').replace(/>/g, '> ').trim();
}
/**
* Improve a chunk of the selector
*
* @param {string} prePart - [description]
* @param {string} current - [description]
* @param {string} postPart - [description]
* @param {Array.<HTMLElement>} elements - [description]
* @return {string} - [description]
*/
function optimizePart(prePart, current, postPart, elements) {
if (prePart.length) prePart = prePart + ' ';
if (postPart.length) postPart = ' ' + postPart;
// robustness: attribute without value (generalization)
if (/\[*\]/.test(current)) {
var key = current.replace(/=.*$/, ']');
var pattern = '' + prePart + key + postPart;
var matches = document.querySelectorAll(pattern);
if (compareResults(matches, elements)) {
current = key;
} else {
// robustness: replace specific key-value with base tag (heuristic)
var references = document.querySelectorAll('' + prePart + key);
var _loop = function _loop() {
var reference = references[i];
if (elements.some(function (element) {
return reference.contains(element);
})) {
var description = reference.tagName.toLowerCase();
pattern = '' + prePart + description + postPart;
matches = document.querySelectorAll(pattern);
if (compareResults(matches, elements)) {
current = description;
}
return 'break';
}
};
for (var i = 0, l = references.length; i < l; i++) {
var pattern;
var matches;
var _ret = _loop();
if (_ret === 'break') break;
}
}
}
// robustness: descendant instead child (heuristic)
if (/>/.test(current)) {
var descendant = current.replace(/>/, '');
var pattern = '' + prePart + descendant + postPart;
var matches = document.querySelectorAll(pattern);
if (compareResults(matches, elements)) {
current = descendant;
}
}
// robustness: 'nth-of-type' instead 'nth-child' (heuristic)
if (/:nth-child/.test(current)) {
// TODO: consider complete coverage of 'nth-of-type' replacement
var type = current.replace(/nth-child/g, 'nth-of-type');
var pattern = '' + prePart + type + postPart;
var matches = document.querySelectorAll(pattern);
if (compareResults(matches, elements)) {
current = type;
}
}
// efficiency: combinations of classname (partial permutations)
if (/\.\S+\.\S+/.test(current)) {
var names = current.trim().split('.').slice(1).map(function (name) {
return '.' + name;
}).sort(function (curr, next) {
return curr.length - next.length;
});
while (names.length) {
var partial = current.replace(names.shift(), '').trim();
var pattern = ('' + prePart + partial + postPart).trim();
if (!pattern.length || pattern.charAt(0) === '>') {
break;
}
var matches = document.querySelectorAll(pattern);
if (compareResults(matches, elements)) {
current = partial;
}
}
// robustness: degrade complex classname (heuristic)
if (current && current.match(/\./g).length > 2) {
var _references = document.querySelectorAll('' + prePart + current);
var _loop2 = function _loop2() {
var reference = _references[i];
if (elements.some(function (element) {
return reference.contains(element);
})) {
// TODO:
// - check using attributes + regard excludes
var description = reference.tagName.toLowerCase();
pattern = '' + prePart + description + postPart;
matches = document.querySelectorAll(pattern);
if (compareResults(matches, elements)) {
current = description;
}
return 'break';
}
};
for (var i = 0, l = _references.length; i < l; i++) {
var pattern;
var matches;
var _ret2 = _loop2();
if (_ret2 === 'break') break;
}
}
}
return current;
}
/**
* Evaluate matches with expected elements
*
* @param {Array.<HTMLElement>} matches - [description]
* @param {Array.<HTMLElement>} elements - [description]
* @return {Boolean} - [description]
*/
function compareResults(matches, elements) {
var length = matches.length;
return length === elements.length && elements.every(function (element) {
for (var i = 0; i < length; i++) {
if (matches[i] === element) {
return true;
}
}
return false;
});
}
module.exports = exports['default'];
});
var optimize = unwrapExports(optimize_1);
var optimize$1 = /*#__PURE__*/Object.freeze({
default: optimize,
__moduleExports: optimize_1
});
var common = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getCommonAncestor = getCommonAncestor;
exports.getCommonProperties = getCommonProperties;
/**
* # Common
*
* Group similars
*/
/**
* Find the last common ancestor of elements
*
* @param {Array.<HTMLElements>} elements - [description]
* @return {HTMLElement} - [description]
*/
function getCommonAncestor(elements) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var _options$root = options.root;
var root = _options$root === undefined ? document : _options$root;
var _options$skip = options.skip;