From a84d34e8e03a4b340873675be07a0755ba4ea422 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Fri, 9 Sep 2022 16:25:52 -0500 Subject: [PATCH 01/17] WIP #36- Compute Diffs --- src/common/JSONImporter.js | 124 +++++++++++++++++++++++++------ test/assets/DiffStates.json | 32 ++++++++ test/common/JSONImporter.spec.js | 24 ++++++ 3 files changed, 159 insertions(+), 21 deletions(-) create mode 100644 test/assets/DiffStates.json diff --git a/src/common/JSONImporter.js b/src/common/JSONImporter.js index 42f4280..0d84734 100644 --- a/src/common/JSONImporter.js +++ b/src/common/JSONImporter.js @@ -183,9 +183,80 @@ define([ await this.apply(child, children[i], resolvedSelectors); } - const current = await this.toJSON(node); - const changes = compare(current, state); + const sortedChanges = this._getSortedStateChanges(current, state); + + for (let i = 0; i < sortedChanges.length; i++) { + if (sortedChanges[i].type === 'put') { + await this._put(node, sortedChanges[i], resolvedSelectors); + } else if (sortedChanges[i].type === 'del') { + await this._delete(node, sortedChanges[i], resolvedSelectors); + } + } + + if (state.children) { + for (let i = currentChildren.length; i--;) { + this.core.deleteNode(currentChildren[i]); + } + } + } + + getDiffs(prevState, newState) { + const rootPatch = new NodePatch( + new NodeSelector(prevState.id) + ); + this._getDiffs(prevState, newState, rootPatch); + return rootPatch; + } + + _getDiffs(prevState, newState, rootPatch) { + const children = newState.children || []; + const currentChildren = prevState.children || []; + + const oldChildrenIds = new Set(currentChildren.map(currentChild => currentChild.id)); + const newChildrenIds = new Set(children.map(child => child.id)) + const [additions, updates] = partition(children, child => !oldChildrenIds.has(child.id)); + const deletions = currentChildren.filter(currentChild => !newChildrenIds.has(currentChild.id)); + rootPatch.children.push( + ...additions.map(nodeState => { + return new NodePatch( + new NodeSelector(nodeState.id), + NODE_PATCH_TYPES.ADD, + [nodeState] + ) + }) + ); + + rootPatch.children.push( + ...deletions.map(nodeState => { + return new NodePatch( + new NodeSelector(nodeState.id), + NODE_PATCH_TYPES.REMOVE, + [nodeState] + ) + }) + ); + + updates.forEach(updateState => { + const childPatch = new NodePatch(updateState.id, NODE_PATCH_TYPES.NO_CHANGE); + const oldState = currentChildren.find(currentChild => currentChild.id === updateState.id); + rootPatch.children.push(childPatch); + this.getDiffs(oldState, updateState, childPatch); + }); + + const sortedChanges = this._getSortedStateChanges(prevState, newState); + + if(sortedChanges.length) { + rootPatch.type = NODE_PATCH_TYPES.UPDATES + rootPatch.patches = sortedChanges; + } + } + + async patch(node, patch, resolvedSelectors=new NodeSelections()) { + // ToDo: Implement Patching + } + + _getSortedStateChanges(prevState, newState) { const keyOrder = [ 'children_meta', 'pointer_meta', @@ -195,12 +266,13 @@ define([ 'member_attributes', 'member_registry', ]; + + const changes = compare(prevState, newState); const singleKeyFields = ['children_meta', 'guid']; - const sortedChanges = changes - .filter( - change => change.key.length > 1 || - (singleKeyFields.includes(change.key[0]) && change.type === 'put') - ) + const sortedChanges = changes.filter( + change => change.key.length > 1 || + (singleKeyFields.includes(change.key[0]) && change.type === 'put') + ) .map((change, index) => { let order = 2 * keyOrder.indexOf(change.key[0]); if (change.type === 'put') { @@ -210,20 +282,7 @@ define([ }) .sort((p1, p2) => p1[0] - p2[0]) .map(pair => changes[pair[1]]); - - for (let i = 0; i < sortedChanges.length; i++) { - if (sortedChanges[i].type === 'put') { - await this._put(node, sortedChanges[i], resolvedSelectors); - } else if (sortedChanges[i].type === 'del') { - await this._delete(node, sortedChanges[i], resolvedSelectors); - } - } - - if (state.children) { - for (let i = currentChildren.length; i--;) { - this.core.deleteNode(currentChildren[i]); - } - } + return sortedChanges; } async resolveSelector(node, state, resolvedSelectors) { @@ -732,6 +791,13 @@ define([ return object; } + function partition(array, fn) { + return array.reduce((arr, next, index, records) => { + arr[fn(next, index, records) ? 0 : 1].push(next); + return arr; + }, [[], []]); + } + class NodeSelector { constructor(idString='') { if (idString.startsWith('/')) { @@ -1006,6 +1072,22 @@ define([ } } + const NODE_PATCH_TYPES = { + REMOVE: 'remove', + ADD: 'add', + UPDATES: 'updates', + NO_CHANGE: 'noChange' + }; + + class NodePatch { + constructor(selector, type=NODE_PATCH_TYPES.NO_CHANGE, patches=[], children=[]) { + this.selector = selector; + this.type = type; + this.patches = patches; + this.children = children; + } + } + const RELATED_PROPERTIES = { sets: ['member_attributes', 'member_registry'], children: ['children_meta'], diff --git a/test/assets/DiffStates.json b/test/assets/DiffStates.json new file mode 100644 index 0000000..6e185f9 --- /dev/null +++ b/test/assets/DiffStates.json @@ -0,0 +1,32 @@ +{ + "putNewChildren": { + "prevState": { + "id": "@guid:12454", + "children": [ + { + "id": "@guid:14523", + "attributes": { + "name": "guid-14523" + } + } + ] + }, + "newState": { + "id": "@guid:12454", + "children": [ + { + "id": "@guid:14523", + "attributes": { + "name": "guid-14523" + } + }, + { + "id": "@guid:14526", + "attributes": { + "name": "guid-14526" + } + } + ] + } + } +} \ No newline at end of file diff --git a/test/common/JSONImporter.spec.js b/test/common/JSONImporter.spec.js index 777da3b..bd8c856 100644 --- a/test/common/JSONImporter.spec.js +++ b/test/common/JSONImporter.spec.js @@ -11,7 +11,9 @@ describe('JSONImporter', function () { const assert = require('assert'); const gmeConfig = testFixture.getGmeConfig(); const path = testFixture.path; + const fs = require('fs'); const SEED_DIR = path.join(__dirname, '..', '..', 'src', 'seeds'); + const ASSETS_DIR = path.join(__dirname, '..', 'assets'); const Q = testFixture.Q; const logger = testFixture.logger.fork('JSONImporter'); const projectName = 'testProject'; @@ -1278,4 +1280,26 @@ describe('JSONImporter', function () { } }); }); + + describe.only('diff', function() { + let diffStates; + before(function () { + diffStates = JSON.parse( + fs.readFileSync(path.join(ASSETS_DIR, 'DiffStates.json'), 'utf-8') + ); + }); + + it('should add single key children', () => { + const {prevState, newState} = diffStates.putNewChildren; + const diff = importer.getDiffs(prevState, newState); + console.log(JSON.stringify(diff)); + + }); + + it('should remove single key children', () => { + const {prevState, newState} = diffStates.putNewChildren; + const diff = importer.getDiffs(newState, prevState); + console.log(JSON.stringify(diff)); + }); + }); }); From af486ca000df770a84198e7a2dc802d6c6d32c22 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Mon, 12 Sep 2022 18:41:10 -0500 Subject: [PATCH 02/17] WIP- resolve most issues (10 failing) --- src/common/JSONImporter.js | 302 +++++++++++++++++++++++++------------ 1 file changed, 205 insertions(+), 97 deletions(-) diff --git a/src/common/JSONImporter.js b/src/common/JSONImporter.js index 0d84734..acefce4 100644 --- a/src/common/JSONImporter.js +++ b/src/common/JSONImporter.js @@ -167,93 +167,131 @@ define([ return json; } - async apply (node, state, resolvedSelectors=new NodeSelections()) { - await this.resolveSelectors(node, state, resolvedSelectors); - - const children = state.children || []; - const currentChildren = await this.core.loadChildren(node); - - for (let i = 0; i < children.length; i++) { - const idString = children[i].id; - const child = await this.findNode(node, idString, resolvedSelectors); - const index = currentChildren.indexOf(child); - if (index > -1) { - currentChildren.splice(index, 1); - } - - await this.apply(child, children[i], resolvedSelectors); - } - const current = await this.toJSON(node); - const sortedChanges = this._getSortedStateChanges(current, state); - - for (let i = 0; i < sortedChanges.length; i++) { - if (sortedChanges[i].type === 'put') { - await this._put(node, sortedChanges[i], resolvedSelectors); - } else if (sortedChanges[i].type === 'del') { - await this._delete(node, sortedChanges[i], resolvedSelectors); - } - } - - if (state.children) { - for (let i = currentChildren.length; i--;) { - this.core.deleteNode(currentChildren[i]); - } - } + // async apply (node, state, resolvedSelectors=new NodeSelections()) { + // await this.resolveSelectors(node, state, resolvedSelectors); + // + // const children = state.children || []; + // const currentChildren = await this.core.loadChildren(node); + // + // for (let i = 0; i < children.length; i++) { + // const idString = children[i].id; + // const child = await this.findNode(node, idString, resolvedSelectors); + // const index = currentChildren.indexOf(child); + // if (index > -1) { + // currentChildren.splice(index, 1); + // } + // + // await this.apply(child, children[i], resolvedSelectors); + // } + // const current = await this.toJSON(node); + // const sortedChanges = this._getSortedStateChanges(current, state); + // + // for (let i = 0; i < sortedChanges.length; i++) { + // if (sortedChanges[i].type === 'put') { + // await this._put(node, sortedChanges[i], resolvedSelectors); + // } else if (sortedChanges[i].type === 'del') { + // await this._delete(node, sortedChanges[i], resolvedSelectors); + // } + // } + // + // if (state.children) { + // for (let i = currentChildren.length; i--;) { + // this.core.deleteNode(currentChildren[i]); + // } + // } + // } + + async apply(node, state, resolvedSelectors = new NodeSelections()) { + const toOmit = state.children?.length === 0 ? ['children']: []; + const prevState = await this.toJSON(node, new OmittedProperties(toOmit)); + const diffs = await this.getDiffs(prevState, state, resolvedSelectors, this.core.getParent(node)); + await this.patch(node, diffs, resolvedSelectors); } - getDiffs(prevState, newState) { - const rootPatch = new NodePatch( - new NodeSelector(prevState.id) + async getDiffs(prevState, newState, resolvedSelectors=new NodeSelections(), parent=null) { + const rootPatch = new NodeDiff( + prevState.id ); - this._getDiffs(prevState, newState, rootPatch); + await this._getDiffs(prevState, newState, rootPatch, parent, resolvedSelectors); return rootPatch; } - _getDiffs(prevState, newState, rootPatch) { - const children = newState.children || []; - const currentChildren = prevState.children || []; - - const oldChildrenIds = new Set(currentChildren.map(currentChild => currentChild.id)); - const newChildrenIds = new Set(children.map(child => child.id)) - const [additions, updates] = partition(children, child => !oldChildrenIds.has(child.id)); - const deletions = currentChildren.filter(currentChild => !newChildrenIds.has(currentChild.id)); - rootPatch.children.push( + async _getDiffs(prevState, newState, rootDiff, parent, resolvedSelectors) { + const node = await this.resolveSelectorsFromState(prevState, parent, resolvedSelectors); + console.log(JSON.stringify({prevState, newState}, null, 2)); + const {additions, updates, removals} = this.getImmediateChildrenDiffs(prevState, newState); + rootDiff.children.push( ...additions.map(nodeState => { - return new NodePatch( - new NodeSelector(nodeState.id), + return new NodeDiff( + nodeState.id, NODE_PATCH_TYPES.ADD, [nodeState] ) }) ); - rootPatch.children.push( - ...deletions.map(nodeState => { - return new NodePatch( - new NodeSelector(nodeState.id), + rootDiff.children.push( + ...removals.map(nodeState => { + return new NodeDiff( + nodeState.id, NODE_PATCH_TYPES.REMOVE, [nodeState] ) }) ); - updates.forEach(updateState => { - const childPatch = new NodePatch(updateState.id, NODE_PATCH_TYPES.NO_CHANGE); - const oldState = currentChildren.find(currentChild => currentChild.id === updateState.id); - rootPatch.children.push(childPatch); - this.getDiffs(oldState, updateState, childPatch); - }); + await Promise.all(updates.map(async ([prev, new_]) => { + const childPatch = new NodeDiff(prev.id, NODE_PATCH_TYPES.NO_CHANGE); + rootDiff.children.push(childPatch); + return await this._getDiffs(prev, new_, childPatch, node, resolvedSelectors); + })); const sortedChanges = this._getSortedStateChanges(prevState, newState); - if(sortedChanges.length) { - rootPatch.type = NODE_PATCH_TYPES.UPDATES - rootPatch.patches = sortedChanges; + rootDiff.type = NODE_PATCH_TYPES.UPDATES + rootDiff.patches = sortedChanges; } + } - async patch(node, patch, resolvedSelectors=new NodeSelections()) { - // ToDo: Implement Patching + getImmediateChildrenDiffs(prevState, newState) { + const children = newState.children || []; + const prevChildren = prevState.children || []; + + const childrenPairsNewOld = children.map(child => { + if(child.id) { + const childSelector = new NodeSelector(child.id); + return [childSelector.findInParentState({children: prevChildren}, this.core, this.rootNode), child]; + } else { + return [undefined, child]; + } + }); + let [additions, updates] = partition(childrenPairsNewOld, ([prevChild, _]) => !prevChild); + additions = additions.map(add => add.shift()); + + const updatedStateIds = updates.map(([prev, _]) => prev).map(prev => prev.id).filter(id => !!id); + const wasUpdated = child => { + return updatedStateIds.includes(child.id); + } + + const removals = prevChildren.filter(child => !wasUpdated(child)); + return {additions, updates, removals}; + } + + async patch(node, diff, resolvedSelectors) { + await this._patch(node, this.core.getParent(node), diff, resolvedSelectors); + } + + async _patch(node, parent, diff, resolvedSelectors) { + if(node && !parent) { + parent = this.core.getParent(node); + } + + node = await this._patch[diff.type].call(this, ...arguments); + await(Promise.all(diff.children.map( async child => { + const childNode = await this.findNode(parent, child.id, resolvedSelectors); + await this._patch(childNode, node, child, resolvedSelectors); + }))); } _getSortedStateChanges(prevState, newState) { @@ -360,6 +398,16 @@ define([ await this.tryResolveSelectors(stateNodePairs, resolvedSelectors); } + async resolveSelectorsFromState(state, parent, resolvedSelectors) { + const nodeSelector = new NodeSelector(state.id); + const node = await nodeSelector.findNode(this.core, this.rootNode, parent, resolvedSelectors); + if(!node) { + throw new Error(`Can't find node with id: ${state.id}`); + } + await this.resolveSelectors(node, state, resolvedSelectors); + return node; + } + async findNode(parent, idString, resolvedSelectors=new NodeSelections()) { if (idString === undefined) { return; @@ -436,6 +484,42 @@ define([ return node; } } + const NODE_PATCH_TYPES = { + REMOVE: 'remove', + ADD: 'add', + UPDATES: 'updates', + NO_CHANGE: 'noChange' + }; + + Importer.prototype._patch[NODE_PATCH_TYPES.ADD] = async function(node, parent, diff, resolvedSelectors) { + const state = diff.patches.pop(); + const base = state.pointers?.base; + const baseNode = await this.findNode(parent, base, resolvedSelectors); + const createdNode = await this.createNode(parent, state, baseNode); + return createdNode; + } + + Importer.prototype._patch[NODE_PATCH_TYPES.REMOVE] = async function(node, parent, diff, resolvedSelectors) { + this.core.deleteNode(node); + return node; + } + + Importer.prototype._patch[NODE_PATCH_TYPES.UPDATES] = async function(node, parent, diff, resolvedSelectors) { + const sortedChanges = diff.patches; + for (let i = 0; i < sortedChanges.length; i++) { + if (sortedChanges[i].type === 'put') { + await this._put(node, sortedChanges[i], resolvedSelectors); + } else if (sortedChanges[i].type === 'del') { + await this._delete(node, sortedChanges[i], resolvedSelectors); + } + } + return node; + } + + Importer.prototype._patch[NODE_PATCH_TYPES.NO_CHANGE] = async function(node, parent, diff, resolvedSelectors) { + return node; + } + Importer.prototype._put.guid = async function(node, change, resolvedSelectors) { const {value} = change; @@ -464,7 +548,6 @@ define([ change.key.length === 2, `Complex attributes not currently supported: ${change.key.join(', ')}` ); - const [/*type*/, name] = change.key; this.core.setAttribute(node, name, change.value); }; @@ -858,33 +941,13 @@ define([ } if (this.tag === '@meta') { - const metanodes = Object.values(core.getAllMetaNodes(rootNode)); - const libraries = core.getLibraryNames(rootNode) - .map(name => [ - core.getPath(core.getLibraryRoot(rootNode, name)), - name, - ]); - - function getFullyQualifiedName(node) { - const name = core.getAttribute(node, 'name'); - const path = core.getPath(node); - const libraryPair = libraries.find(([rootPath,]) => path.startsWith(rootPath)); - if (libraryPair) { - const [,libraryName] = libraryPair; - return libraryName + '.' + name; - } - return name; - } - - return metanodes - .find(child => { - const name = core.getAttribute(child, 'name'); - const fullName = getFullyQualifiedName(child); - return name === this.value || fullName === this.value; - }); + return this.findMetaNodeForTag(core, rootNode); } if (this.tag === '@attribute') { + if(!parent) { + throw new Error(`cannot resolve tag ${this.tag} without a parent`); + } const [attr, value] = this.value; const children = await core.loadChildren(parent); return children @@ -915,6 +978,58 @@ define([ throw new Error(`Unknown tag: ${this.tag}`); } + findInParentState(state, core, rootNode) { // How to deal with crisscross between @meta and @id tags? + if (this.tag === '@path') { + return state.children.find(child => child.id === this.value || child.path === this.value.split(':').pop()); + } + + if (this.tag === '@guid') { + return state.children.find(child => child.id === this.value || child.guid === this.value.split(':').pop()); + } + + if (this.tag === '@attribute') { + const [attr, value] = this.value; + return state.children.find(child => { + return child.attributes ? child.attributes[attr] === value : false; + }); + } + + if(this.tag === '@meta') { + const metaNode = this.findMetaNodeForTag(core, rootNode); + const [guid, path] = [core.getGuid(metaNode), core.getPath(metaNode)]; + return state.children.find(child => { + return [guid, path].includes(child.guid) || [guid, path].includes(child.path); + }) + } + } + + findMetaNodeForTag(core, rootNode) { + const metanodes = Object.values(core.getAllMetaNodes(rootNode)); + const libraries = core.getLibraryNames(rootNode) + .map(name => [ + core.getPath(core.getLibraryRoot(rootNode, name)), + name, + ]); + + function getFullyQualifiedName(node) { + const name = core.getAttribute(node, 'name'); + const path = core.getPath(node); + const libraryPair = libraries.find(([rootPath,]) => path.startsWith(rootPath)); + if (libraryPair) { + const [, libraryName] = libraryPair; + return libraryName + '.' + name; + } + return name; + } + + return metanodes + .find(child => { + const name = core.getAttribute(child, 'name'); + const fullName = getFullyQualifiedName(child); + return name === this.value || fullName === this.value; + }); + } + async nodeSearch(core, node, fn, searchOpts = new NodeSearchOpts()) { if (searchOpts.cache && searchOpts.cacheKey) { const {cache, cacheKey} = searchOpts; @@ -1072,16 +1187,9 @@ define([ } } - const NODE_PATCH_TYPES = { - REMOVE: 'remove', - ADD: 'add', - UPDATES: 'updates', - NO_CHANGE: 'noChange' - }; - - class NodePatch { - constructor(selector, type=NODE_PATCH_TYPES.NO_CHANGE, patches=[], children=[]) { - this.selector = selector; + class NodeDiff { + constructor(nodeId, type=NODE_PATCH_TYPES.NO_CHANGE, patches=[], children=[]) { + this.id = nodeId; this.type = type; this.patches = patches; this.children = children; From 434f13310a96ddf76be7d552d5bb5edc64b06e0c Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Tue, 13 Sep 2022 18:07:55 -0500 Subject: [PATCH 03/17] Clean separation of concerns for diffs; Id resolutions remaining --- src/common/JSONImporter.js | 327 +++++++++++++------------------ test/common/JSONImporter.spec.js | 29 +-- 2 files changed, 138 insertions(+), 218 deletions(-) diff --git a/src/common/JSONImporter.js b/src/common/JSONImporter.js index acefce4..5e8ac1a 100644 --- a/src/common/JSONImporter.js +++ b/src/common/JSONImporter.js @@ -167,131 +167,79 @@ define([ return json; } - // async apply (node, state, resolvedSelectors=new NodeSelections()) { - // await this.resolveSelectors(node, state, resolvedSelectors); - // - // const children = state.children || []; - // const currentChildren = await this.core.loadChildren(node); - // - // for (let i = 0; i < children.length; i++) { - // const idString = children[i].id; - // const child = await this.findNode(node, idString, resolvedSelectors); - // const index = currentChildren.indexOf(child); - // if (index > -1) { - // currentChildren.splice(index, 1); - // } - // - // await this.apply(child, children[i], resolvedSelectors); - // } - // const current = await this.toJSON(node); - // const sortedChanges = this._getSortedStateChanges(current, state); - // - // for (let i = 0; i < sortedChanges.length; i++) { - // if (sortedChanges[i].type === 'put') { - // await this._put(node, sortedChanges[i], resolvedSelectors); - // } else if (sortedChanges[i].type === 'del') { - // await this._delete(node, sortedChanges[i], resolvedSelectors); - // } - // } - // - // if (state.children) { - // for (let i = currentChildren.length; i--;) { - // this.core.deleteNode(currentChildren[i]); - // } - // } - // } - async apply(node, state, resolvedSelectors = new NodeSelections()) { - const toOmit = state.children?.length === 0 ? ['children']: []; - const prevState = await this.toJSON(node, new OmittedProperties(toOmit)); - const diffs = await this.getDiffs(prevState, state, resolvedSelectors, this.core.getParent(node)); - await this.patch(node, diffs, resolvedSelectors); - } - - async getDiffs(prevState, newState, resolvedSelectors=new NodeSelections(), parent=null) { - const rootPatch = new NodeDiff( - prevState.id - ); - await this._getDiffs(prevState, newState, rootPatch, parent, resolvedSelectors); - return rootPatch; - } - - async _getDiffs(prevState, newState, rootDiff, parent, resolvedSelectors) { - const node = await this.resolveSelectorsFromState(prevState, parent, resolvedSelectors); - console.log(JSON.stringify({prevState, newState}, null, 2)); - const {additions, updates, removals} = this.getImmediateChildrenDiffs(prevState, newState); - rootDiff.children.push( - ...additions.map(nodeState => { - return new NodeDiff( - nodeState.id, + const diff = await this.getDiffs(node, state, resolvedSelectors); + await this._patch(diff, resolvedSelectors); + } + + async getDiffs(node, state, resolvedSelectors=new NodeSelections()) { + await this.resolveSelectorsForExistingNodes(node, state, resolvedSelectors); + const rootDiff = new NodeDiff(state.id); + const children = state.children || []; + const currentChildren = await this.core.loadChildren(node); + await Promise.all(children.map( async childState => { + const idString = childState.id; + const childNode = await this.findNode(node, idString, resolvedSelectors); + const index = currentChildren.indexOf(childNode); + if(index > -1) { + currentChildren.splice(index, 1); + } + if(childNode) { + rootDiff.children.push(await this.getDiffs(childNode, childState, resolvedSelectors)); + } else { + rootDiff.children.push(new NodeDiff( + childState.id || '', NODE_PATCH_TYPES.ADD, - [nodeState] - ) - }) - ); - - rootDiff.children.push( - ...removals.map(nodeState => { - return new NodeDiff( - nodeState.id, - NODE_PATCH_TYPES.REMOVE, - [nodeState] - ) - }) - ); - - await Promise.all(updates.map(async ([prev, new_]) => { - const childPatch = new NodeDiff(prev.id, NODE_PATCH_TYPES.NO_CHANGE); - rootDiff.children.push(childPatch); - return await this._getDiffs(prev, new_, childPatch, node, resolvedSelectors); + [{ + parentPath: this.core.getPath(node), + state: childState, + }] + )); + } })); - - const sortedChanges = this._getSortedStateChanges(prevState, newState); - if(sortedChanges.length) { - rootDiff.type = NODE_PATCH_TYPES.UPDATES - rootDiff.patches = sortedChanges; + const current = await this.toJSON(node, new OmittedProperties(['children'])); + const changes = this._getSortedStateChanges(current, state); + if(changes.length) { + rootDiff.type = NODE_PATCH_TYPES.UPDATES; + const parent = this.core.getParent(node); + const parentPath = this.core.getPath(parent); + rootDiff.patches = changes.map(change => { + change.parentPath = parentPath; + change.nodeId = state.id || this.core.getPath(node); + return change; + }); } - } - - getImmediateChildrenDiffs(prevState, newState) { - const children = newState.children || []; - const prevChildren = prevState.children || []; - - const childrenPairsNewOld = children.map(child => { - if(child.id) { - const childSelector = new NodeSelector(child.id); - return [childSelector.findInParentState({children: prevChildren}, this.core, this.rootNode), child]; - } else { - return [undefined, child]; - } - }); - let [additions, updates] = partition(childrenPairsNewOld, ([prevChild, _]) => !prevChild); - additions = additions.map(add => add.shift()); - - const updatedStateIds = updates.map(([prev, _]) => prev).map(prev => prev.id).filter(id => !!id); - const wasUpdated = child => { - return updatedStateIds.includes(child.id); + if(state.children && currentChildren.length) { + const deletions = currentChildren.map(child => new NodeDiff( + this.core.getPath(child), + NODE_PATCH_TYPES.REMOVE, + [{ + parentPath: this.core.getPath(node), + nodeId: `@path:${this.core.getPath(child)}` + }] + )); + + rootDiff.children.push(...deletions); } - const removals = prevChildren.filter(child => !wasUpdated(child)); - return {additions, updates, removals}; - } - - async patch(node, diff, resolvedSelectors) { - await this._patch(node, this.core.getParent(node), diff, resolvedSelectors); + return rootDiff; } - async _patch(node, parent, diff, resolvedSelectors) { - if(node && !parent) { - parent = this.core.getParent(node); - } + async _patch(diff, resolvedSelectors) { + await this._patch[diff.type].call(this, ...arguments); + const [childrenWithids, otherChildren] = partition( + diff.children, childDiff => (childDiff.id || '').startsWith('@id') + ); // Make sure children with @ids are created/updated first + await Promise.all(childrenWithids.map(async child => { + const args = [child, resolvedSelectors]; + await this._patch[child.type].call(this, ...args); + })); - node = await this._patch[diff.type].call(this, ...arguments); - await(Promise.all(diff.children.map( async child => { - const childNode = await this.findNode(parent, child.id, resolvedSelectors); - await this._patch(childNode, node, child, resolvedSelectors); - }))); + await Promise.all(otherChildren.map(async child => { + const args = [child, resolvedSelectors]; + await this._patch[child.type].call(this, ...args); + })); } _getSortedStateChanges(prevState, newState) { @@ -344,6 +292,15 @@ define([ return (state.children || []).map(s => [s, node]); } + async getCreationSubtrees(node, state, resolvedSelectors) { + const stateNodePairs = this.getChildStateNodePairs(node, state); + const toCreateNodes = (await Promise.all(stateNodePairs + .filter(async ([state, parent]) => { + return !(await this.findNode(parent, state.id, resolvedSelectors)); + }))); + return toCreateNodes; + } + async tryResolveSelectors(stateNodePairs, resolvedSelectors) { let tryResolveMore = true; while (tryResolveMore) { @@ -352,33 +309,33 @@ define([ const [state, parentNode] = stateNodePairs[i]; let child = await this.findNode(parentNode, state.id, resolvedSelectors); //const canCreate = !state.id; - if (!child /*&& canCreate*/) { - let baseNode; - if (state.pointers) { - const {base} = state.pointers; - if (!base) { - const stateID = state.id || JSON.stringify(state); - throw new Error(`No base provided for ${stateID}`); - } - baseNode = await this.findNode(parentNode, base, resolvedSelectors); - - - } else { - const fco = await this.core.loadByPath(this.rootNode, '/1'); - baseNode = fco; - } - - if (baseNode) { - child = await this.createNode(parentNode, state, baseNode); - } - } - + // if (!child /*&& canCreate*/) { + // let baseNode; + // if (state.pointers) { + // const {base} = state.pointers; + // if (!base) { + // const stateID = state.id || JSON.stringify(state); + // throw new Error(`No base provided for ${stateID}`); + // } + // baseNode = await this.findNode(parentNode, base, resolvedSelectors); + // + // + // } else { + // const fco = await this.core.loadByPath(this.rootNode, '/1'); + // baseNode = fco; + // } + // + // if (baseNode) { + // child = await this.createNode(parentNode, state, baseNode); + // } + // } + let pairs = []; if (child) { this.resolveSelector(child, state, resolvedSelectors); - const pairs = this.getChildStateNodePairs(child, state); - stateNodePairs.splice(i, 1, ...pairs); + pairs = this.getChildStateNodePairs(child, state); tryResolveMore = true; } + stateNodePairs.splice(i, 1, ...pairs); } } @@ -387,7 +344,7 @@ define([ } } - async resolveSelectors(node, state, resolvedSelectors) { + async resolveSelectorsForExistingNodes(node, state, resolvedSelectors) { const parent = this.core.getParent(node); if (state.id && parent) { @@ -398,16 +355,6 @@ define([ await this.tryResolveSelectors(stateNodePairs, resolvedSelectors); } - async resolveSelectorsFromState(state, parent, resolvedSelectors) { - const nodeSelector = new NodeSelector(state.id); - const node = await nodeSelector.findNode(this.core, this.rootNode, parent, resolvedSelectors); - if(!node) { - throw new Error(`Can't find node with id: ${state.id}`); - } - await this.resolveSelectors(node, state, resolvedSelectors); - return node; - } - async findNode(parent, idString, resolvedSelectors=new NodeSelections()) { if (idString === undefined) { return; @@ -458,6 +405,25 @@ define([ return node; } + async createStateSubTree(parentPath, state, resolvedSelectors) { + const base = state.pointers?.base; + const parent = await this.core.loadByPath(this.rootNode, parentPath); + const baseNode = await this.findNode(parent, base, resolvedSelectors); + const created = await this.createNode(parent, state, baseNode); + const nodeSelector = new NodeSelector(this.core.getPath(created)); + resolvedSelectors.record(parentPath, nodeSelector, created); + const nodeState = await this.toJSON(created, new OmittedProperties(['children'])); + const changes = this._getSortedStateChanges(nodeState, state); + await Promise.all(changes.map(async change => { + await this._put(created, change, resolvedSelectors); + })); + await Promise.all((state.children || []).map(async child => { + await this.createStateSubTree(this.core.getPath(created), child, resolvedSelectors); + })); + + return created; + } + async _put (node, change) { const [type] = change.key; if (type !== 'path' && type !== 'id') { @@ -491,33 +457,31 @@ define([ NO_CHANGE: 'noChange' }; - Importer.prototype._patch[NODE_PATCH_TYPES.ADD] = async function(node, parent, diff, resolvedSelectors) { - const state = diff.patches.pop(); - const base = state.pointers?.base; - const baseNode = await this.findNode(parent, base, resolvedSelectors); - const createdNode = await this.createNode(parent, state, baseNode); - return createdNode; + Importer.prototype._patch[NODE_PATCH_TYPES.ADD] = async function(diff, resolvedSelectors) { + const {parentPath, state} = diff.patches.pop(); + const node = await this.createStateSubTree(parentPath, state, resolvedSelectors); + return node; } - Importer.prototype._patch[NODE_PATCH_TYPES.REMOVE] = async function(node, parent, diff, resolvedSelectors) { + Importer.prototype._patch[NODE_PATCH_TYPES.REMOVE] = async function(diff, resolvedSelectors) { + const {parentPath, nodeId} = diff.patches.pop(); + const parent = await this.core.loadByPath(this.rootNode, parentPath); + const node = await this.findNode(parent, nodeId, resolvedSelectors); this.core.deleteNode(node); - return node; } - Importer.prototype._patch[NODE_PATCH_TYPES.UPDATES] = async function(node, parent, diff, resolvedSelectors) { - const sortedChanges = diff.patches; - for (let i = 0; i < sortedChanges.length; i++) { - if (sortedChanges[i].type === 'put') { - await this._put(node, sortedChanges[i], resolvedSelectors); - } else if (sortedChanges[i].type === 'del') { - await this._delete(node, sortedChanges[i], resolvedSelectors); - } - } - return node; + Importer.prototype._patch[NODE_PATCH_TYPES.UPDATES] = async function(diff, resolvedSelectors) { + const changes = diff.patches; + return await Promise.all(changes.map(async change => { + const parent = await this.core.loadByPath(this.rootNode, change.parentPath); + const node = await this.findNode(parent, change.nodeId, resolvedSelectors); + if(change.type === 'put') return await this._put(node, change, resolvedSelectors); + if(change.type === 'del') return await this._delete(node, change, resolvedSelectors); + })); } - Importer.prototype._patch[NODE_PATCH_TYPES.NO_CHANGE] = async function(node, parent, diff, resolvedSelectors) { - return node; + Importer.prototype._patch[NODE_PATCH_TYPES.NO_CHANGE] = async function(diff, resolvedSelectors) { + } @@ -978,31 +942,6 @@ define([ throw new Error(`Unknown tag: ${this.tag}`); } - findInParentState(state, core, rootNode) { // How to deal with crisscross between @meta and @id tags? - if (this.tag === '@path') { - return state.children.find(child => child.id === this.value || child.path === this.value.split(':').pop()); - } - - if (this.tag === '@guid') { - return state.children.find(child => child.id === this.value || child.guid === this.value.split(':').pop()); - } - - if (this.tag === '@attribute') { - const [attr, value] = this.value; - return state.children.find(child => { - return child.attributes ? child.attributes[attr] === value : false; - }); - } - - if(this.tag === '@meta') { - const metaNode = this.findMetaNodeForTag(core, rootNode); - const [guid, path] = [core.getGuid(metaNode), core.getPath(metaNode)]; - return state.children.find(child => { - return [guid, path].includes(child.guid) || [guid, path].includes(child.path); - }) - } - } - findMetaNodeForTag(core, rootNode) { const metanodes = Object.values(core.getAllMetaNodes(rootNode)); const libraries = core.getLibraryNames(rootNode) diff --git a/test/common/JSONImporter.spec.js b/test/common/JSONImporter.spec.js index bd8c856..5146454 100644 --- a/test/common/JSONImporter.spec.js +++ b/test/common/JSONImporter.spec.js @@ -246,7 +246,6 @@ describe('JSONImporter', function () { original2 = await importer.toJSON(node2); const nodePath = core.getPath(node3); original2.pointers.base = nodePath; - await importer.apply(node2, original2); assert.equal(core.getPointerPath(node2, 'base'), nodePath); assert( @@ -305,7 +304,7 @@ describe('JSONImporter', function () { }); it('should set base correctly during structural inheritance', async function() { - // Create nodes: A, B, and A' where + // Create nodes: A, B, and A' where // - B is contained in A // - A' inherits from A // @@ -1082,7 +1081,7 @@ describe('JSONImporter', function () { ] }; const selectors = new NodeSelections(); - await importer.resolveSelectors(parent, parentJson, selectors); + await importer.resolveSelectorsForExistingNodes(parent, parentJson, selectors); assert.equal(selectors.cache.length, 1); }); @@ -1101,7 +1100,7 @@ describe('JSONImporter', function () { ] }; const selectors = new NodeSelections(); - await importer.resolveSelectors(parent, parentJson, selectors); + await importer.resolveSelectorsForExistingNodes(parent, parentJson, selectors); assert.equal(selectors.cache.length, 2); }); @@ -1281,25 +1280,7 @@ describe('JSONImporter', function () { }); }); - describe.only('diff', function() { - let diffStates; - before(function () { - diffStates = JSON.parse( - fs.readFileSync(path.join(ASSETS_DIR, 'DiffStates.json'), 'utf-8') - ); - }); - - it('should add single key children', () => { - const {prevState, newState} = diffStates.putNewChildren; - const diff = importer.getDiffs(prevState, newState); - console.log(JSON.stringify(diff)); - - }); - - it('should remove single key children', () => { - const {prevState, newState} = diffStates.putNewChildren; - const diff = importer.getDiffs(newState, prevState); - console.log(JSON.stringify(diff)); - }); + describe('diff', function() { + // ToDo: add testing; }); }); From 11d455298649657f81bab480d038b6ddb89a3f2d Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Wed, 14 Sep 2022 14:22:26 -0500 Subject: [PATCH 04/17] WIP- Cleanup api, interative diffs --- src/common/JSONImporter.js | 217 ++++++++++++++++++------------------- 1 file changed, 107 insertions(+), 110 deletions(-) diff --git a/src/common/JSONImporter.js b/src/common/JSONImporter.js index 5e8ac1a..6561250 100644 --- a/src/common/JSONImporter.js +++ b/src/common/JSONImporter.js @@ -168,77 +168,82 @@ define([ } async apply(node, state, resolvedSelectors = new NodeSelections()) { - const diff = await this.getDiffs(node, state, resolvedSelectors); - await this._patch(diff, resolvedSelectors); + const diffs = await this.getDiffs(node, state, resolvedSelectors); + await this.applyDiffs(diffs, resolvedSelectors); } async getDiffs(node, state, resolvedSelectors=new NodeSelections()) { await this.resolveSelectorsForExistingNodes(node, state, resolvedSelectors); - const rootDiff = new NodeDiff(state.id); + + const parent = this.core.getParent(node); + const parentPath = this.core.getPath(parent) || ''; + const nodePath = this.core.getPath(node); + const diffs = []; const children = state.children || []; const currentChildren = await this.core.loadChildren(node); - await Promise.all(children.map( async childState => { + + diffs.push(...(await Promise.all(children.map(async childState => { const idString = childState.id; const childNode = await this.findNode(node, idString, resolvedSelectors); const index = currentChildren.indexOf(childNode); - if(index > -1) { + if (index > -1) { currentChildren.splice(index, 1); } - if(childNode) { - rootDiff.children.push(await this.getDiffs(childNode, childState, resolvedSelectors)); + if (childNode) { + const childDiffs = await this.getDiffs(childNode, childState, resolvedSelectors); + return childDiffs; } else { - rootDiff.children.push(new NodeDiff( - childState.id || '', - NODE_PATCH_TYPES.ADD, - [{ - parentPath: this.core.getPath(node), - state: childState, - }] - )); + return [ + new NodeChangeSet( + nodePath, + childState.id || '', + 'add_subtree', + '', + childState + ) + ]; } - })); + })))); const current = await this.toJSON(node, new OmittedProperties(['children'])); const changes = this._getSortedStateChanges(current, state); if(changes.length) { - rootDiff.type = NODE_PATCH_TYPES.UPDATES; - const parent = this.core.getParent(node); - const parentPath = this.core.getPath(parent); - rootDiff.patches = changes.map(change => { - change.parentPath = parentPath; - change.nodeId = state.id || this.core.getPath(node); - return change; - }); + diffs.push(...changes.map( + change => NodeChangeSet.fromDiffObj( + parentPath, + nodePath, + change + ) + )); } if(state.children && currentChildren.length) { - const deletions = currentChildren.map(child => new NodeDiff( + const deletions = currentChildren.map(child => new NodeChangeSet( + nodePath, + this.core.getPath(child), + 'remove_node', this.core.getPath(child), - NODE_PATCH_TYPES.REMOVE, - [{ - parentPath: this.core.getPath(node), - nodeId: `@path:${this.core.getPath(child)}` - }] + null )); - - rootDiff.children.push(...deletions); + diffs.push(...deletions); } - return rootDiff; + return diffs.flat(); } - async _patch(diff, resolvedSelectors) { - await this._patch[diff.type].call(this, ...arguments); - const [childrenWithids, otherChildren] = partition( - diff.children, childDiff => (childDiff.id || '').startsWith('@id') - ); // Make sure children with @ids are created/updated first - await Promise.all(childrenWithids.map(async child => { - const args = [child, resolvedSelectors]; - await this._patch[child.type].call(this, ...args); - })); + async applyDiffsForNode(node, diffs) { + const resolvedSelectors = new NodeSelections(); + const currentState = await this.toJSON(node); + await this.resolveSelectors(node, currentState, resolvedSelectors); + await this.applyDiffs(diffs, resolvedSelectors); + } + + async applyDiffs(diffs, resolvedSelectors) { + const [additions, remaining] = partition(diffs, diff => diff.type === 'add_subtree' && (diff.nodeId || '').startsWith('@id')); - await Promise.all(otherChildren.map(async child => { - const args = [child, resolvedSelectors]; - await this._patch[child.type].call(this, ...args); + await Promise.all([...additions, ...remaining ].map(async diff => { + const parent = await this.core.loadByPath(this.rootNode, diff.parentPath); + const node = await this.findNode(parent, diff.nodeId, resolvedSelectors); + return await this.applyDiffs[diff.type].call(this, node, diff, resolvedSelectors); })); } @@ -292,16 +297,7 @@ define([ return (state.children || []).map(s => [s, node]); } - async getCreationSubtrees(node, state, resolvedSelectors) { - const stateNodePairs = this.getChildStateNodePairs(node, state); - const toCreateNodes = (await Promise.all(stateNodePairs - .filter(async ([state, parent]) => { - return !(await this.findNode(parent, state.id, resolvedSelectors)); - }))); - return toCreateNodes; - } - - async tryResolveSelectors(stateNodePairs, resolvedSelectors) { + async tryResolveSelectors(stateNodePairs, resolvedSelectors, create) { let tryResolveMore = true; while (tryResolveMore) { tryResolveMore = false; @@ -309,29 +305,29 @@ define([ const [state, parentNode] = stateNodePairs[i]; let child = await this.findNode(parentNode, state.id, resolvedSelectors); //const canCreate = !state.id; - // if (!child /*&& canCreate*/) { - // let baseNode; - // if (state.pointers) { - // const {base} = state.pointers; - // if (!base) { - // const stateID = state.id || JSON.stringify(state); - // throw new Error(`No base provided for ${stateID}`); - // } - // baseNode = await this.findNode(parentNode, base, resolvedSelectors); - // - // - // } else { - // const fco = await this.core.loadByPath(this.rootNode, '/1'); - // baseNode = fco; - // } - // - // if (baseNode) { - // child = await this.createNode(parentNode, state, baseNode); - // } - // } + if (!child && create) { + let baseNode; + if (state.pointers) { + const {base} = state.pointers; + if (!base) { + const stateID = state.id || JSON.stringify(state); + throw new Error(`No base provided for ${stateID}`); + } + baseNode = await this.findNode(parentNode, base, resolvedSelectors); + + + } else { + const fco = await this.core.loadByPath(this.rootNode, '/1'); + baseNode = fco; + } + + if (baseNode) { + child = await this.createNode(parentNode, state, baseNode); + } + } let pairs = []; if (child) { - this.resolveSelector(child, state, resolvedSelectors); + this.resolveSelector(child, state, resolvedSelectors, create); pairs = this.getChildStateNodePairs(child, state); tryResolveMore = true; } @@ -345,6 +341,10 @@ define([ } async resolveSelectorsForExistingNodes(node, state, resolvedSelectors) { + await this.resolveSelectors(node, state, resolvedSelectors, false); + } + + async resolveSelectors(node, state, resolvedSelectors, create=true) { const parent = this.core.getParent(node); if (state.id && parent) { @@ -352,7 +352,7 @@ define([ } const stateNodePairs = this.getChildStateNodePairs(node, state); - await this.tryResolveSelectors(stateNodePairs, resolvedSelectors); + await this.tryResolveSelectors(stateNodePairs, resolvedSelectors, create); } async findNode(parent, idString, resolvedSelectors=new NodeSelections()) { @@ -450,39 +450,23 @@ define([ return node; } } - const NODE_PATCH_TYPES = { - REMOVE: 'remove', - ADD: 'add', - UPDATES: 'updates', - NO_CHANGE: 'noChange' - }; - Importer.prototype._patch[NODE_PATCH_TYPES.ADD] = async function(diff, resolvedSelectors) { - const {parentPath, state} = diff.patches.pop(); - const node = await this.createStateSubTree(parentPath, state, resolvedSelectors); - return node; - } + Importer.prototype.applyDiffs.add_subtree = async function(node, change, resolvedSelectors) { + const created = await this.createStateSubTree(change.parentPath, change.value, resolvedSelectors); + return created; + }; - Importer.prototype._patch[NODE_PATCH_TYPES.REMOVE] = async function(diff, resolvedSelectors) { - const {parentPath, nodeId} = diff.patches.pop(); - const parent = await this.core.loadByPath(this.rootNode, parentPath); - const node = await this.findNode(parent, nodeId, resolvedSelectors); + Importer.prototype.applyDiffs.remove_node = async function(node, /*change, resolvedSelectors*/) { this.core.deleteNode(node); - } - - Importer.prototype._patch[NODE_PATCH_TYPES.UPDATES] = async function(diff, resolvedSelectors) { - const changes = diff.patches; - return await Promise.all(changes.map(async change => { - const parent = await this.core.loadByPath(this.rootNode, change.parentPath); - const node = await this.findNode(parent, change.nodeId, resolvedSelectors); - if(change.type === 'put') return await this._put(node, change, resolvedSelectors); - if(change.type === 'del') return await this._delete(node, change, resolvedSelectors); - })); - } + }; - Importer.prototype._patch[NODE_PATCH_TYPES.NO_CHANGE] = async function(diff, resolvedSelectors) { + Importer.prototype.applyDiffs.put = async function(node, change, resolvedSelectors) { + return await this._put(node, change, resolvedSelectors); + }; - } + Importer.prototype.applyDiffs.del = async function(node, change, resolvedSelectors) { + return await this._delete(node, change, resolvedSelectors); + }; Importer.prototype._put.guid = async function(node, change, resolvedSelectors) { @@ -1126,12 +1110,23 @@ define([ } } - class NodeDiff { - constructor(nodeId, type=NODE_PATCH_TYPES.NO_CHANGE, patches=[], children=[]) { - this.id = nodeId; + class NodeChangeSet { + constructor(parentPath, nodeId, type, key, value) { + this.parentPath = parentPath; + this.nodeId = nodeId; this.type = type; - this.patches = patches; - this.children = children; + this.key = key; + this.value = value; + } + + static fromDiffObj(parentPath, nodeId, diffObj) { + return new NodeChangeSet( + parentPath, + nodeId, + diffObj.type, + diffObj.key, + diffObj.value + ); } } @@ -1165,5 +1160,7 @@ define([ Importer.NodeSelector = NodeSelector; Importer.NodeSelections = NodeSelections; Importer.OmittedProperties = OmittedProperties; + Importer.NodeChangeSet = NodeChangeSet; + Importer.diff = compare; return Importer; }); From 2b3fa60ecff87e9f309570224634fead6011ac80 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Wed, 14 Sep 2022 14:25:48 -0500 Subject: [PATCH 05/17] Remove assets dir, cleanup test --- test/assets/DiffStates.json | 32 -------------------------------- test/common/JSONImporter.spec.js | 3 +-- 2 files changed, 1 insertion(+), 34 deletions(-) delete mode 100644 test/assets/DiffStates.json diff --git a/test/assets/DiffStates.json b/test/assets/DiffStates.json deleted file mode 100644 index 6e185f9..0000000 --- a/test/assets/DiffStates.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "putNewChildren": { - "prevState": { - "id": "@guid:12454", - "children": [ - { - "id": "@guid:14523", - "attributes": { - "name": "guid-14523" - } - } - ] - }, - "newState": { - "id": "@guid:12454", - "children": [ - { - "id": "@guid:14523", - "attributes": { - "name": "guid-14523" - } - }, - { - "id": "@guid:14526", - "attributes": { - "name": "guid-14526" - } - } - ] - } - } -} \ No newline at end of file diff --git a/test/common/JSONImporter.spec.js b/test/common/JSONImporter.spec.js index 5146454..fd75c45 100644 --- a/test/common/JSONImporter.spec.js +++ b/test/common/JSONImporter.spec.js @@ -11,9 +11,7 @@ describe('JSONImporter', function () { const assert = require('assert'); const gmeConfig = testFixture.getGmeConfig(); const path = testFixture.path; - const fs = require('fs'); const SEED_DIR = path.join(__dirname, '..', '..', 'src', 'seeds'); - const ASSETS_DIR = path.join(__dirname, '..', 'assets'); const Q = testFixture.Q; const logger = testFixture.logger.fork('JSONImporter'); const projectName = 'testProject'; @@ -246,6 +244,7 @@ describe('JSONImporter', function () { original2 = await importer.toJSON(node2); const nodePath = core.getPath(node3); original2.pointers.base = nodePath; + await importer.apply(node2, original2); assert.equal(core.getPointerPath(node2, 'base'), nodePath); assert( From 59d174e36706b00c076606ce8c0f7fd4f40a549a Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Wed, 14 Sep 2022 14:54:48 -0500 Subject: [PATCH 06/17] Flat where needed not in return value; remove_node to remove_subtree --- src/common/JSONImporter.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/common/JSONImporter.js b/src/common/JSONImporter.js index 6561250..e7da16b 100644 --- a/src/common/JSONImporter.js +++ b/src/common/JSONImporter.js @@ -203,7 +203,7 @@ define([ ) ]; } - })))); + }))).flat()); const current = await this.toJSON(node, new OmittedProperties(['children'])); const changes = this._getSortedStateChanges(current, state); if(changes.length) { @@ -220,14 +220,14 @@ define([ const deletions = currentChildren.map(child => new NodeChangeSet( nodePath, this.core.getPath(child), - 'remove_node', + 'remove_subtree', this.core.getPath(child), null )); diffs.push(...deletions); } - return diffs.flat(); + return diffs; } async applyDiffsForNode(node, diffs) { @@ -238,9 +238,7 @@ define([ } async applyDiffs(diffs, resolvedSelectors) { - const [additions, remaining] = partition(diffs, diff => diff.type === 'add_subtree' && (diff.nodeId || '').startsWith('@id')); - - await Promise.all([...additions, ...remaining ].map(async diff => { + await Promise.all(diffs.map(async diff => { const parent = await this.core.loadByPath(this.rootNode, diff.parentPath); const node = await this.findNode(parent, diff.nodeId, resolvedSelectors); return await this.applyDiffs[diff.type].call(this, node, diff, resolvedSelectors); @@ -456,7 +454,7 @@ define([ return created; }; - Importer.prototype.applyDiffs.remove_node = async function(node, /*change, resolvedSelectors*/) { + Importer.prototype.applyDiffs.remove_subtree = async function(node, /*change, resolvedSelectors*/) { this.core.deleteNode(node); }; From dc0133a951412238af61858edd03ab80e74509ff Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Thu, 15 Sep 2022 14:03:14 -0500 Subject: [PATCH 07/17] WIP- use state id rather than nodePath --- src/common/JSONImporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/JSONImporter.js b/src/common/JSONImporter.js index e7da16b..c3caa6c 100644 --- a/src/common/JSONImporter.js +++ b/src/common/JSONImporter.js @@ -210,7 +210,7 @@ define([ diffs.push(...changes.map( change => NodeChangeSet.fromDiffObj( parentPath, - nodePath, + state.id, change ) )); From ddbce4f8f56f6adc6a1b571d7c0fe7502b677307 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Thu, 15 Sep 2022 14:09:51 -0500 Subject: [PATCH 08/17] WIP- optionally add `nodePath` if id not present --- src/common/JSONImporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/JSONImporter.js b/src/common/JSONImporter.js index c3caa6c..99da7d7 100644 --- a/src/common/JSONImporter.js +++ b/src/common/JSONImporter.js @@ -210,7 +210,7 @@ define([ diffs.push(...changes.map( change => NodeChangeSet.fromDiffObj( parentPath, - state.id, + state.id || nodePath, change ) )); From e0ee322d5dc598afd0b7bb2c01ecbc47c0425012 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Thu, 15 Sep 2022 15:27:49 -0500 Subject: [PATCH 09/17] Refactoring; Record extra info in resolved selectors Currently, the resolveSelectors, doesn't record the node if the state doesn't have ID. However, in `patchSync` we might need the node info even if the state id is not present. This refactor tries to handle it by adding the current node's path to cache as well. --- src/common/JSONImporter.js | 41 ++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/common/JSONImporter.js b/src/common/JSONImporter.js index 99da7d7..53195e9 100644 --- a/src/common/JSONImporter.js +++ b/src/common/JSONImporter.js @@ -168,11 +168,11 @@ define([ } async apply(node, state, resolvedSelectors = new NodeSelections()) { - const diffs = await this.getDiffs(node, state, resolvedSelectors); - await this.applyDiffs(diffs, resolvedSelectors); + const diffs = await this.diff(node, state, resolvedSelectors); + await this.patchSync(diffs, resolvedSelectors); } - async getDiffs(node, state, resolvedSelectors=new NodeSelections()) { + async diff(node, state, resolvedSelectors=new NodeSelections()) { await this.resolveSelectorsForExistingNodes(node, state, resolvedSelectors); const parent = this.core.getParent(node); @@ -190,7 +190,7 @@ define([ currentChildren.splice(index, 1); } if (childNode) { - const childDiffs = await this.getDiffs(childNode, childState, resolvedSelectors); + const childDiffs = await this.diff(childNode, childState, resolvedSelectors); return childDiffs; } else { return [ @@ -230,18 +230,19 @@ define([ return diffs; } - async applyDiffsForNode(node, diffs) { - const resolvedSelectors = new NodeSelections(); - const currentState = await this.toJSON(node); - await this.resolveSelectors(node, currentState, resolvedSelectors); - await this.applyDiffs(diffs, resolvedSelectors); + async patch(node, diffs, resolvedSelectors=new NodeSelections()) { + const diffIds = diffs.map(diff => { + return {id: diff.nodeId}; + }); + await Promise.all(diffIds.map(async diffId => await this.resolveSelectorsForExistingNodes(node, diffId, resolvedSelectors))); + await this.patchSync(diffs, resolvedSelectors); } - async applyDiffs(diffs, resolvedSelectors) { + async patchSync(diffs, resolvedSelectors) { await Promise.all(diffs.map(async diff => { - const parent = await this.core.loadByPath(this.rootNode, diff.parentPath); - const node = await this.findNode(parent, diff.nodeId, resolvedSelectors); - return await this.applyDiffs[diff.type].call(this, node, diff, resolvedSelectors); + const selector = new NodeSelector(diff.nodeId); + const node = resolvedSelectors.get(diff.parentPath, selector); + return await this.patchSync[diff.type].call(this, node, diff, resolvedSelectors); })); } @@ -274,7 +275,7 @@ define([ return sortedChanges; } - async resolveSelector(node, state, resolvedSelectors) { + resolveSelector(node, state, resolvedSelectors) { const parent = this.core.getParent(node); if (!parent) { @@ -344,7 +345,9 @@ define([ async resolveSelectors(node, state, resolvedSelectors, create=true) { const parent = this.core.getParent(node); - + if(parent) { + this.resolveSelector(node, {id: this.core.getPath(node)}, resolvedSelectors); + } if (state.id && parent) { this.resolveSelector(node, state, resolvedSelectors); } @@ -449,20 +452,20 @@ define([ } } - Importer.prototype.applyDiffs.add_subtree = async function(node, change, resolvedSelectors) { + Importer.prototype.patchSync.add_subtree = async function(node, change, resolvedSelectors) { const created = await this.createStateSubTree(change.parentPath, change.value, resolvedSelectors); return created; }; - Importer.prototype.applyDiffs.remove_subtree = async function(node, /*change, resolvedSelectors*/) { + Importer.prototype.patchSync.remove_subtree = async function(node, /*change, resolvedSelectors*/) { this.core.deleteNode(node); }; - Importer.prototype.applyDiffs.put = async function(node, change, resolvedSelectors) { + Importer.prototype.patchSync.put = async function(node, change, resolvedSelectors) { return await this._put(node, change, resolvedSelectors); }; - Importer.prototype.applyDiffs.del = async function(node, change, resolvedSelectors) { + Importer.prototype.patchSync.del = async function(node, change, resolvedSelectors) { return await this._delete(node, change, resolvedSelectors); }; From 3d3ee43ba891a34b0733ab0cb5a8ee2f3f4a70af Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Thu, 15 Sep 2022 19:07:45 -0500 Subject: [PATCH 10/17] Don't lookup nodes for add subtree --- src/common/JSONImporter.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/common/JSONImporter.js b/src/common/JSONImporter.js index 53195e9..261b0bf 100644 --- a/src/common/JSONImporter.js +++ b/src/common/JSONImporter.js @@ -240,8 +240,11 @@ define([ async patchSync(diffs, resolvedSelectors) { await Promise.all(diffs.map(async diff => { - const selector = new NodeSelector(diff.nodeId); - const node = resolvedSelectors.get(diff.parentPath, selector); + let node=null; + if(diff.type !== 'add_subtree'){ + const parent = await this.core.loadByPath(this.rootNode, diff.parentPath); + node = await this.findNode(parent, diff.nodeId, resolvedSelectors); + } return await this.patchSync[diff.type].call(this, node, diff, resolvedSelectors); })); } @@ -498,7 +501,7 @@ define([ `Complex attributes not currently supported: ${change.key.join(', ')}` ); const [/*type*/, name] = change.key; - this.core.setAttribute(node, name, change.value); + this.core.setAttribute(node, name, change.value || ''); }; Importer.prototype._delete.attributes = function(node, change) { From dae1c6c71a71209c8122ae3040759c4f22ee6671 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Fri, 16 Sep 2022 17:48:09 -0500 Subject: [PATCH 11/17] Fix cases for @id based resolutions --- src/common/JSONImporter.js | 44 +++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/common/JSONImporter.js b/src/common/JSONImporter.js index 261b0bf..ebdf16a 100644 --- a/src/common/JSONImporter.js +++ b/src/common/JSONImporter.js @@ -169,7 +169,7 @@ define([ async apply(node, state, resolvedSelectors = new NodeSelections()) { const diffs = await this.diff(node, state, resolvedSelectors); - await this.patchSync(diffs, resolvedSelectors); + await this._patch(diffs, resolvedSelectors); } async diff(node, state, resolvedSelectors=new NodeSelections()) { @@ -235,18 +235,28 @@ define([ return {id: diff.nodeId}; }); await Promise.all(diffIds.map(async diffId => await this.resolveSelectorsForExistingNodes(node, diffId, resolvedSelectors))); - await this.patchSync(diffs, resolvedSelectors); + await this._patch(diffs, resolvedSelectors); } - async patchSync(diffs, resolvedSelectors) { - await Promise.all(diffs.map(async diff => { - let node=null; - if(diff.type !== 'add_subtree'){ - const parent = await this.core.loadByPath(this.rootNode, diff.parentPath); - node = await this.findNode(parent, diff.nodeId, resolvedSelectors); - } - return await this.patchSync[diff.type].call(this, node, diff, resolvedSelectors); - })); + async _patch(diffs, resolvedSelectors) { + const [firstOrderDiffs, dependentDiffs] = this._partitionDiffs(diffs); + const apply = diffs => { + return diffs.map(async diff => { + let node = null; + if (diff.type !== 'add_subtree') { + const parent = await this.core.loadByPath(this.rootNode, diff.parentPath); + node = await this.findNode(parent, diff.nodeId, resolvedSelectors); + } + return await this._patch[diff.type].call(this, node, diff, resolvedSelectors); + }); + }; + + await Promise.all(apply(firstOrderDiffs)); + await Promise.all(apply(dependentDiffs)); + } + + _partitionDiffs(diffs) { + return partition(diffs, diff => diff.type === 'add_subtree' && diff.nodeId.startsWith('@id')); } _getSortedStateChanges(prevState, newState) { @@ -416,6 +426,10 @@ define([ const created = await this.createNode(parent, state, baseNode); const nodeSelector = new NodeSelector(this.core.getPath(created)); resolvedSelectors.record(parentPath, nodeSelector, created); + if(state.id && !state.id.startsWith('@internal')) { + const alternateSelector = new NodeSelector(state.id); + resolvedSelectors.record(parentPath, alternateSelector, created); + } const nodeState = await this.toJSON(created, new OmittedProperties(['children'])); const changes = this._getSortedStateChanges(nodeState, state); await Promise.all(changes.map(async change => { @@ -455,20 +469,20 @@ define([ } } - Importer.prototype.patchSync.add_subtree = async function(node, change, resolvedSelectors) { + Importer.prototype._patch.add_subtree = async function(node, change, resolvedSelectors) { const created = await this.createStateSubTree(change.parentPath, change.value, resolvedSelectors); return created; }; - Importer.prototype.patchSync.remove_subtree = async function(node, /*change, resolvedSelectors*/) { + Importer.prototype._patch.remove_subtree = async function(node, /*change, resolvedSelectors*/) { this.core.deleteNode(node); }; - Importer.prototype.patchSync.put = async function(node, change, resolvedSelectors) { + Importer.prototype._patch.put = async function(node, change, resolvedSelectors) { return await this._put(node, change, resolvedSelectors); }; - Importer.prototype.patchSync.del = async function(node, change, resolvedSelectors) { + Importer.prototype._patch.del = async function(node, change, resolvedSelectors) { return await this._delete(node, change, resolvedSelectors); }; From 6716aaa9121e78d86581a8fe6c688f087edf98e1 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Fri, 16 Sep 2022 18:36:21 -0500 Subject: [PATCH 12/17] Tests for diffs --- src/common/JSONImporter.js | 4 +- test/common/JSONImporter.spec.js | 92 +++++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/src/common/JSONImporter.js b/src/common/JSONImporter.js index ebdf16a..5605d8a 100644 --- a/src/common/JSONImporter.js +++ b/src/common/JSONImporter.js @@ -239,7 +239,7 @@ define([ } async _patch(diffs, resolvedSelectors) { - const [firstOrderDiffs, dependentDiffs] = this._partitionDiffs(diffs); + const [firstOrderDiffs, dependentDiffs] = this._partitionDiffsByPriority(diffs); const apply = diffs => { return diffs.map(async diff => { let node = null; @@ -255,7 +255,7 @@ define([ await Promise.all(apply(dependentDiffs)); } - _partitionDiffs(diffs) { + _partitionDiffsByPriority(diffs) { return partition(diffs, diff => diff.type === 'add_subtree' && diff.nodeId.startsWith('@id')); } diff --git a/test/common/JSONImporter.spec.js b/test/common/JSONImporter.spec.js index fd75c45..efcb15f 100644 --- a/test/common/JSONImporter.spec.js +++ b/test/common/JSONImporter.spec.js @@ -1279,7 +1279,95 @@ describe('JSONImporter', function () { }); }); - describe('diff', function() { - // ToDo: add testing; + describe.only('diff', function () { + let node1; + beforeEach(async function () { + node1 = core.createNode({ + parent: root, + base: fco + }); + }); + + it('should find a single diff on attribute addition', async () => { + core.setAttribute(node1, 'name', 'New Name'); + const state = await importer.toJSON(node1); + core.delAttribute(node1, 'name'); + const diffs = await importer.diff(node1, state); + + assert.equal(diffs.length, 1, 'more than once diff found'); + const diff = diffs.shift(); + const [type, name] = diff.key; + assert(diff.type === 'put'); + assert(type === 'attributes'); + assert(name === 'name'); + assert(diff.value === 'New Name'); + }); + + it('should find add_subtree diff type on children addition', async () => { + const state = { + children: [{ + attributes: { + attr1: 'attr1', + } + }] + }; + + const diff = (await importer.diff(node1, state)).shift(); + assert(diff.type === 'add_subtree', 'add_subtree diff not found') + }); + + it('should find delete_subtree children diff for removed children', async () => { + + range(5).forEach(idx => { + const node = core.createNode({ + parent: node1, + base: fco + }); + core.setAttribute(node, 'name', `child${idx}`); + }); + + const state = await importer.toJSON(node1); + state.children.splice(0, 2); + + const diffs = await importer.diff(node1, state); + assert(diffs.length === 2); + assert(diffs.every(diff => diff.type === 'remove_subtree')); + }); + + it('should find pointer changes in diff by path', async () => { + range(5).forEach(idx => { + const node = core.createNode({ + parent: node1, + base: fco + }); + core.setAttribute(node, 'name', `child${idx}`); + }); + + const node2 = core.createNode({ + parent: root, + base: fco + }); + + const state = await importer.toJSON(node1); + state.children.forEach(child => child.pointers.base = core.getPath(node2)); + + const diffs = await importer.diff(node1, state); + assert(diffs.length === 5, 'more than 5 diffs found'); + assert(diffs.every(diff => { + const diffType = diff.type; + const [type, name] = diff.key; + const value = diff.value; + return ( + diffType === 'put' && + type === 'pointers' && + name === 'base' && + value === core.getPath(node2) + ); + })); + }); }); }); + +function range(size, startAt = 0) { + return [...Array(size).keys()].map(i => i + startAt); +} \ No newline at end of file From 839f31cd0d6e3a746139833a73e88ca7e4f7704f Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Fri, 16 Sep 2022 18:38:46 -0500 Subject: [PATCH 13/17] WIP- Basic tests for diffs --- test/common/JSONImporter.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/common/JSONImporter.spec.js b/test/common/JSONImporter.spec.js index efcb15f..79535a8 100644 --- a/test/common/JSONImporter.spec.js +++ b/test/common/JSONImporter.spec.js @@ -1313,7 +1313,7 @@ describe('JSONImporter', function () { }; const diff = (await importer.diff(node1, state)).shift(); - assert(diff.type === 'add_subtree', 'add_subtree diff not found') + assert(diff.type === 'add_subtree', 'add_subtree diff not found'); }); it('should find delete_subtree children diff for removed children', async () => { From 370e8bc509d52fd40f1bcfe64a67b31f9f8410ea Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Fri, 16 Sep 2022 18:43:04 -0500 Subject: [PATCH 14/17] Empty newline for file ending --- test/common/JSONImporter.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/common/JSONImporter.spec.js b/test/common/JSONImporter.spec.js index 79535a8..aa7c0e6 100644 --- a/test/common/JSONImporter.spec.js +++ b/test/common/JSONImporter.spec.js @@ -1370,4 +1370,4 @@ describe('JSONImporter', function () { function range(size, startAt = 0) { return [...Array(size).keys()].map(i => i + startAt); -} \ No newline at end of file +} From 720b94f69654382ddb3c3982a72b0fdaa5d643a5 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Fri, 16 Sep 2022 22:15:02 -0500 Subject: [PATCH 15/17] WIP- fix describe.only --- test/common/JSONImporter.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/common/JSONImporter.spec.js b/test/common/JSONImporter.spec.js index aa7c0e6..e634bb4 100644 --- a/test/common/JSONImporter.spec.js +++ b/test/common/JSONImporter.spec.js @@ -1279,7 +1279,7 @@ describe('JSONImporter', function () { }); }); - describe.only('diff', function () { + describe('diff', function () { let node1; beforeEach(async function () { node1 = core.createNode({ From b3a8460d6b21f9a063583ce1e20bc7b489e208b8 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Tue, 20 Sep 2022 09:57:35 -0500 Subject: [PATCH 16/17] WIP- uniform diff types and for children addition/removal --- src/common/JSONImporter.js | 55 ++++++++++++++++++-------------- test/common/JSONImporter.spec.js | 10 +++--- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/common/JSONImporter.js b/src/common/JSONImporter.js index 5605d8a..649069f 100644 --- a/src/common/JSONImporter.js +++ b/src/common/JSONImporter.js @@ -197,8 +197,8 @@ define([ new NodeChangeSet( nodePath, childState.id || '', - 'add_subtree', - '', + 'put', + ['children'], childState ) ]; @@ -217,13 +217,16 @@ define([ } if(state.children && currentChildren.length) { - const deletions = currentChildren.map(child => new NodeChangeSet( - nodePath, - this.core.getPath(child), - 'remove_subtree', - this.core.getPath(child), - null - )); + const deletions = currentChildren.map(child =>{ + const childPath = this.core.getPath(child); + return new NodeChangeSet( + nodePath, + childPath, + 'del', + ['children'], + childPath + ) + }); diffs.push(...deletions); } @@ -243,11 +246,17 @@ define([ const apply = diffs => { return diffs.map(async diff => { let node = null; - if (diff.type !== 'add_subtree') { + const isNewNode = (diff.type === 'put' && diff.key[0] === 'children'); + if (!isNewNode) { const parent = await this.core.loadByPath(this.rootNode, diff.parentPath); node = await this.findNode(parent, diff.nodeId, resolvedSelectors); } - return await this._patch[diff.type].call(this, node, diff, resolvedSelectors); + + if(diff.type === 'put') { + return await this._put(node, diff, resolvedSelectors); + } else if(diff.type === 'del') { + return await this._delete(node, diff, resolvedSelectors); + } }); }; @@ -256,7 +265,14 @@ define([ } _partitionDiffsByPriority(diffs) { - return partition(diffs, diff => diff.type === 'add_subtree' && diff.nodeId.startsWith('@id')); + const isIdBasedCreation = (diff) => { + const type = diff.type; + const [key,] = diff.key; + const nodeSelectorKey = diff.nodeId.slice(0, 2); + return type === 'put' && key === 'children' && nodeSelectorKey === '@id'; + }; + + return partition(diffs, isIdBasedCreation); } _getSortedStateChanges(prevState, newState) { @@ -454,7 +470,7 @@ define([ async _delete (node, change) { const [type] = change.key; - if (change.key.length > 1) { + if (change.key.length > 1 || type === 'children') { if (!this._delete[type]) { throw new Error(`Unrecognized key ${type}`); } @@ -469,24 +485,15 @@ define([ } } - Importer.prototype._patch.add_subtree = async function(node, change, resolvedSelectors) { + Importer.prototype._put.children = async function(node, change, resolvedSelectors) { const created = await this.createStateSubTree(change.parentPath, change.value, resolvedSelectors); return created; }; - Importer.prototype._patch.remove_subtree = async function(node, /*change, resolvedSelectors*/) { + Importer.prototype._delete.children = async function(node, /*change, resolvedSelectors*/) { this.core.deleteNode(node); }; - Importer.prototype._patch.put = async function(node, change, resolvedSelectors) { - return await this._put(node, change, resolvedSelectors); - }; - - Importer.prototype._patch.del = async function(node, change, resolvedSelectors) { - return await this._delete(node, change, resolvedSelectors); - }; - - Importer.prototype._put.guid = async function(node, change, resolvedSelectors) { const {value} = change; this.core.setGuid(node, value); diff --git a/test/common/JSONImporter.spec.js b/test/common/JSONImporter.spec.js index aa7c0e6..597807e 100644 --- a/test/common/JSONImporter.spec.js +++ b/test/common/JSONImporter.spec.js @@ -1279,7 +1279,7 @@ describe('JSONImporter', function () { }); }); - describe.only('diff', function () { + describe('diff', function () { let node1; beforeEach(async function () { node1 = core.createNode({ @@ -1303,7 +1303,7 @@ describe('JSONImporter', function () { assert(diff.value === 'New Name'); }); - it('should find add_subtree diff type on children addition', async () => { + it('should find put children diff type on children addition', async () => { const state = { children: [{ attributes: { @@ -1313,10 +1313,10 @@ describe('JSONImporter', function () { }; const diff = (await importer.diff(node1, state)).shift(); - assert(diff.type === 'add_subtree', 'add_subtree diff not found'); + assert(diff.type === 'put' && diff.key[0] === 'children', 'add_subtree diff not found'); }); - it('should find delete_subtree children diff for removed children', async () => { + it('should find del children diff type on children removal', async () => { range(5).forEach(idx => { const node = core.createNode({ @@ -1331,7 +1331,7 @@ describe('JSONImporter', function () { const diffs = await importer.diff(node1, state); assert(diffs.length === 2); - assert(diffs.every(diff => diff.type === 'remove_subtree')); + assert(diffs.every(diff => diff.type === 'del' && diff.key[0] === 'children')); }); it('should find pointer changes in diff by path', async () => { From f76a750cc5e0ef92d6d8e3ca60fb5c920b92a375 Mon Sep 17 00:00:00 2001 From: Brian Broll Date: Tue, 20 Sep 2022 11:19:26 -0500 Subject: [PATCH 17/17] fix error msg --- test/common/JSONImporter.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/common/JSONImporter.spec.js b/test/common/JSONImporter.spec.js index 597807e..dc50b9e 100644 --- a/test/common/JSONImporter.spec.js +++ b/test/common/JSONImporter.spec.js @@ -1313,7 +1313,7 @@ describe('JSONImporter', function () { }; const diff = (await importer.diff(node1, state)).shift(); - assert(diff.type === 'put' && diff.key[0] === 'children', 'add_subtree diff not found'); + assert(diff.type === 'put' && diff.key[0] === 'children', 'put children diff not found'); }); it('should find del children diff type on children removal', async () => {