UNPKG

diffhtml-middleware-verify-state

Version:

Verifies render state middleware, useful for sanity checking

26 lines (22 loc) 5.47 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.verifyState = factory()); }(this, (function () { 'use strict'; const root=typeof global!=="undefined"?global:window;const binding=globalThis[Symbol.for("diffHTML")];const{NodeCache,decodeEntities,Pool}=binding.Internals;const{createTree}=binding;const getValue=(vTree,keyName)=>{if(vTree instanceof Node&&vTree.attributes){return vTree.attributes[keyName].value||vTree[keyName]}else {return vTree.attributes[keyName]}};const setupDebugger=options=>message=>{if(options.debug){debugger;throw new Error(message)}else {console.warn(message);}};const element=root.document?document.createElement("div"):null;const flattenFragments=vTree=>{vTree.childNodes.forEach((childNode,i)=>{if(childNode.nodeType===11){// Flatten the nodes into the position. vTree.childNodes.splice.apply(vTree.childNodes,[i,1,...childNode.childNodes]);childNode.childNodes.forEach(childNode=>flattenFragments(childNode));return}flattenFragments(childNode);});return vTree};// Verify that a VTree matches what the NodeCache has associated. const verifyTreeNodeAssociation=(debug,vTree)=>{const node=NodeCache.get(vTree);if(!node);else if(node.nodeName.toLowerCase()!==vTree.nodeName){debug(`[Mismatched DOM Node] ${vTree.nodeName} has an invalid DOM Node association with ${node.nodeName}`);}else if(!Pool.memory.protected.has(vTree)){debug(`[Unprotected DOM Node] ${vTree.nodeName} was not protected in memory`);}// Recursively search for problems. vTree.childNodes.forEach(_vTree=>verifyTreeNodeAssociation(debug,_vTree));};const compareTrees=(options,transaction,mount,newTree,verifyTree)=>{const debug=setupDebugger(options);let oldAttrKeys=Object.keys(verifyTree.attributes||{}).sort().filter(Boolean);let newAttrKeys=Object.keys(newTree.attributes||{}).sort().filter(Boolean);const oldTreeIsNode=verifyTree instanceof Node;const oldLabel=oldTreeIsNode?"ON DOM NODE":"OLD";if(oldTreeIsNode){newTree=flattenFragments(newTree);}const oldValue=decodeEntities(verifyTree.nodeValue||"").replace(/\r?\n|\r/g,"");const newValue=decodeEntities(newTree.nodeValue||"").replace(/\r?\n|\r/g,"");if(verifyTree.nodeName.toLowerCase()!==newTree.nodeName.toLowerCase()&&newTree.nodeType!==11){debug(`[Mismatched nodeName] ${oldLabel}: ${verifyTree.nodeName} NEW TREE: ${newTree.nodeName}`);}else if(verifyTree.nodeValue&&newTree.nodeValue&&oldValue!==newValue){debug(`[Mismatched nodeValue] ${oldLabel}: ${oldValue} NEW TREE: ${newValue}`);}else if(verifyTree.nodeType!==newTree.nodeType&&newTree.nodeType!==11){debug(`[Mismatched nodeType] ${oldLabel}: ${verifyTree.nodeType} NEW TREE: ${newTree.nodeType}`);}else if(verifyTree.childNodes.length!==newTree.childNodes.length){debug(`[Mismatched childNodes length] ${oldLabel}: ${verifyTree.childNodes.length} NEW TREE: ${newTree.childNodes.length}`);}if(oldTreeIsNode&&oldTree.attributes){oldAttrKeys=[...verifyTree.attributes].map(s=>String(s.name)).sort();}//if (!oldTreeIsNode && !NodeCache.has(oldTree)) { // debug(`Tree does not have an associated DOM Node`); //} // Look for attribute differences. if(newTree.nodeType!==11){for(let i=0;i<oldAttrKeys.length;i++){const oldValue=getValue(verifyTree,oldAttrKeys[i])||"";const newValue=getValue(newTree,newAttrKeys[i])||"";// If names are different report it out. if(oldAttrKeys[i].toLowerCase()!==newAttrKeys[i].toLowerCase()){if(!newAttrKeys[i]){debug(`[Unexpected attribute] ${oldLabel}: ${oldAttrKeys[i]}="${oldValue}"`);}else if(!oldAttrKeys[i]){debug(`[Unexpected attribute] IN NEW TREE: ${newAttrKeys[i]}="${newValue}"`);}else {debug(`[Unexpected attribute] ${oldLabel}: ${oldAttrKeys[i]}="${oldValue}" IN NEW TREE: ${newAttrKeys[i]}="${newValue}"`);}}// If values are different else if(!oldTreeIsNode&&oldValue!==newValue){debug(`[Unexpected attribute] ${oldLabel}: ${oldAttrKeys[i]}="${oldValue}" IN NEW TREE: ${newAttrKeys[i]}="${newValue}"`);}}for(let i=0;i<verifyTree.childNodes.length;i++){if(verifyTree.childNodes[i]&&newTree.childNodes[i]){compareTrees(options,transaction,verifyTree.childNodes[i],newTree.childNodes[i],verifyTree.childNodes[i]);}}}verifyTreeNodeAssociation(debug,verifyTree);};function reduceEntry(inputAsVTree){/** @type {VTree[]} */let foundElements=[];for(let i=0;i<inputAsVTree.childNodes.length;i++){const value=inputAsVTree.childNodes[i];const isText=value.nodeType===3;// This is most likely the element that is requested to compare to. Will // need to keep checking or more input though to be totally sure. if(!isText||value.nodeValue.trim()){foundElements.push(value);}}// If only one element is found, we can use this directly. if(foundElements.length===1){return foundElements[0]}// Otherwise consider the entire fragment. else if(foundElements.length>1){return createTree(inputAsVTree.childNodes)}else {return createTree(inputAsVTree)}}var index = ((options={})=>function verifyStateTask(){return transaction=>{const{mount,state}=transaction;const oldTree=reduceEntry(transaction.oldTree||state.oldTree);const newTree=reduceEntry(transaction.newTree);if(oldTree&&newTree){compareTrees(options,transaction,oldTree,newTree,oldTree);}transaction.onceEnded(()=>{//compareTrees(options, transaction, mount, newTree, newTree); compareTrees(options,transaction,mount,newTree,oldTree);});}}); return index; })));