zpt
Version:
Zenon Page Templates - JS (ZPT-JS)
682 lines (598 loc) • 22.9 kB
JavaScript
/*
htmlComparator singleton class
*/
export const htmlComparator = (function() {
/**
* Find an appropriate `Assert` context to `push` results to.
* @param * context - An unknown context, possibly `Assert`, `Test`, or neither
* @private
*/
/*
function _getPushContext(context) {
var pushContext;
if (context && typeof context.push === "function") {
// `context` is an `Assert` context
pushContext = context;
}
else if (context && context.assert && typeof context.assert.push === "function") {
// `context` is a `Test` context
pushContext = context.assert;
}
else if (
QUnit && QUnit.config && QUnit.config.current && QUnit.config.current.assert &&
typeof QUnit.config.current.assert.push === "function"
) {
// `context` is an unknown context but we can find the `Assert` context via QUnit
pushContext = QUnit.config.current.assert;
}
else if (QUnit && typeof QUnit.push === "function") {
pushContext = QUnit.push;
}
else {
throw new Error("Could not find the QUnit `Assert` context to push results");
}
return pushContext;
}*/
var trim = function( s ) {
if ( !s ) {
return "";
}
return typeof s.trim === "function" ? s.trim() : s.replace( /^\s+|\s+$/g, "" );
};
var normalizeWhitespace = function( s ) {
if ( !s ) {
return "";
}
return trim( s.replace( /\s+/g, " " ) );
//return trim( s.replace(/(\r\n|\n|\r)/gm, "").replace( /\s+/g, " " ) );
};
var dedupeFlatDict = function( dictToDedupe, parentDict ) {
var key, val;
if ( parentDict ) {
for ( key in dictToDedupe ) {
val = dictToDedupe[key];
if ( val && ( val === parentDict[key] ) ) {
delete dictToDedupe[key];
}
}
}
return dictToDedupe;
};
var objectKeys = Object.keys || (function() {
var hasOwn = function( obj, propName ) {
return Object.prototype.hasOwnProperty.call( obj, propName );
};
return function( obj ) {
var keys = [],
key;
for ( key in obj ) {
if ( hasOwn( obj, key ) ) {
keys.push( key );
}
}
return keys;
};
})();
/*
var addEvent = function( elem, type, fn ) {
if ( elem.addEventListener ) {
// Standards-based browsers
elem.addEventListener( type, fn, false );
} else if ( elem.attachEvent ) {
// support: IE <9
elem.attachEvent( "on" + type, fn );
}
};*/
/**
* Calculate based on `currentStyle`/`getComputedStyle` styles instead
*/
var getElementStyles = (function() {
// Memoized
var camelCase = (function() {
var camelCaseFn = (function() {
// Matches dashed string for camelizing
var rmsPrefix = /^-ms-/,
msPrefixFix = "ms-",
rdashAlpha = /-([\da-z])/gi,
camelCaseReplacerFn = function( all, letter ) {
return ( letter + "" ).toUpperCase();
};
return function( s ) {
return s.replace(rmsPrefix, msPrefixFix).replace(rdashAlpha, camelCaseReplacerFn);
};
})();
var camelCaseMemoizer = {};
return function( s ) {
var temp = camelCaseMemoizer[s];
if ( temp ) {
return temp;
}
temp = camelCaseFn( s );
camelCaseMemoizer[s] = temp;
return temp;
};
})();
var styleKeySortingFn = function( a, b ) {
return camelCase( a ) < camelCase( b );
};
return function( elem ) {
var styleCount, i, key,
styles = {},
styleKeys = [],
style = elem.ownerDocument.defaultView ?
elem.ownerDocument.defaultView.getComputedStyle( elem, null ) :
elem.currentStyle;
// `getComputedStyle`
if ( style && style.length && style[0] && style[style[0]] ) {
styleCount = style.length;
while ( styleCount-- ) {
styleKeys.push( style[styleCount] );
}
styleKeys.sort( styleKeySortingFn );
for ( i = 0, styleCount = styleKeys.length ; i < styleCount ; i++ ) {
key = styleKeys[i];
if ( typeof style[key] === "string" && style[key] ) {
if ( key === "cssFloat" || key === "styleFloat" ) {
styles["float"] = style[key];
}
else if ( key !== "cssText" ) {
styles[camelCase( key )] = style[key];
}
}
}
}
// `currentStyle` support: IE < 9.0, Opera < 10.6
else {
for ( key in style ) {
styleKeys.push( key );
}
styleKeys.sort();
for ( i = 0, styleCount = styleKeys.length ; i < styleCount ; i++ ) {
key = styleKeys[i];
if ( typeof style[key] === "string" && style[key] ) {
if ( key === "cssFloat" || key === "styleFloat" ) {
styles["float"] = style[key];
}
else if ( key !== "cssText" ) {
styles[key] = style[key];
}
}
}
}
return styles;
};
})();
var serializeElementNode = function( elementNode, rootNodeStyles ) {
var subNodes, i, len, styles, attrName,
serializedNode = {
NodeType: elementNode.nodeType,
NodeName: elementNode.nodeName.toLowerCase(),
Attributes: {},
ChildNodes: []
};
subNodes = elementNode.attributes;
for ( i = 0, len = subNodes.length ; i < len ; i++ ) {
attrName = subNodes[i].name.toLowerCase();
serializedNode.Attributes[attrName] = normalizeWhitespace( subNodes[i].value );
/*
if ( attrName === "class" ) {
serializedNode.Attributes[attrName] = normalizeWhitespace( subNodes[i].value );
}
else if ( attrName !== "style" ) {
serializedNode.Attributes[attrName] = normalizeWhitespace( subNodes[i].value );
}
// Ignore the "style" attribute completely
*/
}
// Only add the style attribute if there is 1+ pertinent rules
styles = dedupeFlatDict( getElementStyles( elementNode ), rootNodeStyles );
if ( styles && objectKeys( styles ).length ) {
serializedNode.Attributes["style"] = styles;
}
subNodes = elementNode.childNodes;
for ( i = 0, len = subNodes.length; i < len; i++ ) {
var serializedSubnode = serializeNode( subNodes[i], rootNodeStyles );
if ( serializedSubnode ){
serializedNode.ChildNodes.push( serializedSubnode );
}
}
return serializedNode;
};
var serializeNode = function( node, rootNodeStyles ) {
var serializedNode;
switch ( node.nodeType ) {
case 1: // Node.ELEMENT_NODE
serializedNode = serializeElementNode( node, rootNodeStyles );
break;
case 3: // Node.TEXT_NODE
var text = node.nodeValue.trim();
if ( text ){
serializedNode = {
NodeType: node.nodeType,
NodeName: node.nodeName.toLowerCase(),
NodeValue: text
};
}
break;
case 4: // Node.CDATA_SECTION_NODE
case 7: // Node.PROCESSING_INSTRUCTION_NODE
case 8: // Node.COMMENT_NODE
serializedNode = {
NodeType: node.nodeType,
NodeName: node.nodeName.toLowerCase(),
NodeValue: trim( node.nodeValue )
};
break;
case 5: // Node.ENTITY_REFERENCE_NODE
case 9: // Node.DOCUMENT_NODE
case 10: // Node.DOCUMENT_TYPE_NODE
case 11: // Node.DOCUMENT_FRAGMENT_NODE
serializedNode = {
NodeType: node.nodeType,
NodeName: node.nodeName
};
break;
case 2: // Node.ATTRIBUTE_NODE
throw new Error( "`node.nodeType` was `Node.ATTRIBUTE_NODE` (2), which is not supported by this method" );
case 6: // Node.ENTITY_NODE
throw new Error( "`node.nodeType` was `Node.ENTITY_NODE` (6), which is not supported by this method" );
case 12: // Node.NOTATION_NODE
throw new Error( "`node.nodeType` was `Node.NOTATION_NODE` (12), which is not supported by this method" );
default:
throw new Error( "`node.nodeType` was not recognized: " + node.nodeType );
}
return serializedNode;
};
var serializeHtml = function( html ) {
var rootNode = window.document.createElement( 'div' ),
rootNodeStyles = getElementStyles( rootNode ),
serializedHtml = [],
kids, i, len;
rootNode.innerHTML = trim( html );
kids = rootNode.childNodes;
for ( i = 0, len = kids.length; i < len; i++ ) {
var serializedSubnode = serializeNode( kids[i], rootNodeStyles );
if ( serializedSubnode ){
serializedHtml.push( serializedSubnode );
}
}
return serializedHtml;
};
/*
var serializeHtml = function( html ) {
var scratch = getCleanSlate(),
rootNode = scratch.container(),
rootNodeStyles = getElementStyles( rootNode ),
serializedHtml = [],
kids, i, len;
rootNode.innerHTML = trim( html );
kids = rootNode.childNodes;
for ( i = 0, len = kids.length; i < len; i++ ) {
serializedHtml.push( serializeNode( kids[i], rootNodeStyles ) );
}
scratch.reset();
return serializedHtml;
};
*/
var singletonElements = " " + [
"area", "base", "br", "col", "command", "embed", "hr", "img", "input",
"keygen", "link", "meta", "param", "source", "track", "wbr"
].join( " " ) + " ";
var isEmptyElement = function( serializedElementNode ) {
return (
serializedElementNode.ChildNodes.length === 0 &&
(
singletonElements.indexOf( serializedElementNode.NodeName ) >= 0 ||
(
serializedElementNode.NodeName === "colgroup" &&
serializedElementNode.Attributes.hasOwnProperty( "span" )
)
)
);
};
var dashify = (function() {
var dashifyFn = function( s ) {
return s
.replace( /([\da-z])([\dA-Z])/, function( all, letter1, letter2 ) {
return letter1 + "-" + ( letter2 + "" ).toLowerCase();
})
.replace( /^ms-/, "-ms-" );
};
var dashifyMemoizer = {};
return function( s ) {
var temp = dashifyMemoizer[s];
if ( temp ) {
return temp;
}
temp = dashifyFn( s );
dashifyMemoizer[s] = temp;
return temp;
};
})();
var deserializeElementNode = function( serializedElementNode, depth ) {
var deserializedElementNodeHtml = "";
deserializedElementNodeHtml += "<" + serializedElementNode.NodeName;
var attrNames = objectKeys( serializedElementNode.Attributes ).sort();
for ( var i = 0, len = attrNames.length; i < len; i++ ) {
if ( attrNames[i] !== "style" ) {
deserializedElementNodeHtml +=
" " + attrNames[i] + '="' +
serializedElementNode.Attributes[attrNames[i]] + '"';
}
else {
var styles = serializedElementNode.Attributes[attrNames[i]];
var styleKeys = objectKeys( styles ).sort();
if ( styleKeys.length > 0 ) {
deserializedElementNodeHtml += " " + attrNames[i] + '="';
var stylesArr = [];
for ( var j = 0, count = styleKeys.length; j < count; j++ ) {
stylesArr.push(
dashify( styleKeys[j] ) + ":" + styles[styleKeys[j]] + ";"
);
}
deserializedElementNodeHtml += stylesArr.join( " " );
deserializedElementNodeHtml += '"';
}
}
}
if ( isEmptyElement( serializedElementNode ) ) {
deserializedElementNodeHtml += " />";
}
else {
deserializedElementNodeHtml +=
">" +
deserializeHtml( serializedElementNode.ChildNodes, depth ) +
buildSpaces( --depth ) +
"</" + serializedElementNode.NodeName + ">";
}
return deserializedElementNodeHtml;
};
var buildSpaces = function( depth ){
var result = "";
for ( var i = 0; i < depth; ++i ){
result += " ";
}
return result;
};
var deserializeNode = function( serializedNode, depth ) {
var deserializedNodeHtml = "\n" + buildSpaces( depth );
switch ( serializedNode.NodeType ) {
case 1: // Node.ELEMENT_NODE
deserializedNodeHtml += deserializeElementNode( serializedNode, ++depth );
break;
case 3: // Node.TEXT_NODE
deserializedNodeHtml += serializedNode.NodeValue;
break;
case 4: // Node.CDATA_SECTION_NODE
deserializedNodeHtml += "<![CDATA[" + serializedNode.NodeValue + "]]>";
break;
case 7: // Node.PROCESSING_INSTRUCTION_NODE
deserializedNodeHtml += "<?" + serializedNode.NodeName + " " + serializedNode.NodeValue + "?>";
break;
case 8: // Node.COMMENT_NODE
deserializedNodeHtml += "<!-- " + serializedNode.NodeValue + " -->";
break;
case 5: // Node.ENTITY_REFERENCE_NODE
deserializedNodeHtml += "&" + serializedNode.NodeName + ";";
break;
case 10: // Node.DOCUMENT_TYPE_NODE
deserializedNodeHtml += "<!DOCTYPE " + serializedNode.NodeName + ">";
break;
case 9: // Node.DOCUMENT_NODE
case 11: // Node.DOCUMENT_FRAGMENT_NODE
deserializedNodeHtml += "";
break;
case 2: // Node.ATTRIBUTE_NODE
throw new Error( "`serializedNode.NodeType` was `Node.ATTRIBUTE_NODE` (2), which is not supported by this method" );
case 6: // Node.ENTITY_NODE
throw new Error( "`serializedNode.NodeType` was `Node.ENTITY_NODE` (6), which is not supported by this method" );
case 12: // Node.NOTATION_NODE
throw new Error( "`serializedNode.NodeType` was `Node.NOTATION_NODE` (12), which is not supported by this method" );
default:
throw new Error( "`serializedNode.NodeType` was not recognized: " + serializedNode.NodeType );
}
deserializedNodeHtml += "\n";
return deserializedNodeHtml;
};
var deserializeHtml = function( serializedHtmlNodes, _depth ) {
var i, len,
deserializedHtml = "",
depth = _depth? _depth: 0;
for ( i = 0, len = serializedHtmlNodes.length; i < len; i++ ) {
deserializedHtml += deserializeNode( serializedHtmlNodes[i], depth );
}
return deserializedHtml;
};
/*
var getCleanSlate = (function() {
var containerElId = "qunit-html-addon-container",
iframeReady = false,
iframeLoaded = function() {
iframeReady = true;
},
iframeReadied = function() {
if (iframe.readyState === "complete" || iframe.readyState === 4) {
iframeReady = true;
}
},
iframeApi,
iframe,
iframeWin,
iframeDoc;
if ( !iframeApi ) {
QUnit.begin(function() {
// Initialize the background iframe!
if ( !iframe || !iframeWin || !iframeDoc ) {
iframe = window.document.createElement( "iframe" );
addEvent( iframe, "load", iframeLoaded );
addEvent( iframe, "readystatechange", iframeReadied );
iframe.style.position = "absolute";
iframe.style.top = iframe.style.left = "-1000px";
iframe.height = iframe.width = 0;
// `getComputedStyle` behaves inconsistently cross-browser when not attached to a live DOM
window.document.body.appendChild( iframe );
iframeWin = iframe.contentWindow ||
iframe.window ||
iframe.contentDocument && iframe.contentDocument.defaultView ||
iframe.document && ( iframe.document.defaultView || iframe.document.window ) ||
window.frames[( iframe.name || iframe.id )];
iframeDoc = iframeWin && iframeWin.document ||
iframe.contentDocument ||
iframe.document;
var iframeContents = [
"<!DOCTYPE html>",
"<html>",
"<head>",
" <title>QUnit HTML addon iframe</title>",
"</head>",
"<body>",
" <div id=\"" + containerElId + "\"></div>",
" <script type=\"text/javascript\">",
" window.isReady = true;",
" </script>",
"</body>",
"</html>"
].join( "\n" );
iframeDoc.open();
iframeDoc.write( iframeContents );
iframeDoc.close();
// Is ready?
iframeReady = iframeReady || iframeWin.isReady;
}
});
QUnit.done(function() {
if ( iframe && iframe.ownerDocument ) {
iframe.parentNode.removeChild( iframe );
}
iframe = iframeWin = iframeDoc = null;
iframeReady = false;
});
var waitForIframeReady = function( maxTimeout ) {
if ( !iframeReady ) {
if ( !maxTimeout ) {
maxTimeout = 2000; // 2 seconds MAX
}
var startTime = new Date();
while ( !iframeReady && ( ( new Date() - startTime ) < maxTimeout ) ) {
iframeReady = iframeReady || iframeWin.isReady;
}
}
};
iframeApi = {
container: function() {
waitForIframeReady();
if ( iframeReady && iframeDoc ) {
return iframeDoc.getElementById( containerElId );
}
return; // undefined
},
reset: function() {
var containerEl = iframeApi.container();
if ( containerEl ) {
containerEl.innerHTML = "";
}
}
};
}
// Actual function signature for `getCleanState`
return function() { return iframeApi; };
})();
*/
var genericEqual = function( actual, expected, message, mustBeEquals ){
var serializedActual, serializedExpected,
pushContext = _getPushContext( this );
message = message || mustBeEquals? "HTML should be equal": "HTML should not be equal";
serializedActual = serializeHtml( actual );
serializedExpected = serializeHtml( expected );
// Don't escape quotes!
Qunit.dump.setParser(
'string',
function( str ){
return str;
}
);
var isEquiv = QUnit.equiv( serializedActual, serializedExpected );
pushContext.push(
mustBeEquals? isEquiv: !isEquiv,
deserializeHtml( serializedActual ),
deserializeHtml( serializedExpected ),
message
);
};
var api = {
compare: function( actual, expected ) {
// Serialize
var serializedActual = serializeHtml( actual );
var serializedExpected = serializeHtml( expected );
// Deserialize
var deserializedActual = deserializeHtml( serializedActual );
var deserializedExpected = deserializeHtml( serializedExpected );
return {
equals: deserializedActual === deserializedExpected,
actual: deserializedActual,
expected: deserializedExpected
};
},
/**
* Compare two snippets of HTML for equality after normalization.
*
* @example assert.htmlEqual("<B>Hello, QUnit!</B> ", "<b>Hello, QUnit!</b>", "HTML should be equal");
* @param {String} actual The actual HTML before normalization.
* @param {String} expected The excepted HTML before normalization.
* @param {String} [message] Optional message to display in the results.
*/
htmlEqual: function( actual, expected, message ) {
return genericEqual( actual, expected, message, true );
},
/*
htmlEqual: function( actual, expected, message ) {
var serializedActual, serializedExpected,
pushContext = _getPushContext( this );
message = message || "HTML should be equal";
serializedActual = serializeHtml( actual );
serializedExpected = serializeHtml( expected );
pushContext.push(
QUnit.equiv( serializedActual, serializedExpected ),
deserializeHtml( serializedActual ),
deserializeHtml( serializedExpected ),
message
);
},*/
/**
* Compare two snippets of HTML for inequality after normalization.
*
* @example assert.notHtmlEqual("<b>Hello, <i>QUnit!</i></b>", "<b>Hello, QUnit!</b>", "HTML should not be equal");
* @param {String} actual The actual HTML before normalization.
* @param {String} expected The excepted HTML before normalization.
* @param {String} [message] Optional message to display in the results.
*/
notHtmlEqual: function( actual, expected, message ) {
return genericEqual( actual, expected, message, false );
},
/*
notHtmlEqual: function( actual, expected, message ) {
var serializedActual, serializedExpected,
pushContext = _getPushContext( this );
message = message || "HTML should not be equal";
serializedActual = serializeHtml( actual );
serializedExpected = serializeHtml( expected );
pushContext.push(
!QUnit.equiv( serializedActual, serializedExpected ),
deserializeHtml( serializedActual ),
deserializeHtml( serializedExpected ),
message
);
},*/
/**
* @private
* Normalize and serialize an HTML snippet. Primarily only exposed for unit testing purposes.
*
* @example assert._serializeHtml('<b style="color:red;">Test</b>');
* @param {String} html The HTML snippet to normalize and serialize.
* @returns {Object[]} The normalized and serialized form of the HTML snippet.
*/
_serializeHtml: serializeHtml
};
//QUnit.extend( QUnit.assert, api );
return api;
})();