diff --git a/cjs/index.js b/cjs/index.js index 4566c30..68e627a 100644 --- a/cjs/index.js +++ b/cjs/index.js @@ -45,15 +45,23 @@ module.exports = (parentNode, a, b, get, before) => { (get(b[bStart - 1], -0).nextSibling) : get(b[bEnd - bStart], 0)) : before; + const nodes = []; while (bStart < bEnd) - parentNode.insertBefore(get(b[bStart++], 1), node); + nodes.push(get(b[bStart++], 1)); + if (node) { + nodes.push(node); + node.replaceWith(...nodes); + } + else { + parentNode.append(...nodes); + } } // remove head or tail: fast path else if (bEnd === bStart) { while (aStart < aEnd) { // remove the node only if it's unknown or not live if (!map || !map.has(a[aStart])) - parentNode.removeChild(get(a[aStart], -1)); + get(a[aStart], -1).remove(); aStart++; } } @@ -130,17 +138,17 @@ module.exports = (parentNode, a, b, get, before) => { // will be processed at zero cost if (sequence > (index - bStart)) { const node = get(a[aStart], 0); + const nodes = []; while (bStart < index) - parentNode.insertBefore(get(b[bStart++], 1), node); + nodes.push(get(b[bStart++], 1)); + nodes.push(node); + node.replaceWith(...nodes); } // if the effort wasn't good enough, fallback to a replace, // moving both source and target indexes forward, hoping that some // similar node will be found later on, to go back to the fast path else { - parentNode.replaceChild( - get(b[bStart++], 1), - get(a[aStart++], -1) - ); + get(a[aStart++], -1).replaceWith(get(b[bStart++], 1)); } } // otherwise move the source forward, 'cause there's nothing to do @@ -150,8 +158,9 @@ module.exports = (parentNode, a, b, get, before) => { // this node has no meaning in the future list, so it's more than safe // to remove it, and check the next live node out instead, meaning // that only the live list index should be forwarded - else - parentNode.removeChild(get(a[aStart++], -1)); + else { + get(a[aStart++], -1).remove(); + } } } return b; diff --git a/esm/index.js b/esm/index.js index fdc7508..7e07962 100644 --- a/esm/index.js +++ b/esm/index.js @@ -44,15 +44,23 @@ export default (parentNode, a, b, get, before) => { (get(b[bStart - 1], -0).nextSibling) : get(b[bEnd - bStart], 0)) : before; + const nodes = []; while (bStart < bEnd) - parentNode.insertBefore(get(b[bStart++], 1), node); + nodes.push(get(b[bStart++], 1)); + if (node) { + nodes.push(node); + node.replaceWith(...nodes); + } + else { + parentNode.append(...nodes); + } } // remove head or tail: fast path else if (bEnd === bStart) { while (aStart < aEnd) { // remove the node only if it's unknown or not live if (!map || !map.has(a[aStart])) - parentNode.removeChild(get(a[aStart], -1)); + get(a[aStart], -1).remove(); aStart++; } } @@ -129,17 +137,17 @@ export default (parentNode, a, b, get, before) => { // will be processed at zero cost if (sequence > (index - bStart)) { const node = get(a[aStart], 0); + const nodes = []; while (bStart < index) - parentNode.insertBefore(get(b[bStart++], 1), node); + nodes.push(get(b[bStart++], 1)); + nodes.push(node); + node.replaceWith(...nodes); } // if the effort wasn't good enough, fallback to a replace, // moving both source and target indexes forward, hoping that some // similar node will be found later on, to go back to the fast path else { - parentNode.replaceChild( - get(b[bStart++], 1), - get(a[aStart++], -1) - ); + get(a[aStart++], -1).replaceWith(get(b[bStart++], 1)); } } // otherwise move the source forward, 'cause there's nothing to do @@ -149,8 +157,9 @@ export default (parentNode, a, b, get, before) => { // this node has no meaning in the future list, so it's more than safe // to remove it, and check the next live node out instead, meaning // that only the live list index should be forwarded - else - parentNode.removeChild(get(a[aStart++], -1)); + else { + get(a[aStart++], -1).remove(); + } } } return b; diff --git a/index.js b/index.js index ae0827e..7db11ab 100644 --- a/index.js +++ b/index.js @@ -35,7 +35,6 @@ var udomdiff = (function (exports) { var aStart = 0; var bStart = 0; var map = null; - while (aStart < aEnd || bStart < bEnd) { // append head, tail, or nodes in between: fast path if (aEnd === aStart) { @@ -44,111 +43,118 @@ var udomdiff = (function (exports) { // the node to `insertBefore`, if the index is more than 0 // must be retrieved, otherwise it's gonna be the first item. var node = bEnd < bLength ? bStart ? get(b[bStart - 1], -0).nextSibling : get(b[bEnd - bStart], 0) : before; - - while (bStart < bEnd) { - parentNode.insertBefore(get(b[bStart++], 1), node); + var nodes = []; + while (bStart < bEnd) nodes.push(get(b[bStart++], 1)); + if (node) { + nodes.push(node); + node.replaceWith.apply(node, nodes); + } else { + parentNode.append.apply(parentNode, nodes); } - } // remove head or tail: fast path + } + // remove head or tail: fast path else if (bEnd === bStart) { - while (aStart < aEnd) { - // remove the node only if it's unknown or not live - if (!map || !map.has(a[aStart])) parentNode.removeChild(get(a[aStart], -1)); - aStart++; + while (aStart < aEnd) { + // remove the node only if it's unknown or not live + if (!map || !map.has(a[aStart])) get(a[aStart], -1).remove(); + aStart++; + } + } + // same node: fast path + else if (a[aStart] === b[bStart]) { + aStart++; + bStart++; + } + // same tail: fast path + else if (a[aEnd - 1] === b[bEnd - 1]) { + aEnd--; + bEnd--; + } + // The once here single last swap "fast path" has been removed in v1.1.0 + // https://github.com/WebReflection/udomdiff/blob/single-final-swap/esm/index.js#L69-L85 + // reverse swap: also fast path + else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) { + // this is a "shrink" operation that could happen in these cases: + // [1, 2, 3, 4, 5] + // [1, 4, 3, 2, 5] + // or asymmetric too + // [1, 2, 3, 4, 5] + // [1, 2, 3, 5, 6, 4] + var _node = get(a[--aEnd], -1).nextSibling; + parentNode.insertBefore(get(b[bStart++], 1), get(a[aStart++], -1).nextSibling); + parentNode.insertBefore(get(b[--bEnd], 1), _node); + // mark the future index as identical (yeah, it's dirty, but cheap 👍) + // The main reason to do this, is that when a[aEnd] will be reached, + // the loop will likely be on the fast path, as identical to b[bEnd]. + // In the best case scenario, the next loop will skip the tail, + // but in the worst one, this node will be considered as already + // processed, bailing out pretty quickly from the map index check + a[aEnd] = b[bEnd]; + } + // map based fallback, "slow" path + else { + // the map requires an O(bEnd - bStart) operation once + // to store all future nodes indexes for later purposes. + // In the worst case scenario, this is a full O(N) cost, + // and such scenario happens at least when all nodes are different, + // but also if both first and last items of the lists are different + if (!map) { + map = new Map(); + var i = bStart; + while (i < bEnd) map.set(b[i], i++); + } + // if it's a future node, hence it needs some handling + if (map.has(a[aStart])) { + // grab the index of such node, 'cause it might have been processed + var index = map.get(a[aStart]); + // if it's not already processed, look on demand for the next LCS + if (bStart < index && index < bEnd) { + var _i = aStart; + // counts the amount of nodes that are the same in the future + var sequence = 1; + while (++_i < aEnd && _i < bEnd && map.get(a[_i]) === index + sequence) sequence++; + // effort decision here: if the sequence is longer than replaces + // needed to reach such sequence, which would brings again this loop + // to the fast path, prepend the difference before a sequence, + // and move only the future list index forward, so that aStart + // and bStart will be aligned again, hence on the fast path. + // An example considering aStart and bStart are both 0: + // a: [1, 2, 3, 4] + // b: [7, 1, 2, 3, 6] + // this would place 7 before 1 and, from that time on, 1, 2, and 3 + // will be processed at zero cost + if (sequence > index - bStart) { + var _node2 = get(a[aStart], 0); + var _nodes = []; + while (bStart < index) _nodes.push(get(b[bStart++], 1)); + _nodes.push(_node2); + _node2.replaceWith.apply(_node2, _nodes); + } + // if the effort wasn't good enough, fallback to a replace, + // moving both source and target indexes forward, hoping that some + // similar node will be found later on, to go back to the fast path + else { + get(a[aStart++], -1).replaceWith(get(b[bStart++], 1)); + } } - } // same node: fast path - else if (a[aStart] === b[bStart]) { - aStart++; - bStart++; - } // same tail: fast path - else if (a[aEnd - 1] === b[bEnd - 1]) { - aEnd--; - bEnd--; - } // The once here single last swap "fast path" has been removed in v1.1.0 - // https://github.com/WebReflection/udomdiff/blob/single-final-swap/esm/index.js#L69-L85 - // reverse swap: also fast path - else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) { - // this is a "shrink" operation that could happen in these cases: - // [1, 2, 3, 4, 5] - // [1, 4, 3, 2, 5] - // or asymmetric too - // [1, 2, 3, 4, 5] - // [1, 2, 3, 5, 6, 4] - var _node = get(a[--aEnd], -1).nextSibling; - parentNode.insertBefore(get(b[bStart++], 1), get(a[aStart++], -1).nextSibling); - parentNode.insertBefore(get(b[--bEnd], 1), _node); // mark the future index as identical (yeah, it's dirty, but cheap 👍) - // The main reason to do this, is that when a[aEnd] will be reached, - // the loop will likely be on the fast path, as identical to b[bEnd]. - // In the best case scenario, the next loop will skip the tail, - // but in the worst one, this node will be considered as already - // processed, bailing out pretty quickly from the map index check - - a[aEnd] = b[bEnd]; - } // map based fallback, "slow" path - else { - // the map requires an O(bEnd - bStart) operation once - // to store all future nodes indexes for later purposes. - // In the worst case scenario, this is a full O(N) cost, - // and such scenario happens at least when all nodes are different, - // but also if both first and last items of the lists are different - if (!map) { - map = new Map(); - var i = bStart; - - while (i < bEnd) { - map.set(b[i], i++); - } - } // if it's a future node, hence it needs some handling - - - if (map.has(a[aStart])) { - // grab the index of such node, 'cause it might have been processed - var index = map.get(a[aStart]); // if it's not already processed, look on demand for the next LCS - - if (bStart < index && index < bEnd) { - var _i = aStart; // counts the amount of nodes that are the same in the future - - var sequence = 1; - - while (++_i < aEnd && _i < bEnd && map.get(a[_i]) === index + sequence) { - sequence++; - } // effort decision here: if the sequence is longer than replaces - // needed to reach such sequence, which would brings again this loop - // to the fast path, prepend the difference before a sequence, - // and move only the future list index forward, so that aStart - // and bStart will be aligned again, hence on the fast path. - // An example considering aStart and bStart are both 0: - // a: [1, 2, 3, 4] - // b: [7, 1, 2, 3, 6] - // this would place 7 before 1 and, from that time on, 1, 2, and 3 - // will be processed at zero cost - - - if (sequence > index - bStart) { - var _node2 = get(a[aStart], 0); - - while (bStart < index) { - parentNode.insertBefore(get(b[bStart++], 1), _node2); - } - } // if the effort wasn't good enough, fallback to a replace, - // moving both source and target indexes forward, hoping that some - // similar node will be found later on, to go back to the fast path - else { - parentNode.replaceChild(get(b[bStart++], 1), get(a[aStart++], -1)); - } - } // otherwise move the source forward, 'cause there's nothing to do - else aStart++; - } // this node has no meaning in the future list, so it's more than safe - // to remove it, and check the next live node out instead, meaning - // that only the live list index should be forwarded - else parentNode.removeChild(get(a[aStart++], -1)); - } + // otherwise move the source forward, 'cause there's nothing to do + else aStart++; + } + // this node has no meaning in the future list, so it's more than safe + // to remove it, and check the next live node out instead, meaning + // that only the live list index should be forwarded + else { + get(a[aStart++], -1).remove(); + } + } } - return b; }); - exports.default = index; + exports["default"] = index; + + Object.defineProperty(exports, '__esModule', { value: true }); return exports; -}({}).default); +})({}).default; diff --git a/min.js b/min.js index edec11e..3621a07 100644 --- a/min.js +++ b/min.js @@ -1 +1 @@ -var udomdiff=function(e){"use strict";return e.default=function(e,r,i,f,l){for(var n=i.length,t=r.length,s=n,o=0,a=0,v=null;o{const n=i.length;let r=t.length,s=n,o=0,c=0,g=null;for(;ol-c){const n=f(t[o],0);for(;c{const n=s.length;let r=t.length,f=n,o=0,u=0,c=null;for(;oe-u){const l=i(t[o],0),n=[];for(;u'); const parent = document.createElement('div'); -instrument(parent); + +const { + clear, reset, verifyNodes, + random, reverse, + create1000, create10000, + append1000, prepend1000, + swapRows, updateEach10thRow +} = require('./utils.js')(document, parent, () => before); libs.forEach((lib, i) => { if (i) @@ -66,16 +73,16 @@ libs.forEach((lib, i) => { parent.appendChild(before); //* warm up + checking everything works upfront - let childNodes = create1000(parent, diff, []); + let childNodes = create1000(diff, []); console.assert( - verifyNodes(parent, childNodes, 1000), + verifyNodes(childNodes, 1000), '%s warmup create', lib ); - childNodes = create1000(parent, diff, childNodes); + childNodes = create1000(diff, childNodes); console.assert( - verifyNodes(parent, childNodes, 1000), + verifyNodes(childNodes, 1000), '%s warmup replace', lib ); @@ -88,43 +95,43 @@ libs.forEach((lib, i) => { shuffleSeed = shuffle.map((node) => childNodes.indexOf(node)); } - childNodes = append1000(parent, diff, childNodes); + childNodes = append1000(diff, childNodes); console.assert( - verifyNodes(parent, childNodes, 2000), + verifyNodes(childNodes, 2000), '%s warmup append', lib ); - childNodes = prepend1000(parent, diff, childNodes); + childNodes = prepend1000(diff, childNodes); console.assert( - verifyNodes(parent, childNodes, 3000), + verifyNodes(childNodes, 3000), '%s warmup prepend', lib ); - childNodes = clear(parent, diff, childNodes); + childNodes = clear(diff, childNodes); console.assert( - verifyNodes(parent, childNodes, 0), + verifyNodes(childNodes, 0), '%s warmup clear', lib ); - childNodes = create10000(parent, diff, childNodes); + childNodes = create10000(diff, childNodes); console.assert( - verifyNodes(parent, childNodes, 10000), + verifyNodes(childNodes, 10000), '%s warmup 10k', lib ); - childNodes = clear(parent, diff, childNodes); + childNodes = clear(diff, childNodes); console.assert( - verifyNodes(parent, childNodes, 0), + verifyNodes(childNodes, 0), '%s warmup clear 10k', lib ); - childNodes = create1000(parent, diff, childNodes); - childNodes = swapRows(parent, diff, childNodes); + childNodes = create1000(diff, childNodes); + childNodes = swapRows(diff, childNodes); console.assert(childNodes[1].textContent == 998, '%s warmup swap', lib); console.assert(childNodes[998].textContent == 1, '%s warmup swap', lib); - childNodes = clear(parent, diff, childNodes); - childNodes = create1000(parent, diff, childNodes); - childNodes = updateEach10thRow(parent, diff, childNodes); + childNodes = clear(diff, childNodes); + childNodes = create1000(diff, childNodes); + childNodes = updateEach10thRow(diff, childNodes); console.assert( /!$/.test(childNodes[0].textContent), '%s warmup update', @@ -140,9 +147,9 @@ libs.forEach((lib, i) => { '%s warmup update', lib ); - childNodes = clear(parent, diff, childNodes); + childNodes = clear(diff, childNodes); console.assert( - verifyNodes(parent, childNodes, 0), + verifyNodes(childNodes, 0), '%s warmup clear', lib ); @@ -154,13 +161,13 @@ libs.forEach((lib, i) => { let begin; const start = () => { - reset(parent); + reset(); begin = microtime.now(); }; const stop = (count, operationMax) => { const end = microtime.now() - begin; const delta = count - operationMax; - libResults.push(`${round(end / 1000)}ms + libResults.push(`${(end / 1000).toPrecision(2)}ms ${c.gray(count)}${ count > operationMax ? (delta > 99 ? '\n' : ' ') + c.bgRed.black(`+${delta}`) @@ -171,121 +178,121 @@ libs.forEach((lib, i) => { // actual benchmark start(); - childNodes = create1000(parent, diff, childNodes); - stop(parent.mutations.length, 1000); + childNodes = create1000(diff, childNodes); + stop(parent.count(), 1000); console.assert( - verifyNodes(parent, childNodes, 1000), + verifyNodes(childNodes, 1000), '%s 1k', lib ); start(); - childNodes = create1000(parent, diff, childNodes); - stop(parent.mutations.length, 2000); + childNodes = create1000(diff, childNodes); + stop(parent.count(), 2000); console.assert( - verifyNodes(parent, childNodes, 1000), + verifyNodes(childNodes, 1000), '%s replace', lib ); start(); - childNodes = random(parent, diff, childNodes); - stop(parent.mutations.length, 2000); + childNodes = random(shuffleSeed, diff, childNodes); + stop(parent.count(), 2000); console.assert( - verifyNodes(parent, childNodes, 1000), + verifyNodes(childNodes, 1000), '%s random', lib ); start(); - childNodes = reverse(parent, diff, childNodes); - stop(parent.mutations.length, 2000); + childNodes = reverse(diff, childNodes); + stop(parent.count(), 2000); console.assert( - verifyNodes(parent, childNodes, 1000), + verifyNodes(childNodes, 1000), '%s reverse', lib ); start(); - childNodes = clear(parent, diff, childNodes); - stop(parent.mutations.length, 1000); + childNodes = clear(diff, childNodes); + stop(parent.count(), 1000); console.assert( - verifyNodes(parent, childNodes, 0), + verifyNodes(childNodes, 0), '%s clear', lib ); - childNodes = create1000(parent, diff, childNodes); + childNodes = create1000(diff, childNodes); start(); - childNodes = append1000(parent, diff, childNodes); - stop(parent.mutations.length, 2000); + childNodes = append1000(diff, childNodes); + stop(parent.count(), 2000); console.assert( - verifyNodes(parent, childNodes, 2000), + verifyNodes(childNodes, 2000), '%s append 1k', lib ); start(); - childNodes = prepend1000(parent, diff, childNodes); - stop(parent.mutations.length, 1000); + childNodes = prepend1000(diff, childNodes); + stop(parent.count(), 1000); console.assert( - verifyNodes(parent, childNodes, 3000), + verifyNodes(childNodes, 3000), '%s prepend 1k', lib ); - childNodes = clear(parent, diff, childNodes); - childNodes = create1000(parent, diff, childNodes); + childNodes = clear(diff, childNodes); + childNodes = create1000(diff, childNodes); start(); - childNodes = swapRows(parent, diff, childNodes); - stop(parent.mutations.length, 4); + childNodes = swapRows(diff, childNodes); + stop(parent.count(), 4); console.assert( parent.childNodes[1].textContent == 998 && parent.childNodes[998].textContent == 1 && - verifyNodes(parent, childNodes, 1000), + verifyNodes(childNodes, 1000), '%s swap2 1k', lib ); start(); - childNodes = updateEach10thRow(parent, diff, childNodes); - stop(parent.mutations.length, 200); + childNodes = updateEach10thRow(diff, childNodes); + stop(parent.count(), 200); console.assert( - verifyNodes(parent, childNodes, 1000), + verifyNodes(childNodes, 1000), '%s update 10th', lib ); - childNodes = clear(parent, diff, childNodes); + childNodes = clear(diff, childNodes); start(); - childNodes = create10000(parent, diff, childNodes); - stop(parent.mutations.length, 10000); + childNodes = create10000(diff, childNodes); + stop(parent.count(), 10000); console.assert( - verifyNodes(parent, childNodes, 10000), + verifyNodes(childNodes, 10000), '%s 10k', lib ); start(); - childNodes = swapRows(parent, diff, childNodes); - stop(parent.mutations.length, 4); + childNodes = swapRows(diff, childNodes); + stop(parent.count(), 4); console.assert( parent.childNodes[1].textContent == 9998 && parent.childNodes[9998].textContent == 1 && - verifyNodes(parent, childNodes, 10000), + verifyNodes(childNodes, 10000), '%s swap2 10k', lib ); - childNodes = clear(parent, diff, childNodes); - reset(parent); + childNodes = clear(diff, childNodes); + reset(); //*/ - libResults.push(`${round((microtime.now() - totalStart) / 1000)}ms`); + libResults.push(`${((microtime.now() - totalStart) / 1000).toPrecision(3)}ms`); libResults.push(`${gzip}B`); // const used = process.memoryUsage().heapUsed / 1024 / 1024; @@ -307,133 +314,3 @@ table.sort((a, b) => { }); console.log(table.toString()); - - -// Benchnmark Utilities - -function instrument(parent) { - const { - appendChild, - insertBefore, - removeChild, - replaceChild - } = parent; - parent.mutations = []; - parent.appendChild = function (newNode) { - const {textContent} = newNode; - if (newNode.parentNode) - this.mutations.push(`append: drop(${textContent})`); - this.mutations.push(`append: add(${textContent})`); - return appendChild.call(this, newNode); - }; - parent.insertBefore = function (newNode, oldNode) { - const {textContent} = newNode; - if (newNode.parentNode) - this.mutations.push(`insert: drop(${textContent})`); - this.mutations.push( - oldNode ? - `insert: put(${textContent}) before (${oldNode.textContent})` : - `insert: add(${textContent})` - ); - return insertBefore.call(this, newNode, oldNode); - }; - parent.removeChild = function (oldNode) { - this.mutations.push(`remove: drop(${oldNode.textContent})`); - return removeChild.call(this, oldNode); - }; - parent.replaceChild = function (newNode, oldNode) { - const {textContent} = newNode; - this.mutations.push(`replace: drop(${oldNode.textContent})`); - if (newNode.parentNode) - this.mutations.push(`replace: drop(${textContent})`); - this.mutations.push(`replace: put(${textContent})`); - return replaceChild.call(this, newNode, oldNode); - }; -} - -function reset(parent) { - parent.mutations.splice(0); -} - -function round(num) { - return Math.round((num + Number.EPSILON) * 10) / 10; -} - -function verifyNodes(parent, childNodes, expected) { - return childNodes.length === expected && - childNodes.every((row, i) => row === parent.childNodes[i]) && - parent.childNodes.length === expected + (before ? 1 : 0) && - (!before || parent.childNodes[expected] === before); -} - - -// Benchnmark Functions - -function random(parent, diff, oldNodes) { - return diff( - parent, - oldNodes, - shuffleSeed.map((newIdx) => oldNodes[newIdx]), - get, - before - ); -} - -function reverse(parent, diff, oldNodes) { - return diff(parent, oldNodes, oldNodes.slice().reverse(), get, before); -} - -function append1000(parent, diff, oldNodes) { - const start = oldNodes.length; - const childNodes = oldNodes.slice(); - for (let i = 0; i < 1000; i++) - childNodes.push(document.createTextNode(parent, start + i)); - return diff(parent, oldNodes, childNodes, get, before); -} - -function clear(parent, diff, oldNodes) { - return diff(parent, oldNodes, [], get, before); -} - -function create1000(parent, diff, oldNodes) { - const childNodes = []; - for (let i = 0; i < 1000; i++) - childNodes.push(document.createTextNode(i)); - return diff(parent, oldNodes, childNodes, get, before); -} - -function create10000(parent, diff, oldNodes) { - const childNodes = []; - for (let i = 0; i < 10000; i++) - childNodes.push(document.createTextNode(i)); - return diff(parent, oldNodes, childNodes, get, before); -} - -function prepend1000(parent, diff, oldNodes) { - const childNodes = []; - for (let i = 0; i < 1000; i++) - childNodes.push(document.createTextNode(parent, -i)); - return diff( - parent, - oldNodes, - childNodes.reverse().concat(oldNodes), - get, - before - ); -} - -function swapRows(parent, diff, oldNodes) { - const childNodes = oldNodes.slice(); - const $1 = childNodes[1]; - const index = childNodes.length - 2; - childNodes[1] = childNodes[index]; - childNodes[index] = $1; - return diff(parent, oldNodes, childNodes, get, before); -} - -function updateEach10thRow(parent, diff, oldNodes) { - const childNodes = oldNodes.slice(); - for (let i = 0; i < childNodes.length; i += 10) - childNodes[i] = document.createTextNode(i + '!'); - return diff(parent, oldNodes, childNodes, get, before); -} diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 0000000..f905f57 --- /dev/null +++ b/test/utils.js @@ -0,0 +1,119 @@ +const get = o => o; +module.exports = (document, container, before) => { + const mutations = []; + const { + appendChild, + insertBefore, + removeChild, + replaceChild + } = container; + container.count = () => mutations.length; + container.appendChild = function (newNode) { + const {textContent} = newNode; + if (newNode.parentNode) + mutations.push(`append: drop(${textContent})`); + mutations.push(`append: add(${textContent})`); + return appendChild.call(this, newNode); + }; + container.insertBefore = function (newNode, oldNode) { + const {textContent} = newNode; + if (newNode.parentNode) + mutations.push(`insert: drop(${textContent})`); + mutations.push( + oldNode ? + `insert: put(${textContent}) before (${oldNode.textContent})` : + `insert: add(${textContent})` + ); + return insertBefore.call(this, newNode, oldNode); + }; + container.removeChild = function (oldNode) { + mutations.push(`remove: drop(${oldNode.textContent})`); + return removeChild.call(this, oldNode); + }; + container.replaceChild = function (newNode, oldNode) { + const {textContent} = newNode; + mutations.push(`replace: drop(${oldNode.textContent})`); + if (newNode.parentNode) + mutations.push(`replace: drop(${textContent})`); + mutations.push(`replace: put(${textContent})`); + return replaceChild.call(this, newNode, oldNode); + }; + const createNode = text => { + const node = document.createElement('p'); + node.appendChild(document.createTextNode(text)); + return node; + }; + return { + // Benchnmark Utilities + reset() { + mutations.splice(0); + }, + verifyNodes(childNodes, expected) { + return childNodes.length === expected && + childNodes.every((row, i) => row === container.childNodes[i]) && + container.childNodes.length === expected + (before() ? 1 : 0) && + (!before || container.childNodes[expected] === before()); + }, + // Benchnmark Functions + random(shuffleSeed, diff, oldNodes) { + return diff( + container, + oldNodes, + shuffleSeed.map((newIdx) => oldNodes[newIdx]), + get, + before() + ); + }, + reverse(diff, oldNodes) { + return diff(container, oldNodes, oldNodes.slice().reverse(), get, before()); + }, + append1000(diff, oldNodes) { + const start = oldNodes.length; + const childNodes = oldNodes.slice(); + for (let i = 0; i < 1000; i++) + childNodes.push(createNode(start + i)); + return diff(container, oldNodes, childNodes, get, before()); + }, + clear(diff, oldNodes) { + return diff(container, oldNodes, [], get, before()); + }, + create1000(diff, oldNodes) { + const childNodes = []; + for (let i = 0; i < 1000; i++) + childNodes.push(createNode(i)); + return diff(container, oldNodes, childNodes, get, before()); + }, + create10000(diff, oldNodes) { + const childNodes = []; + for (let i = 0; i < 10000; i++) + childNodes.push(createNode(i)); + return diff(container, oldNodes, childNodes, get, before()); + }, + prepend1000(diff, oldNodes) { + const childNodes = []; + for (let i = 0; i < 1000; i++) + childNodes.push(createNode(-i)); + return diff( + container, + oldNodes, + childNodes.reverse().concat(oldNodes), + get, + before() + ); + }, + swapRows(diff, oldNodes) { + const childNodes = oldNodes.slice(); + const $1 = childNodes[1]; + const index = childNodes.length - 2; + childNodes[1] = childNodes[index]; + childNodes[index] = $1; + return diff(container, oldNodes, childNodes, get, before()); + }, + updateEach10thRow(diff, oldNodes) { + const childNodes = oldNodes.slice(); + for (let i = 0; i < childNodes.length; i += 10) + childNodes[i] = createNode(i + '!'); + return diff(container, oldNodes, childNodes, get, before()); + } + }; +};