From 977e29cfdb2b622a7032ba6d19e287bae0a40ad6 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Thu, 11 Dec 2014 23:52:53 -0500 Subject: [PATCH] Most iD.actions.MergeRemoteChanges features complete (todo: merging ways where nodelist has not been reordered) --- js/id/actions/merge_remote_changes.js | 89 ++-- test/spec/actions/merge_remote_changes.js | 470 ++++++++++++++-------- 2 files changed, 358 insertions(+), 201 deletions(-) diff --git a/js/id/actions/merge_remote_changes.js b/js/id/actions/merge_remote_changes.js index 509d8187c..a7e867093 100644 --- a/js/id/actions/merge_remote_changes.js +++ b/js/id/actions/merge_remote_changes.js @@ -1,29 +1,57 @@ iD.actions.MergeRemoteChanges = function(id, localGraph, remoteGraph) { var base = localGraph.base().entities[id], - local = localGraph.entities[id], - remote = remoteGraph.entities[id], - option = 'safe', // 'safe', 'force_local', 'force_remote' - target; + local = localGraph.entity(id), + remote = remoteGraph.entity(id), + option = 'safe'; // 'safe', 'force_local', 'force_remote' - function sameLocation() { - var epsilon = 1e-6; - return (Math.abs(remote.loc[0] - local.loc[0]) < epsilon) && - (Math.abs(remote.loc[1] - local.loc[1]) < epsilon); + + function mergeLocation(target) { + function pointEqual(a, b) { + var epsilon = 1e-6; + return (Math.abs(a[0] - b[0]) < epsilon) && (Math.abs(a[1] - b[1]) < epsilon); + } + + if (!pointEqual(remote.loc, local.loc)) { + return (option === 'force_remote') ? target.update({loc: remote.loc}) : undefined; + } + return target; } - function mergeChildren() { + function mergeRemoteChildren(target) { + if (option === 'force_remote') { + return target.update({nodes: remote.nodes}); + } + // todo, support non-destructive merging - return _.isEqual(local.nodes, remote.nodes); + // for now fail on any change.. + if (!_.isEqual(local.nodes, remote.nodes)) { + return; + } + return target; } - function mergeMembers() { + function mergeRemoteMembers(target) { + if (option === 'force_remote') { + return target.update({members: remote.members}); + } + // todo, support non-destructive merging - return _.isEqual(local.members, remote.members); + // for now fail on any change.. + if (!_.isEqual(local.members, remote.members)) { + return; + } + return target; } - function mergeTags() { + function mergeRemoteTags(target) { + if (!target) { return; } + if (option === 'force_remote') { + return target.update({tags: remote.tags}); + } + var keys = _.reject(_.union(_.keys(base.tags), _.keys(remote.tags)), ignoreKey), - tags = _.cloneDeep(target.tags); + tags = _.cloneDeep(target.tags), + changed = false; function ignoreKey(k) { return k.indexOf('tiger:') === 0 || _.contains(iD.data.discarded, k); @@ -33,34 +61,35 @@ iD.actions.MergeRemoteChanges = function(id, localGraph, remoteGraph) { var k = keys[i]; if (remote.tags[k] !== base.tags[k]) { // tag modified remotely.. if (local.tags[k] && local.tags[k] !== remote.tags[k]) { - return false; + return; } else { tags[k] = remote.tags[k]; + changed = true; } } } - target = target.update({tags: tags}); - return true; + return changed ? target.update({tags: tags}) : target; } var action = function(graph) { + var target = iD.Entity(local, {version: remote.version}); - target = iD.Entity(local, {version: remote.version}); - if (option === 'force_remote') { return graph.replace(remote); } - if (option === 'force_local') { return graph.replace(target); } - - // otherwise, safe mode: only permit non-destructive merges.. - var doMerge; - if (target.type === 'node') { - doMerge = (sameLocation() && mergeTags()); - } else if (target.type === 'way') { - doMerge = (mergeChildren() && mergeTags()); - } else if (target.type === 'relation') { - doMerge = (mergeMembers() && mergeTags()); + if (option === 'force_local') { + return graph.replace(target); } - return doMerge ? graph.replace(target) : graph; + if (target.type === 'node') { + target = mergeLocation(target); + } else if (target.type === 'way') { + graph.rebase(remoteGraph.childNodes(remote), [graph], false); + target = mergeRemoteChildren(target); + } else if (target.type === 'relation') { + target = mergeRemoteMembers(target); + } + + target = mergeRemoteTags(target); + return target ? graph.replace(target) : graph; }; action.withOption = function(opt) { diff --git a/test/spec/actions/merge_remote_changes.js b/test/spec/actions/merge_remote_changes.js index 68991d913..e7d554746 100644 --- a/test/spec/actions/merge_remote_changes.js +++ b/test/spec/actions/merge_remote_changes.js @@ -1,210 +1,338 @@ describe("iD.actions.MergeRemoteChanges", function () { var base = iD.Graph([ - iD.Node({id: 'a', loc: [1, 1], version: '1', tags: {foo: 'foo'}}), + iD.Node({id: 'a', loc: [1, 1], version: '1', tags: {foo: 'foo'}}), - iD.Node({id: 'p1', loc: [ 10, 10], version: '1', tags: {foo: 'foo'}}), - iD.Node({id: 'p2', loc: [ 10, -10], version: '1', tags: {foo: 'foo'}}), - iD.Node({id: 'p3', loc: [-10, -10], version: '1', tags: {foo: 'foo'}}), - iD.Node({id: 'p4', loc: [-10, 10], version: '1', tags: {foo: 'foo'}}), - iD.Way({ - id: 'w1', - nodes: ['p1', 'p2', 'p3', 'p4', 'p1'], - version: '1', - tags: {foo: 'foo', area: 'yes'} - }), + iD.Node({id: 'p1', loc: [ 10, 10], version: '1', tags: {foo: 'foo'}}), + iD.Node({id: 'p2', loc: [ 10, -10], version: '1', tags: {foo: 'foo'}}), + iD.Node({id: 'p3', loc: [-10, -10], version: '1', tags: {foo: 'foo'}}), + iD.Node({id: 'p4', loc: [-10, 10], version: '1', tags: {foo: 'foo'}}), + iD.Way({ + id: 'w1', + nodes: ['p1', 'p2', 'p3', 'p4', 'p1'], + version: '1', + tags: {foo: 'foo', area: 'yes'} + }), - iD.Node({id: 'q1', loc: [ 5, 5], version: '1', tags: {foo: 'foo'}}), - iD.Node({id: 'q2', loc: [ 5, -5], version: '1', tags: {foo: 'foo'}}), - iD.Node({id: 'q3', loc: [-5, -5], version: '1', tags: {foo: 'foo'}}), - iD.Node({id: 'q4', loc: [-5, 5], version: '1', tags: {foo: 'foo'}}), - iD.Way({ - id: 'w2', - nodes: ['q1', 'q2', 'q3', 'q4', 'q1'], - version: '1', - tags: {foo: 'foo', area: 'yes'} - }), + iD.Node({id: 'q1', loc: [ 5, 5], version: '1', tags: {foo: 'foo'}}), + iD.Node({id: 'q2', loc: [ 5, -5], version: '1', tags: {foo: 'foo'}}), + iD.Node({id: 'q3', loc: [-5, -5], version: '1', tags: {foo: 'foo'}}), + iD.Node({id: 'q4', loc: [-5, 5], version: '1', tags: {foo: 'foo'}}), + iD.Way({ + id: 'w2', + nodes: ['q1', 'q2', 'q3', 'q4', 'q1'], + version: '1', + tags: {foo: 'foo', area: 'yes'} + }), - iD.Relation({ - id: 'r', - members: [{id: 'w1', role: 'outer'}, {id: 'w2', role: 'inner'}], - version: '1', - tags: {type: 'multipolygon', foo: 'foo'} - }), + iD.Relation({ + id: 'r', + members: [{id: 'w1', role: 'outer'}, {id: 'w2', role: 'inner'}], + version: '1', + tags: {type: 'multipolygon', foo: 'foo'} + }) + ]), - ]); + // some new objects not in the graph yet.. + r1 = iD.Node({id: 'r1', loc: [ 12, 12], version: '1', tags: {foo: 'foo_new'}}), + r2 = iD.Node({id: 'r2', loc: [ 12, -12], version: '1', tags: {foo: 'foo_new'}}), + r3 = iD.Node({id: 'r3', loc: [-12, -12], version: '1', tags: {foo: 'foo_new'}}), + r4 = iD.Node({id: 'r4', loc: [-12, 12], version: '1', tags: {foo: 'foo_new'}}), + w3 = iD.Way({ + id: 'w3', + nodes: ['r1', 'r2', 'r3', 'r4', 'r1'], + version: '1', + tags: {foo: 'foo_new', area: 'yes'} + }), + + s1 = iD.Node({id: 's1', loc: [ 6, 6], version: '1', tags: {foo: 'foo_new'}}), + s2 = iD.Node({id: 's2', loc: [ 6, -6], version: '1', tags: {foo: 'foo_new'}}), + s3 = iD.Node({id: 's3', loc: [-6, -6], version: '1', tags: {foo: 'foo_new'}}), + s4 = iD.Node({id: 's4', loc: [-6, 6], version: '1', tags: {foo: 'foo_new'}}), + w4 = iD.Way({ + id: 'w4', + nodes: ['s1', 's2', 's3', 's4', 's1'], + version: '1', + tags: {foo: 'foo_new', area: 'yes'} + }); + + function makeGraph(entities) { + return _.reduce(entities, function(graph, entity) { + return graph.replace(entity); + }, iD.Graph(base)); + } describe("non-destuctive merging", function () { - it("doesn't merge nodes if location is different", function () { - var local = iD.Node({id: 'a', loc: [1, 1], version: '1', v: 2, tags: {foo: 'foo_local'}}), - remote = iD.Node({id: 'a', loc: [3, 3], version: '2', tags: {foo: 'foo', bar: 'bar_remote'}}), - graph = iD.Graph(base).replace(local), - altGraph = iD.Graph(base).replace(remote), - action = iD.actions.MergeRemoteChanges('a', graph, altGraph); + describe("nodes", function () { + it("doesn't merge nodes if location is different", function () { + var localTags = {foo: 'foo_local'}, // changed tag foo + remoteTags = {foo: 'foo', bar: 'bar_remote'}, // didn't change tag foo, added tag bar + localLoc = [1, 1], // didn't move node + remoteLoc = [3, 3], // moved node + local = iD.Node({id: 'a', loc: localLoc, version: '1', v: 2, tags: localTags }), + remote = iD.Node({id: 'a', loc: remoteLoc, version: '2', tags: remoteTags}), + graph = makeGraph([local]), + altGraph = makeGraph([remote]), + action = iD.actions.MergeRemoteChanges('a', graph, altGraph); - graph = action(graph); + graph = action(graph); - expect(graph.entity('a')).to.eql(local); + expect(graph.entity('a')).to.eql(local); + }); + + it("doesn't merge nodes if changed tags conflict", function () { + var localTags = {foo: 'foo_local'}, // changed tag foo + remoteTags = {foo: 'foo_remote', bar: 'bar_remote'}, // changed tag foo, added tag bar + localLoc = [1, 1], // didn't move node + remoteLoc = [1, 1], // didn't move node + local = iD.Node({id: 'a', loc: localLoc, version: '1', v: 2, tags: localTags }), + remote = iD.Node({id: 'a', loc: remoteLoc, version: '2', tags: remoteTags}), + graph = makeGraph([local]), + altGraph = makeGraph([remote]), + action = iD.actions.MergeRemoteChanges('a', graph, altGraph); + + graph = action(graph); + + expect(graph.entity('a')).to.eql(local); + }); + + it("merges nodes if location is same and changed tags don't conflict", function () { + var localTags = {foo: 'foo_local'}, // changed tag foo + remoteTags = {foo: 'foo', bar: 'bar_remote'}, // didn't change tag foo, added tag bar + localLoc = [1, 1], // didn't move node + remoteLoc = [1, 1], // didn't move node + local = iD.Node({id: 'a', loc: localLoc, version: '1', v: 2, tags: localTags }), + remote = iD.Node({id: 'a', loc: remoteLoc, version: '2', tags: remoteTags}), + graph = makeGraph([local]), + altGraph = makeGraph([remote]), + action = iD.actions.MergeRemoteChanges('a', graph, altGraph); + + graph = action(graph); + + expect(graph.entity('a').version).to.eql('2'); + expect(graph.entity('a').tags).to.eql({foo: 'foo_local', bar: 'bar_remote'}); + }); }); - it("doesn't merge nodes if changed tags conflict", function () { - var local = iD.Node({id: 'a', loc: [1, 1], version: '1', v: 2, tags: {foo: 'foo_local'}}), - remote = iD.Node({id: 'a', loc: [1, 1], version: '2', tags: {foo: 'foo_remote', bar: 'bar_remote'}}), - graph = iD.Graph(base).replace(local), - altGraph = iD.Graph(base).replace(remote), - action = iD.actions.MergeRemoteChanges('a', graph, altGraph); + describe("ways", function () { + it("doesn't merge ways if changed tags conflict", function () { + var localNodes = ['p1', 'p2', 'p3', 'p4', 'p1'], // didn't change nodes + remoteNodes = ['p1', 'p2', 'p3', 'p4', 'p1'], // didn't change nodes + localTags = {foo: 'foo_local', area: 'yes'}, // changed tag foo + remoteTags = {foo: 'foo_remote', bar: 'bar_remote', area: 'yes'}, // changed tag foo, added tag bar + local = iD.Way({id: 'w1', nodes: localNodes, version: '1', v: 2, tags: localTags}), + remote = iD.Way({id: 'w1', nodes: remoteNodes, version: '2', tags: remoteTags}), + graph = makeGraph([local]), + altGraph = makeGraph([remote]), + action = iD.actions.MergeRemoteChanges('w1', graph, altGraph); - graph = action(graph); + graph = action(graph); + + expect(graph.entity('w1')).to.eql(local); + }); + + it("merges ways if nodelist is same and tags don't conflict", function () { + var localNodes = ['p1', 'p2', 'p3', 'p4', 'p1'], // didn't change nodes + remoteNodes = ['p1', 'p2', 'p3', 'p4', 'p1'], // didn't change nodes + localTags = {foo: 'foo_local', area: 'yes'}, // changed tag foo + remoteTags = {foo: 'foo', bar: 'bar_remote', area: 'yes'}, // didn't change tag foo, added tag bar + local = iD.Way({id: 'w1', nodes: localNodes, version: '1', v: 2, tags: localTags}), + remote = iD.Way({id: 'w1', nodes: remoteNodes, version: '2', tags: remoteTags}), + graph = makeGraph([local]), + altGraph = makeGraph([remote]), + action = iD.actions.MergeRemoteChanges('w1', graph, altGraph); + + graph = action(graph); + + expect(graph.entity('w1').version).to.eql('2'); + expect(graph.entity('w1').tags).to.eql({foo: 'foo_local', bar: 'bar_remote', area: 'yes'}); + }); + + it("doesn't merge ways if nodelist reordered", function () { + var localNodes = ['p1', 'p2', 'p3', 'p4', 'p1'], // didn't change nodes + remoteNodes = ['p1', 'p3', 'p4', 'p2', 'p1'], // reordered nodes + localTags = {foo: 'foo_local', area: 'yes'}, // changed tag foo + remoteTags = {foo: 'foo', bar: 'bar_remote', area: 'yes'}, // didn't change tag foo, added tag bar + local = iD.Way({id: 'w1', nodes: localNodes, version: '1', v: 2, tags: localTags}), + remote = iD.Way({id: 'w1', nodes: remoteNodes, version: '2', tags: remoteTags}), + graph = makeGraph([local]), + altGraph = makeGraph([remote]), + action = iD.actions.MergeRemoteChanges('w1', graph, altGraph); + + graph = action(graph); + + expect(graph.entity('w1')).to.eql(local); + }); + + it("merges ways if nodelist order preserved"); - expect(graph.entity('a')).to.eql(local); }); - it("does merge nodes if location is same and changed tags don't conflict", function () { - var local = iD.Node({id: 'a', loc: [1, 1], version: '1', v: 2, tags: {foo: 'foo_local'}}), - remote = iD.Node({id: 'a', loc: [1, 1], version: '2', tags: {foo: 'foo', bar: 'bar_remote'}}), - graph = iD.Graph(base).replace(local), - altGraph = iD.Graph(base).replace(remote), - action = iD.actions.MergeRemoteChanges('a', graph, altGraph); + describe("relations", function () { + it("doesn't merge relations if members have changed", function () { + var localMembers = [{id: 'w1', role: 'outer'}, {id: 'w2', role: 'inner'}], // didn't change members + remoteMembers = [{id: 'w1', role: 'outer'}, {id: 'w4', role: 'inner'}], // changed inner to w4 + localRelTags = {type: 'multipolygon', foo: 'foo_local'}, // changed tag foo + remoteRelTags = {type: 'multipolygon', foo: 'foo', bar: 'bar_remote'}, // didn't change tag foo, added tag bar + local = iD.Relation({id: 'r', members: localMembers, version: '1', v: 2, tags: localRelTags}), + remote = iD.Relation({id: 'r', members: remoteMembers, version: '2', tags: remoteRelTags}), + graph = makeGraph([local]), + altGraph = makeGraph([s1, s2, s3, s4, w4]); + action = iD.actions.MergeRemoteChanges('r', graph, altGraph); - graph = action(graph); + graph = action(graph); - expect(graph.entity('a').version).to.eql('2'); - expect(graph.entity('a').tags).to.eql({foo: 'foo_local', bar: 'bar_remote'}); + expect(graph.entity('r')).to.eql(local); + }); + + it("doesn't merge relations if changed tags conflict", function () { + var relMembers = [{id: 'w1', role: 'outer'}, {id: 'w2', role: 'inner'}], // didn't change members + localRelTags = {type: 'multipolygon', foo: 'foo_local'}, // changed tag foo + remoteRelTags = {type: 'multipolygon', foo: 'foo_remote', bar: 'bar_remote'}, // changed tag foo, added tag bar + local = iD.Relation({id: 'r', members: relMembers, version: '1', v: 2, tags: localRelTags}), + remote = iD.Relation({id: 'r', members: relMembers, version: '2', tags: remoteRelTags}), + graph = makeGraph([local]), + altGraph = makeGraph([remote]); + action = iD.actions.MergeRemoteChanges('r', graph, altGraph); + + graph = action(graph); + + expect(graph.entity('r')).to.eql(local); + }); + + it("merges relations if members are same and changed tags don't conflict", function () { + var relMembers = [{id: 'w1', role: 'outer'}, {id: 'w2', role: 'inner'}], // didn't change members + localRelTags = {type: 'multipolygon', foo: 'foo_local'}, // changed tag foo + remoteRelTags = {type: 'multipolygon', foo: 'foo', bar: 'bar_remote'}, // didn't change tag foo, added tag bar + local = iD.Relation({id: 'r', members: relMembers, version: '1', v: 2, tags: localRelTags}), + remote = iD.Relation({id: 'r', members: relMembers, version: '2', tags: remoteRelTags}), + graph = makeGraph([local]), + altGraph = makeGraph([remote]); + action = iD.actions.MergeRemoteChanges('r', graph, altGraph); + + graph = action(graph); + + expect(graph.entity('r').version).to.eql('2'); + expect(graph.entity('r').tags).to.eql({type: 'multipolygon', foo: 'foo_local', bar: 'bar_remote'}); + }); }); - - // test merging ways - - // test merging relations - }); describe("destuctive merging", function () { - it("merges nodes with 'force_local' option", function () { - var localTags = {foo: 'foo_local'}, // changed tag foo - remoteTags = {foo: 'foo_remote'}, // changed tag foo - local = iD.Node({id: 'a', loc: [2, 2], version: '1', v: 2, tags: localTags}), - remote = iD.Node({id: 'a', loc: [3, 3], version: '2', tags: remoteTags}), - graph = iD.Graph(base).replace(local), - altGraph = iD.Graph(base).replace(remote), - action = iD.actions.MergeRemoteChanges('a', graph, altGraph).withOption('force_local'); + describe("nodes", function () { + it("merges nodes with 'force_local' option", function () { + var localTags = {foo: 'foo_local'}, // changed tag foo + remoteTags = {foo: 'foo_remote'}, // changed tag foo + localLoc = [2, 2], // moved node + remoteLoc = [3, 3], // moved node + local = iD.Node({id: 'a', loc: localLoc, version: '1', v: 2, tags: localTags}), + remote = iD.Node({id: 'a', loc: remoteLoc, version: '2', tags: remoteTags}), + graph = makeGraph([local]), + altGraph = makeGraph([remote]), + action = iD.actions.MergeRemoteChanges('a', graph, altGraph).withOption('force_local'); - graph = action(graph); + graph = action(graph); - expect(graph.entity('a').version).to.eql('2'); - expect(graph.entity('a').loc).to.eql([2, 2]); - expect(graph.entity('a').tags).to.eql(localTags); + expect(graph.entity('a').version).to.eql('2'); + expect(graph.entity('a').loc).to.eql(localLoc); + expect(graph.entity('a').tags).to.eql(localTags); + }); + + it("merges nodes with 'force_remote' option", function () { + var localTags = {foo: 'foo_local'}, // changed tag foo + remoteTags = {foo: 'foo_remote'}, // changed tag foo + localLoc = [2, 2], // moved node + remoteLoc = [3, 3], // moved node + local = iD.Node({id: 'a', loc: localLoc, version: '1', v: 2, tags: localTags}), + remote = iD.Node({id: 'a', loc: remoteLoc, version: '2', tags: remoteTags}), + graph = makeGraph([local]), + altGraph = makeGraph([remote]), + action = iD.actions.MergeRemoteChanges('a', graph, altGraph).withOption('force_remote'); + + graph = action(graph); + + expect(graph.entity('a').version).to.eql('2'); + expect(graph.entity('a').loc).to.eql(remoteLoc); + expect(graph.entity('a').tags).to.eql(remoteTags); + }); }); - it("merges nodes with 'force_remote' option", function () { - var localTags = {foo: 'foo_local'}, // changed tag foo - remoteTags = {foo: 'foo_remote'}, // changed tag foo - local = iD.Node({id: 'a', loc: [2, 2], version: '1', v: 2, tags: localTags}), - remote = iD.Node({id: 'a', loc: [3, 3], version: '2', tags: remoteTags}), - graph = iD.Graph(base).replace(local), - altGraph = iD.Graph(base).replace(remote), - action = iD.actions.MergeRemoteChanges('a', graph, altGraph).withOption('force_remote'); + describe("ways", function () { + it("merges ways with 'force_local' option", function () { + var localNodes = ['p1', 'r1', 'p2', 'p3', 'p4', 'p1'], // inserted node r1 + remoteNodes = ['p1', 'p2', 'p3', 's3', 'p4', 'p1'], // inserted node s3 + localTags = {foo: 'foo_local', area: 'yes'}, // changed tag foo + remoteTags = {foo: 'foo_remote', area: 'yes'}, // changed tag foo + local = iD.Way({id: 'w1', nodes: localNodes, version: '1', v: 2, tags: localTags}), + remote = iD.Way({id: 'w1', nodes: remoteNodes, version: '2', tags: remoteTags}), + graph = makeGraph([local, r1]), + altGraph = makeGraph([remote, s3]), + action = iD.actions.MergeRemoteChanges('w1', graph, altGraph).withOption('force_local'); - graph = action(graph); + graph = action(graph); - expect(graph.entity('a').version).to.eql('2'); - expect(graph.entity('a').loc).to.eql([3, 3]); - expect(graph.entity('a').tags).to.eql(remoteTags); + expect(graph.entity('w1').version).to.eql('2'); + expect(graph.hasEntity('s3')).to.be.undefined; + expect(graph.entity('w1').nodes).to.eql(localNodes); + expect(graph.entity('w1').tags).to.eql(localTags); + }); + + it("merges ways with 'force_remote' option", function () { + var localNodes = ['p1', 'r1', 'p2', 'p3', 'p4', 'p1'], // inserted node r1 + remoteNodes = ['p1', 'p2', 'p3', 's3', 'p4', 'p1'], // inserted node s3 + localTags = {foo: 'foo_local', area: 'yes'}, // changed tag foo + remoteTags = {foo: 'foo_remote', area: 'yes'}, // changed tag foo + local = iD.Way({id: 'w1', nodes: localNodes, version: '1', v: 2, tags: localTags}), + remote = iD.Way({id: 'w1', nodes: remoteNodes, version: '2', tags: remoteTags}), + graph = makeGraph([local, r1]), + altGraph = makeGraph([remote, s3]), + action = iD.actions.MergeRemoteChanges('w1', graph, altGraph).withOption('force_remote'); + + graph = action(graph); + + expect(graph.entity('w1').version).to.eql('2'); + expect(graph.hasEntity('s3')).to.eql(s3); + expect(graph.entity('w1').nodes).to.eql(remoteNodes); + expect(graph.entity('w1').tags).to.eql(remoteTags); + }); }); - it("merges ways with 'force_local' option", function () { - var x = iD.Node({id: 'x', loc: [5, 0], tags: {foo: 'foo_local'}}), - y = iD.Node({id: 'y', loc: [-5, 0], version: '2', tags: {foo: 'foo_remote'}}), - localNodes = ['p1', 'x', 'p2', 'p3', 'p4', 'p1'], // inserted node x - remoteNodes = ['p1', 'p2', 'p3', 'y', 'p4', 'p1'], // inserted node y - localTags = {foo: 'foo_local', area: 'yes'}, // changed tag foo - remoteTags = {foo: 'foo_remote', area: 'yes'}, // changed tag foo - local = iD.Way({id: 'w1', nodes: localNodes, version: '1', v: 2, tags: localTags}), - remote = iD.Way({id: 'w1', nodes: remoteNodes, version: '2', tags: remoteTags}), - graph = iD.Graph(base).replace(x).replace(local), - altGraph = iD.Graph(base).replace(y).replace(remote), - action = iD.actions.MergeRemoteChanges('w1', graph, altGraph).withOption('force_local'); + describe("relations", function () { + it("merges relations with 'force_local' option", function () { + var localMembers = [{id: 'w3', role: 'outer'}, {id: 'w2', role: 'inner'}], // changed outer to w3 + remoteMembers = [{id: 'w4', role: 'outer'}, {id: 'w2', role: 'inner'}], // changed outer to w4 + localRelTags = {type: 'multipolygon', foo: 'foo_local'}, // changed tag foo + remoteRelTags = {type: 'multipolygon', foo: 'foo_remote'}, // changed tag foo + local = iD.Relation({id: 'r', members: localMembers, version: '1', v: 2, tags: localRelTags}), + remote = iD.Relation({id: 'r', members: remoteMembers, version: '2', tags: remoteRelTags}), + graph = makeGraph([local, r1, r2, r3, r4, w3]), + altGraph = makeGraph([remote, s1, s2, s3, s4, w4]), + action = iD.actions.MergeRemoteChanges('r', graph, altGraph).withOption('force_local'); - graph = action(graph); + graph = action(graph); - expect(graph.entity('w1').version).to.eql('2'); - // expect(graph.hasEntity('x')).to.be.true; - // expect(graph.hasEntity('y')).to.be.false; - expect(graph.entity('w1').nodes).to.eql(localNodes); - expect(graph.entity('w1').tags).to.eql(localTags); - }); + expect(graph.entity('r').version).to.eql('2'); + expect(graph.entity('r').members).to.eql(localMembers); + expect(graph.entity('r').tags).to.eql(localRelTags); + }); - it("merges ways with 'force_remote' option", function () { - var x = iD.Node({id: 'x', loc: [5, 0], tags: {foo: 'foo_local'}}), - y = iD.Node({id: 'y', loc: [-5, 0], version: '2', tags: {foo: 'foo_remote'}}), - localNodes = ['p1', 'x', 'p2', 'p3', 'p4', 'p1'], // inserted node x - remoteNodes = ['p1', 'p2', 'p3', 'y', 'p4', 'p1'], // inserted node y - localTags = {foo: 'foo_local', area: 'yes'}, // changed tag foo - remoteTags = {foo: 'foo_remote', area: 'yes'}, // changed tag foo - local = iD.Way({id: 'w1', nodes: localNodes, version: '1', v: 2, tags: localTags}), - remote = iD.Way({id: 'w1', nodes: remoteNodes, version: '2', tags: remoteTags}), - graph = iD.Graph(base).replace(x).replace(local), - altGraph = iD.Graph(base).replace(y).replace(remote), - action = iD.actions.MergeRemoteChanges('w1', graph, altGraph).withOption('force_remote'); + it("merges relations with 'force_remote' option", function () { + var localMembers = [{id: 'w3', role: 'outer'}, {id: 'w2', role: 'inner'}], // changed outer to w3 + remoteMembers = [{id: 'w4', role: 'outer'}, {id: 'w2', role: 'inner'}], // changed outer to w4 + localRelTags = {type: 'multipolygon', foo: 'foo_local'}, // changed tag foo + remoteRelTags = {type: 'multipolygon', foo: 'foo_remote'}, // changed tag foo + local = iD.Relation({id: 'r', members: localMembers, version: '1', v: 2, tags: localRelTags}), + remote = iD.Relation({id: 'r', members: remoteMembers, version: '2', tags: remoteRelTags}), + graph = makeGraph([local, r1, r2, r3, r4, w3]), + altGraph = makeGraph([remote, s1, s2, s3, s4, w4]), + action = iD.actions.MergeRemoteChanges('r', graph, altGraph).withOption('force_remote'); - graph = action(graph); + graph = action(graph); - expect(graph.entity('w1').version).to.eql('2'); - // expect(graph.hasEntity('x')).to.be.true; - // expect(graph.hasEntity('y')).to.be.true; - expect(graph.entity('w1').nodes).to.eql(remoteNodes); - expect(graph.entity('w1').tags).to.eql(remoteTags); - }); - - it("merges relations with 'force_local' option", function () { - var localNodes = ['p2', 'p3', 'p4', 'p1', 'p2'], // changed order - remoteNodes = ['p1', 'p4', 'p3', 'p2', 'p1'], // reversed order - localWayTags = {foo: 'foo_local'}, // changed tag foo - remoteWayTags = {foo: 'foo_remote'}, // changed tag foo - x = iD.Way({id: 'x', nodes: localNodes, tags: localWayTags}), - y = iD.Way({id: 'y', nodes: remoteNodes, version: '2', tags: remoteWayTags}), - localMembers = [{id: 'x', role: 'outer'}, {id: 'w2', role: 'inner'}], // changed outer to x - remoteMembers = [{id: 'y', role: 'outer'}, {id: 'w2', role: 'inner'}], // changed outer to y - localRelTags = {type: 'multipolygon', foo: 'foo_local'}, // changed tag foo - remoteRelTags = {type: 'multipolygon', foo: 'foo_remote'}, // changed tag foo - local = iD.Relation({id: 'r', members: localMembers, version: '1', v: 2, tags: localRelTags}), - remote = iD.Relation({id: 'r', members: remoteMembers, version: '2', tags: remoteRelTags}), - graph = iD.Graph(base).replace(x).replace(local), - altGraph = iD.Graph(base).replace(y).replace(remote), - action = iD.actions.MergeRemoteChanges('r', graph, altGraph).withOption('force_local'); - - graph = action(graph); - - expect(graph.entity('r').version).to.eql('2'); - // expect(graph.hasEntity('x')).to.be.true; - // expect(graph.hasEntity('y')).to.be.false; - expect(graph.entity('r').members).to.eql(localMembers); - expect(graph.entity('r').tags).to.eql(localRelTags); - }); - - it("merges relations with 'force_remote' option", function () { - var localNodes = ['p2', 'p3', 'p4', 'p1', 'p2'], // changed order - remoteNodes = ['p1', 'p4', 'p3', 'p2', 'p1'], // reversed - localWayTags = {foo: 'foo_local'}, // changed tag foo - remoteWayTags = {foo: 'foo_remote'}, // changed tag foo - x = iD.Way({id: 'x', nodes: localNodes, tags: localWayTags}), - y = iD.Way({id: 'y', nodes: remoteNodes, version: '2', tags: remoteWayTags}), - localMembers = [{id: 'x', role: 'outer'}, {id: 'w2', role: 'inner'}], // changed outer to x - remoteMembers = [{id: 'y', role: 'outer'}, {id: 'w2', role: 'inner'}], // changed outer to y - localRelTags = {type: 'multipolygon', foo: 'foo_local'}, // changed tag foo - remoteRelTags = {type: 'multipolygon', foo: 'foo_remote'}, // changed tag foo - local = iD.Relation({id: 'r', members: localMembers, version: '1', v: 2, tags: localRelTags}), - remote = iD.Relation({id: 'r', members: remoteMembers, version: '2', tags: remoteRelTags}), - graph = iD.Graph(base).replace(x).replace(local), - altGraph = iD.Graph(base).replace(y).replace(remote), - action = iD.actions.MergeRemoteChanges('r', graph, altGraph).withOption('force_remote'); - - graph = action(graph); - - expect(graph.entity('r').version).to.eql('2'); - // expect(graph.hasEntity('x')).to.be.true; - // expect(graph.hasEntity('y')).to.be.true; - expect(graph.entity('r').members).to.eql(remoteMembers); - expect(graph.entity('r').tags).to.eql(remoteRelTags); + expect(graph.entity('r').version).to.eql('2'); + expect(graph.entity('r').members).to.eql(remoteMembers); + expect(graph.entity('r').tags).to.eql(remoteRelTags); + }); }); });