UNPKG

zpt

Version:

Zenon Page Templates - JS (ZPT-JS)

682 lines (598 loc) 22.9 kB
/* 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; })();