Skip to content

Commit

Permalink
All tests pass new rewrite
Browse files Browse the repository at this point in the history
  • Loading branch information
tbranyen committed Sep 6, 2016
1 parent c59b476 commit e47d98c
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 101 deletions.
62 changes: 46 additions & 16 deletions lib/node/patch.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
const isElementNode = node => node.nodeType === 1;
const { filter } = Array.prototype;
const empty = () => {};
const emptyArray = [];

/**
* Looks to see if an element can be replaced. It must have a parentNode to do
Expand Down Expand Up @@ -128,9 +129,6 @@ export default function patchNode(node, patches) {
// Turn elements into childNodes of the patch element.
node.appendChild(fragment);

// Ensure the new children are protected.
protectElement(patch.fragment);

// Add the new virtual trees into the existing tree.
for (let i = 0; i < patch.fragment.length; i++) {
patch.element.childNodes.push(patch.fragment[i]);
Expand All @@ -150,9 +148,9 @@ export default function patchNode(node, patches) {
})).filter(isElementNode);

// Turn elements into childNodes of the patch element.
node.insertBefore(fragment, oldNode);
node.parentNode.insertBefore(fragment, node);

const index = patch.fragment.indexOf(patch.oldTree);
const index = patch.element.childNodes.indexOf(patch.oldTree);

// Add the new virtual trees into the existing tree.
for (let i = 0; i < patch.fragment.length; i++) {
Expand All @@ -164,24 +162,46 @@ export default function patchNode(node, patches) {
}

else if (patch.type === REMOVE_ELEMENT) {
// Ensure we can remove the old DOM Node.
checkForMissingParent('remove', oldNode, patch);
// Ensure we can remove from the DOM Node.
//checkForMissingParent('remove', node, patch);
const { oldTree } = patch;
const fragment = patch.fragment || emptyArray;

if (oldTree) {
fragment.push(oldTree);
}

const normalize = fragment.map(makeNode).filter(isElementNode);

// Synchronously complete, unless there are transitions. If there are
// transitions this callback will be called once the Promise resolves.
const callback = () => {
if (node) {
node.removeChild(oldNode);
}
// Remove these items from the fragment and unprotect them.
for (let i = 0; i < fragment.length; i++) {
const tree = fragment[i];
const childNode = makeNode(tree);

// And then empty out the entire contents.
oldNode.innerHTML = '';
if (node) {
node.removeChild(childNode);
}
else if (childNode.parentNode) {
childNode.parentNode.removeChild(childNode);
}

unprotectElement(patch.oldTree);
unprotectElement(tree);

if (patch.element) {
const index = patch.element.childNodes.indexOf(tree);

if (index !== -1) {
patch.element.childNodes.splice(index, 1);
}
}
}
};

// Trigger the detached transitions.
trigger('detached', makePromises('detached', [oldNode]), (promises) => {
trigger('detached', makePromises('detached', normalize), (promises) => {
if (promises.length) {
Promise.all(promises).then(callback);
}
Expand Down Expand Up @@ -241,15 +261,25 @@ export default function patchNode(node, patches) {
const callback = () => {
checkForMissingParent('replace', oldNode, patch);

node.replaceChild(newNode, oldNode);
const parent = node || oldNode.parentNode;

parent.replaceChild(newNode, oldNode);

// Try and get the parent as a virtual node.
const childNodes = patch.element.childNodes;
const childNodes = (patch.element || makeTree(parent)).childNodes;
const oldIndex = childNodes.indexOf(patch.oldTree);

// Update the previous nodes with the new values.
childNodes.splice(oldIndex, 1, patch.newTree);

// Top-level element, and we need to reset the StateCache.
if (!node) {
StateCache.set(newNode, {
oldTree: patch.newTree,
element: newNode,
});
}

// Since we have replaced the element, do not track.
unprotectElement(patch.oldTree);
};
Expand Down
6 changes: 1 addition & 5 deletions lib/node/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,7 @@ export default function createTransaction(node, newHTML, options) {
// Synchronize the trees, use any middleware replacements, if supplied.
const patches = syncTree(transactionState.oldTree, transactionState.newTree);

//console.log(
// JSON.stringify(
// patches, null, 2
// )
//);
//console.log(JSON.stringify(patches, null, 2));

// Apply the set of patches to the Node.
const promises = patchNode(node, patches);
Expand Down
73 changes: 42 additions & 31 deletions lib/tree/sync.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Export the patch action type constants.
export const REPLACE_ELEMENT = -1;
export const REMOVE_ELEMENT = -2;
export const APPEND_ELEMENT = -3;
export const PREPEND_ELEMENT = -4;
export const MODIFY_ATTRIBUTE = 4;
export const CHANGE_TEXT = 5;
export const REPLACE_ELEMENT = 'REPLACE_ELEMENT';
export const REMOVE_ELEMENT = 'REMOVE_ELEMENT';
export const APPEND_ELEMENT = 'APPEND_ELEMENT';
export const PREPEND_ELEMENT = 'PREPEND_ELEMENT';
export const MODIFY_ATTRIBUTE = 'MODIFY_ATTRIBUTE';
export const CHANGE_TEXT = 'CHANGE_TEXT';

// This useful function helps us track the various states t
const handleOperationChange = (baton, type) => {
Expand All @@ -25,7 +25,7 @@ const handleOperationChange = (baton, type) => {

// If the new type is an element operation, we can add a fragment property
// to make it easier later on (no need to guard).
if (type < 1) {
if (type && type.indexOf('ELEMENT') > -1) {
baton.patch.fragment = [];
}
};
Expand Down Expand Up @@ -126,53 +126,62 @@ export default function sync(oldTree, newTree) {
// Only loop the `newChildNodes` to discover changes. If we use keys, the
// element lengths will have parity through the modifications here.
for (let i = 0; i < newChildNodes.length; i++) {
const oldNode = oldChildNodes[oldIndex];
const newNode = newChildNodes[i];
const oldKey = oldNode && oldNode.key;
const newKey = newNode && newNode.key;
const oldChildNode = oldChildNodes[oldIndex];
const newChildNode = newChildNodes[i];
const oldKey = oldChildNode && oldChildNode.key;
const newKey = newChildNode && newChildNode.key;
const keysMatch = oldKey && newKey && oldKey === newKey;

oldIndex++;
//console.log('old', JSON.stringify(oldChildNode));
//console.log('new', JSON.stringify(newChildNode));

// First look for new children to append.
if (!oldNode) {
if (!oldChildNode) {
handleOperationChange(baton, APPEND_ELEMENT);

const { patch } = baton;

patch.element = oldTree;
patch.fragment.push(newNode);
patch.fragment.push(newChildNode);
}
// Prepend new items that exist in between elements.
else if (hasOldKeys && !oldKeys.has(newKey) && (oldChildNodes.length - 1) > i) {
else if (hasOldKeys && !oldKeys.has(newKey) && oldChildNodes.length > i) {
handleOperationChange(baton, PREPEND_ELEMENT);

const { patch } = baton;
const nextChildNode = newChildNodes[i + 1];

patch.oldTree = oldNode;
patch.fragment.push(newNode);
patch.element = oldChildNode;
patch.fragment.push(newChildNode);

oldIndex--;

if (nextChildNode && nextChildNode.nodeName === '#text') {
patch.fragment.push(nextChildNode);

// Since we're looking ahead, change our increment to account for this.
i++;

// And since we're lining up with the newChildNodes, set back the old
// index.
oldIndex--;
}
}
// Replace elements when the ids are different.
else if (hasOldKeys && oldKey && newKey && oldKey !== newKey) {
handleOperationChange(baton, REPLACE_ELEMENT);

const { patch } = baton;

patch.oldTree = oldChildNode;
patch.newTree = newChildNode;
patch.element = oldTree;
}
// Remove missing items.
else if (hasOldKeys && !newKeys.has(oldKey)) {
handleOperationChange(baton, REMOVE_ELEMENT);

const { patch } = baton;
const nextChildNode = newChildNodes[i + 1];
const nextChildNode = oldChildNodes[i + 1];

patch.oldTree = oldTree;
patch.fragment.push(oldNode);
patch.element = oldTree;
patch.fragment.push(oldChildNode);

i++;

if (nextChildNode && nextChildNode.nodeName === '#text') {
patch.fragment.push(nextChildNode);
Expand All @@ -185,28 +194,30 @@ export default function sync(oldTree, newTree) {
oldIndex--;
}
}
else if (oldNode) {
sync(oldNode, newNode, patches, oldTree);
else if (oldChildNode) {
sync(oldChildNode, newChildNode, patches, oldTree);
}

oldIndex++;
}

// Find any leftover elements and remove them, ensuring a consistent render.
if (oldChildNodes.length > newChildNodes.length) {
const fragment = oldChildNodes.slice(newChildNodes.length);
const fragment = oldChildNodes.slice(oldIndex);

for (let i = 0; i < fragment.length; i++) {
const vTree = fragment[i];
const nextVTree = fragment[i + 1];

if (!oldKeys || (oldKeys && newKeys.has(vTree.key))) {
if (oldKeys && newKeys.has(vTree.key) && oldKeys.has(vTree.key)) {
continue;
}

handleOperationChange(baton, REMOVE_ELEMENT);

const { patch } = baton;

patch.oldTree = oldTree;
patch.element = oldTree;
patch.fragment.push(fragment[i]);

if (nextVTree && nextVTree.nodeName === '#text') {
Expand Down
2 changes: 1 addition & 1 deletion test/integration/outer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as diff from '../../lib/index';
import { html } from '../../lib/util/tagged-template';
import validateMemory from '../util/validateMemory';

describe.skip('Integration: outerHTML', function() {
describe('Integration: outerHTML', function() {
beforeEach(function() {
this.fixture = document.createElement('div');
this.fixture.innerHTML = '<div></div>';
Expand Down
12 changes: 7 additions & 5 deletions test/integration/transitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,14 +239,16 @@ describe('Integration: Transitions', function() {
assert.equal(result.parentNode, null);
});

it('will replace instead of remove if no new keys match', function() {
it('will replace and remove when a new key is added and old key is removed', function() {
diff.innerHTML(this.fixture, `<div>
<p id="1"></p>
<p id="2"></p>
<p key="1"></p>
<p key="2"></p>
</div>`);

assert.equal(this.fixture.querySelectorAll('p').length, 2);

diff.innerHTML(this.fixture, `<div>
<p id="3"></p>
<p key="3"></p>
</div>`);

assert.equal(this.fixture.querySelectorAll('p').length, 1);
Expand All @@ -271,7 +273,7 @@ describe('Integration: Transitions', function() {
</div>`);

assert.equal(result.id, '2');
assert.equal(result, p);
assert.equal(result, p, 'Result does not equal: ' + p.outerHTML);
assert.equal(result.parentNode, null);
});

Expand Down

0 comments on commit e47d98c

Please sign in to comment.