diff --git a/data/core.yaml b/data/core.yaml index 3cf88dcb2..95fd8bc3b 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -324,7 +324,6 @@ en: next: 'Next >' keep_local: Keep mine keep_remote: Use theirs - deleted: 'This object has been deleted.' restore: Restore delete: Leave Deleted download_changes: Download your changes. @@ -335,6 +334,7 @@ en: your changes or the other user's changes. merge_remote_changes: conflict: + deleted: 'This object has been deleted.' location: 'This object was moved by both you and {user}.' nodelist: 'Nodes were changed by both you and {user}.' memberlist: 'Relation members were changed by both you and {user}.' diff --git a/dist/locales/en.json b/dist/locales/en.json index 51d847fef..12ed1b56f 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -398,7 +398,6 @@ "next": "Next >", "keep_local": "Keep mine", "keep_remote": "Use theirs", - "deleted": "This object has been deleted.", "restore": "Restore", "delete": "Leave Deleted", "download_changes": "Download your changes.", @@ -408,6 +407,7 @@ }, "merge_remote_changes": { "conflict": { + "deleted": "This object has been deleted.", "location": "This object was moved by both you and {user}.", "nodelist": "Nodes were changed by both you and {user}.", "memberlist": "Relation members were changed by both you and {user}.", diff --git a/js/id/actions/merge_remote_changes.js b/js/id/actions/merge_remote_changes.js index 326070b6d..cb9c7520c 100644 --- a/js/id/actions/merge_remote_changes.js +++ b/js/id/actions/merge_remote_changes.js @@ -155,12 +155,18 @@ iD.actions.MergeRemoteChanges = function(id, localGraph, remoteGraph, formatUser for (var i = 0; i < keys.length; i++) { var k = keys[i]; - if (o[k] !== b[k] && a[k] !== b[k]) { // changed remotely.. - if (o[k] !== a[k]) { // changed locally.. + + if (o[k] !== b[k] && a[k] !== b[k]) { // changed remotely.. + if (o[k] !== a[k]) { // changed locally.. conflicts.push(t('merge_remote_changes.conflict.tags', { tag: k, local: a[k], remote: b[k], user: user(remote.user) })); - } else { - tags[k] = b[k]; // unchanged locally, accept remote tag.. + + } else { // unchanged locally, accept remote change.. + if (b.hasOwnProperty(k)) { + tags[k] = b[k]; + } else { + delete tags[k]; + } changed = true; } } @@ -201,6 +207,7 @@ iD.actions.MergeRemoteChanges = function(id, localGraph, remoteGraph, formatUser return graph.replace(target); } else { + conflicts.push(t('merge_remote_changes.conflict.deleted')); return graph; // do nothing } } diff --git a/js/id/modes/save.js b/js/id/modes/save.js index 2a391b62c..643d32e71 100644 --- a/js/id/modes/save.js +++ b/js/id/modes/save.js @@ -66,7 +66,7 @@ iD.modes.Save = function(context) { conflicts.push({ id: id, name: entityName(local), - details: [ t('save.conflict.deleted') ], + details: [ t('merge_remote_changes.conflict.deleted') ], chosen: 1, choices: [ choice(id, t('save.conflict.restore'), forceLocal), diff --git a/test/spec/actions/merge_remote_changes.js b/test/spec/actions/merge_remote_changes.js index 80fd7ae1b..c5d4c9ce1 100644 --- a/test/spec/actions/merge_remote_changes.js +++ b/test/spec/actions/merge_remote_changes.js @@ -65,9 +65,10 @@ describe("iD.actions.MergeRemoteChanges", function () { locale = { _current: 'en', en: { - "merge_remote_changes": { + 'merge_remote_changes': { "annotation": "Merged remote changes from server.", "conflict": { + "deleted": "This object has been deleted.", "location": "This object was moved by both you and {user}.", "nodelist": "Nodes were changed by both you and {user}.", "memberlist": "Relation members were changed by both you and {user}.", @@ -90,379 +91,380 @@ describe("iD.actions.MergeRemoteChanges", function () { } describe("non-destuctive merging", function () { - 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]), + describe("tags", function() { + it("doesn't merge tags if conflict (local change, remote change)", function () { + var localTags = {foo: 'foo_local'}, // changed foo + remoteTags = {foo: 'foo_remote'}, // changed foo + local = base.entity('a').update({tags: localTags}), + remote = base.entity('a').update({tags: remoteTags, version: '2'}), + localGraph = makeGraph([local]), remoteGraph = makeGraph([remote]), - action = iD.actions.MergeRemoteChanges('a', graph, remoteGraph); + action = iD.actions.MergeRemoteChanges('a', localGraph, remoteGraph), + result = action(localGraph); - graph = action(graph); - - expect(graph.entity('a')).to.eql(local); + expect(result).to.eql(localGraph); }); - it("doesn't merge nodes if changed tags conflict (tag change)", 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]), + it("doesn't merge tags if conflict (local change, remote delete)", function () { + var localTags = {foo: 'foo_local'}, // changed foo + remoteTags = {}, // deleted foo + local = base.entity('a').update({tags: localTags}), + remote = base.entity('a').update({tags: remoteTags, version: '2'}), + localGraph = makeGraph([local]), remoteGraph = makeGraph([remote]), - action = iD.actions.MergeRemoteChanges('a', graph, remoteGraph); + action = iD.actions.MergeRemoteChanges('a', localGraph, remoteGraph), + result = action(localGraph); - graph = action(graph); - - expect(graph.entity('a')).to.eql(local); + expect(result).to.eql(localGraph); }); - it("doesn't merge nodes if changed tags conflict (tag delete)", function () { - var localTags = {}, // deleted tag foo - remoteTags = {foo: 'foo_remote'}, // changed tag foo - 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]), + it("doesn't merge tags if conflict (local delete, remote change)", function () { + var localTags = {}, // deleted foo + remoteTags = {foo: 'foo_remote'}, // changed foo + local = base.entity('a').update({tags: localTags}), + remote = base.entity('a').update({tags: remoteTags, version: '2'}), + localGraph = makeGraph([local]), remoteGraph = makeGraph([remote]), - action = iD.actions.MergeRemoteChanges('a', graph, remoteGraph); + action = iD.actions.MergeRemoteChanges('a', localGraph, remoteGraph), + result = action(localGraph); - graph = action(graph); - - expect(graph.entity('a')).to.eql(local); + expect(result).to.eql(localGraph); }); - it("merges nodes if location is same and changed tags don't conflict (tag change)", 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]), + it("doesn't merge tags if conflict (local add, remote add)", function () { + var localTags = {foo: 'foo', bar: 'bar_local'}, // same foo, added bar + remoteTags = {foo: 'foo', bar: 'bar_remote'}, // same foo, added bar + local = base.entity('a').update({tags: localTags}), + remote = base.entity('a').update({tags: remoteTags, version: '2'}), + localGraph = makeGraph([local]), remoteGraph = makeGraph([remote]), - action = iD.actions.MergeRemoteChanges('a', graph, remoteGraph); + action = iD.actions.MergeRemoteChanges('a', localGraph, remoteGraph), + result = action(localGraph); - 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(result).to.eql(localGraph); }); - it("merges nodes if location is same and changed tags don't conflict (tag delete)", function () { - var localTags = {}, // deleted 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]), + it("merges tags if no conflict (remote delete)", function () { + var localTags = {foo: 'foo', bar: 'bar_local'}, // same foo, added bar + remoteTags = {}, // deleted foo + mergedTags = {bar: 'bar_local'}, + local = base.entity('a').update({tags: localTags}), + remote = base.entity('a').update({tags: remoteTags, version: '2'}), + localGraph = makeGraph([local]), remoteGraph = makeGraph([remote]), - action = iD.actions.MergeRemoteChanges('a', graph, remoteGraph); + action = iD.actions.MergeRemoteChanges('a', localGraph, remoteGraph), + result = action(localGraph); - graph = action(graph); + expect(result.entity('a').version).to.eql('2'); + expect(result.entity('a').tags).to.eql(mergedTags); + }); - expect(graph.entity('a').version).to.eql('2'); - expect(graph.entity('a').tags).to.eql({bar: 'bar_remote'}); + it("merges tags if no conflict (local delete)", function () { + var localTags = {}, // deleted foo + remoteTags = {foo: 'foo', bar: 'bar_remote'}, // same foo, added bar + mergedTags = {bar: 'bar_remote'}, + local = base.entity('a').update({tags: localTags}), + remote = base.entity('a').update({tags: remoteTags, version: '2'}), + localGraph = makeGraph([local]), + remoteGraph = makeGraph([remote]), + action = iD.actions.MergeRemoteChanges('a', localGraph, remoteGraph), + result = action(localGraph); + + expect(result.entity('a').version).to.eql('2'); + expect(result.entity('a').tags).to.eql(mergedTags); }); }); - 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]), + + describe("nodes", function () { + it("doesn't merge nodes if location is different", function () { + var localTags = {foo: 'foo_local'}, // changed foo + remoteTags = {foo: 'foo', bar: 'bar_remote'}, // same foo, added bar + localLoc = [2, 2], // moved node + remoteLoc = [3, 3], // moved node + local = base.entity('a').update({tags: localTags, loc: localLoc}), + remote = base.entity('a').update({tags: remoteTags, loc: remoteLoc, version: '2'}), + localGraph = makeGraph([local]), remoteGraph = makeGraph([remote]), - action = iD.actions.MergeRemoteChanges('w1', graph, remoteGraph); + action = iD.actions.MergeRemoteChanges('a', localGraph, remoteGraph), + result = action(localGraph); - graph = action(graph); - - expect(graph.entity('w1')).to.eql(local); + expect(result).to.eql(localGraph); }); - 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]), + it("merges nodes if location is same", function () { + var localTags = {foo: 'foo_local'}, // changed foo + remoteTags = {foo: 'foo', bar: 'bar_remote'}, // same foo, added bar + mergedTags = {foo: 'foo_local', bar: 'bar_remote'}, + localLoc = [2, 2], // moved node + remoteLoc = [2, 2], // moved node + local = base.entity('a').update({tags: localTags, loc: localLoc}), + remote = base.entity('a').update({tags: remoteTags, loc: remoteLoc, version: '2'}), + localGraph = makeGraph([local]), remoteGraph = makeGraph([remote]), - action = iD.actions.MergeRemoteChanges('w1', graph, remoteGraph); + action = iD.actions.MergeRemoteChanges('a', localGraph, remoteGraph), + result = action(localGraph); - graph = action(graph); + expect(result.entity('a').version).to.eql('2'); + expect(result.entity('a').tags).to.eql(mergedTags); + expect(result.entity('a').loc).to.eql([2, 2]); + }); + }); - expect(graph.entity('w1').version).to.eql('2'); - expect(graph.entity('w1').tags).to.eql({foo: 'foo_local', bar: 'bar_remote', area: 'yes'}); + + describe("ways", function () { + it("merges ways if nodelist is same", function () { + var localTags = {foo: 'foo_local', area: 'yes'}, // changed foo + remoteTags = {foo: 'foo', bar: 'bar_remote', area: 'yes'}, // same foo, added bar + mergedTags = {foo: 'foo_local', bar: 'bar_remote', area: 'yes'}, + local = base.entity('w1').update({tags: localTags}), + remote = base.entity('w1').update({tags: remoteTags, version: '2'}), + localGraph = makeGraph([local]), + remoteGraph = makeGraph([remote]), + action = iD.actions.MergeRemoteChanges('w1', localGraph, remoteGraph), + result = action(localGraph); + + expect(result.entity('w1').version).to.eql('2'); + expect(result.entity('w1').tags).to.eql(mergedTags); }); it("merges ways if nodelist changed only remotely", function () { - var localNodes = ['p1', 'p2', 'p3', 'p4', 'p1'], // didn't change nodes - remoteNodes = ['p1', 'r2', 'r3', 'p4', 'p1'], // changed 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]), + var localTags = {foo: 'foo_local', area: 'yes'}, // changed foo + remoteTags = {foo: 'foo', bar: 'bar_remote', area: 'yes'}, // same foo, added bar + mergedTags = {foo: 'foo_local', bar: 'bar_remote', area: 'yes'}, + localNodes = ['p1', 'p2', 'p3', 'p4', 'p1'], // didn't change nodes + remoteNodes = ['p1', 'r2', 'r3', 'p4', 'p1'], // changed nodes + local = base.entity('w1').update({tags: localTags, nodes: localNodes}), + remote = base.entity('w1').update({tags: remoteTags, nodes: remoteNodes, version: '2'}), + localGraph = makeGraph([local]), remoteGraph = makeGraph([remote, r2, r3]), - action = iD.actions.MergeRemoteChanges('w1', graph, remoteGraph); + action = iD.actions.MergeRemoteChanges('w1', localGraph, remoteGraph), + result = action(localGraph); - 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'}); - expect(graph.entity('w1').nodes).to.eql(remoteNodes); - expect(graph.hasEntity('r2')).to.eql(r2); - expect(graph.hasEntity('r3')).to.eql(r3); + expect(result.entity('w1').version).to.eql('2'); + expect(result.entity('w1').tags).to.eql(mergedTags); + expect(result.entity('w1').nodes).to.eql(remoteNodes); + expect(result.hasEntity('r2')).to.eql(r2); + expect(result.hasEntity('r3')).to.eql(r3); }); it("merges ways if nodelist changed only locally", function () { - var localNodes = ['p1', 'r2', 'r3', 'p4', 'p1'], // changed 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, r2, r3]), + var localTags = {foo: 'foo_local', area: 'yes'}, // changed foo + remoteTags = {foo: 'foo', bar: 'bar_remote', area: 'yes'}, // same foo, added bar + mergedTags = {foo: 'foo_local', bar: 'bar_remote', area: 'yes'}, + localNodes = ['p1', 'r2', 'r3', 'p4', 'p1'], // changed nodes + remoteNodes = ['p1', 'p2', 'p3', 'p4', 'p1'], // didn't change nodes + local = base.entity('w1').update({tags: localTags, nodes: localNodes}), + remote = base.entity('w1').update({tags: remoteTags, nodes: remoteNodes, version: '2'}), + localGraph = makeGraph([local, r2, r3]), remoteGraph = makeGraph([remote]), - action = iD.actions.MergeRemoteChanges('w1', graph, remoteGraph); + action = iD.actions.MergeRemoteChanges('w1', localGraph, remoteGraph), + result = action(localGraph); - 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'}); - expect(graph.entity('w1').nodes).to.eql(localNodes); + expect(result.entity('w1').version).to.eql('2'); + expect(result.entity('w1').tags).to.eql(mergedTags); + expect(result.entity('w1').nodes).to.eql(localNodes); }); it("merges ways if nodelist changes don't overlap", function () { - var localNodes = ['p1', 'r1', 'r2', 'p3', 'p4', 'p1'], // changed p2 -> r1, r2 - remoteNodes = ['p1', 'p2', 'p3', 'r3', 'r4', 'p1'], // changed p4 -> r3, r4 - 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, r1, r2]), + var localTags = {foo: 'foo_local', area: 'yes'}, // changed foo + remoteTags = {foo: 'foo', bar: 'bar_remote', area: 'yes'}, // same foo, added bar + mergedTags = {foo: 'foo_local', bar: 'bar_remote', area: 'yes'}, + localNodes = ['p1', 'r1', 'r2', 'p3', 'p4', 'p1'], // changed p2 -> r1, r2 + remoteNodes = ['p1', 'p2', 'p3', 'r3', 'r4', 'p1'], // changed p4 -> r3, r4 + mergedNodes = ['p1', 'r1', 'r2', 'p3', 'r3', 'r4', 'p1'], + local = base.entity('w1').update({tags: localTags, nodes: localNodes}), + remote = base.entity('w1').update({tags: remoteTags, nodes: remoteNodes, version: '2'}), + localGraph = makeGraph([local, r1, r2]), remoteGraph = makeGraph([remote, r3, r4]), - action = iD.actions.MergeRemoteChanges('w1', graph, remoteGraph); + action = iD.actions.MergeRemoteChanges('w1', localGraph, remoteGraph), + result = action(localGraph); - 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'}); - expect(graph.entity('w1').nodes).to.eql(['p1', 'r1', 'r2', 'p3', 'r3', 'r4', 'p1']); - expect(graph.hasEntity('r3')).to.eql(r3); - expect(graph.hasEntity('r4')).to.eql(r4); + expect(result.entity('w1').version).to.eql('2'); + expect(result.entity('w1').tags).to.eql(mergedTags); + expect(result.entity('w1').nodes).to.eql(mergedNodes); + expect(result.hasEntity('r3')).to.eql(r3); + expect(result.hasEntity('r4')).to.eql(r4); }); it("doesn't merge ways if nodelist changes overlap", function () { - var localNodes = ['p1', 'r1', 'r2', 'p3', 'p4', 'p1'], // changed p2 -> r1, r2 - remoteNodes = ['p1', 'r3', 'r4', 'p3', 'p4', 'p1'], // changed p2 -> r3, r4 - 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, r1, r2]), + var localTags = {foo: 'foo_local', area: 'yes'}, // changed foo + remoteTags = {foo: 'foo', bar: 'bar_remote', area: 'yes'}, // same foo, added bar + localNodes = ['p1', 'r1', 'r2', 'p3', 'p4', 'p1'], // changed p2 -> r1, r2 + remoteNodes = ['p1', 'r3', 'r4', 'p3', 'p4', 'p1'], // changed p2 -> r3, r4 + local = base.entity('w1').update({tags: localTags, nodes: localNodes}), + remote = base.entity('w1').update({tags: remoteTags, nodes: remoteNodes, version: '2'}), + localGraph = makeGraph([local, r1, r2]), remoteGraph = makeGraph([remote, r3, r4]), - action = iD.actions.MergeRemoteChanges('w1', graph, remoteGraph); + action = iD.actions.MergeRemoteChanges('w1', localGraph, remoteGraph), + result = action(localGraph); - graph = action(graph); - - expect(graph.entity('w1')).to.eql(local); + expect(result).to.eql(localGraph); }); - }); + 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 + var localTags = {foo: 'foo_local', type: 'multipolygon'}, // changed foo + remoteTags = {foo: 'foo', bar: 'bar_remote', type: 'multipolygon'}, // same foo, added bar + localMembers = [{id: 'w1', role: 'outer'}, {id: 'w2', role: 'inner'}], // same 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]), - remoteGraph = makeGraph([s1, s2, s3, s4, w4]); - action = iD.actions.MergeRemoteChanges('r', graph, remoteGraph); + local = base.entity('r').update({tags: localTags, members: localMembers}), + remote = base.entity('r').update({tags: remoteTags, members: remoteMembers, version: '2'}), + localGraph = makeGraph([local]), + remoteGraph = makeGraph([remote, s1, s2, s3, s4, w4]), + action = iD.actions.MergeRemoteChanges('r', localGraph, remoteGraph), + result = action(localGraph); - graph = action(graph); - - 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]), - remoteGraph = makeGraph([remote]); - action = iD.actions.MergeRemoteChanges('r', graph, remoteGraph); - - graph = action(graph); - - expect(graph.entity('r')).to.eql(local); + expect(result).to.eql(localGraph); }); 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]), - remoteGraph = makeGraph([remote]); - action = iD.actions.MergeRemoteChanges('r', graph, remoteGraph); + var localTags = {foo: 'foo_local', type: 'multipolygon'}, // changed foo + remoteTags = {foo: 'foo', bar: 'bar_remote', type: 'multipolygon'}, // same foo, added bar + mergedTags = {foo: 'foo_local', bar: 'bar_remote', type: 'multipolygon'}, + localMembers = [{id: 'w1', role: 'outer'}, {id: 'w2', role: 'inner'}], // same members + remoteMembers = [{id: 'w1', role: 'outer'}, {id: 'w2', role: 'inner'}], // same members + local = base.entity('r').update({tags: localTags, members: localMembers}), + remote = base.entity('r').update({tags: remoteTags, members: remoteMembers, version: '2'}), + localGraph = makeGraph([local]), + remoteGraph = makeGraph([remote]), + action = iD.actions.MergeRemoteChanges('r', localGraph, remoteGraph), + result = action(localGraph); - 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'}); + expect(result.entity('r').version).to.eql('2'); + expect(result.entity('r').tags).to.eql(mergedTags); }); }); + describe("#conflicts", function () { it("returns conflict details", function () { - var localLoc = [1, 1], // didn't move node - remoteLoc = [3, 3], // moved node - local = iD.Node({id: 'a', loc: localLoc, version: '1', v: 2}), - remote = iD.Node({id: 'a', loc: remoteLoc, version: '2'}), - graph = makeGraph([local]), + var localTags = {foo: 'foo_local'}, // changed foo + remoteTags = {foo: 'foo', bar: 'bar_remote'}, // same foo, added bar + remoteLoc = [2, 2], // moved node + local = base.entity('a').update({tags: localTags}), + remote = base.entity('a').update({tags: remoteTags, loc: remoteLoc, version: '2'}), + localGraph = makeGraph([local]), remoteGraph = makeGraph([remote]), - action = iD.actions.MergeRemoteChanges('a', graph, remoteGraph); - - graph = action(graph); + action = iD.actions.MergeRemoteChanges('a', localGraph, remoteGraph), + result = action(localGraph); expect(action.conflicts()).not.to.be.empty; }); }); }); + describe("destuctive merging", function () { 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]), + var localTags = {foo: 'foo_local'}, // changed foo + remoteTags = {foo: 'foo_remote'}, // changed foo + localLoc = [2, 2], // moved node + remoteLoc = [3, 3], // moved node + local = base.entity('a').update({tags: localTags, loc: localLoc}), + remote = base.entity('a').update({tags: remoteTags, loc: remoteLoc, version: '2'}), + localGraph = makeGraph([local]), remoteGraph = makeGraph([remote]), - action = iD.actions.MergeRemoteChanges('a', graph, remoteGraph).withOption('force_local'); + action = iD.actions.MergeRemoteChanges('a', localGraph, remoteGraph).withOption('force_local'), + result = action(localGraph); - graph = action(graph); - - expect(graph.entity('a').version).to.eql('2'); - expect(graph.entity('a').loc).to.eql(localLoc); - expect(graph.entity('a').tags).to.eql(localTags); + expect(result.entity('a').version).to.eql('2'); + expect(result.entity('a').tags).to.eql(localTags); + expect(result.entity('a').loc).to.eql(localLoc); }); 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]), + var localTags = {foo: 'foo_local'}, // changed foo + remoteTags = {foo: 'foo_remote'}, // changed foo + localLoc = [2, 2], // moved node + remoteLoc = [3, 3], // moved node + local = base.entity('a').update({tags: localTags, loc: localLoc}), + remote = base.entity('a').update({tags: remoteTags, loc: remoteLoc, version: '2'}), + localGraph = makeGraph([local]), remoteGraph = makeGraph([remote]), - action = iD.actions.MergeRemoteChanges('a', graph, remoteGraph).withOption('force_remote'); + action = iD.actions.MergeRemoteChanges('a', localGraph, remoteGraph).withOption('force_remote'), + result = action(localGraph); - 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); + expect(result.entity('a').version).to.eql('2'); + expect(result.entity('a').tags).to.eql(remoteTags); + expect(result.entity('a').loc).to.eql(remoteLoc); }); }); + 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]), - remoteGraph = makeGraph([remote, s3]), - action = iD.actions.MergeRemoteChanges('w1', graph, remoteGraph).withOption('force_local'); + var localTags = {foo: 'foo_local', area: 'yes'}, // changed foo + remoteTags = {foo: 'foo_remote', area: 'yes'}, // changed foo + localNodes = ['p1', 'r1', 'r2', 'p3', 'p4', 'p1'], // changed p2 -> r1, r2 + remoteNodes = ['p1', 'r3', 'r4', 'p3', 'p4', 'p1'], // changed p2 -> r3, r4 + local = base.entity('w1').update({tags: localTags, nodes: localNodes}), + remote = base.entity('w1').update({tags: remoteTags, nodes: remoteNodes, version: '2'}), + localGraph = makeGraph([local, r1, r2]), + remoteGraph = makeGraph([remote, r3, r4]), + action = iD.actions.MergeRemoteChanges('w1', localGraph, remoteGraph).withOption('force_local'), + result = action(localGraph); - graph = action(graph); - - expect(graph.entity('w1').version).to.eql('2'); - expect(graph.entity('w1').nodes).to.eql(localNodes); - expect(graph.entity('w1').tags).to.eql(localTags); + expect(result.entity('w1').version).to.eql('2'); + expect(result.entity('w1').tags).to.eql(localTags); + expect(result.entity('w1').nodes).to.eql(localNodes); }); 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]), - remoteGraph = makeGraph([remote, s3]), - action = iD.actions.MergeRemoteChanges('w1', graph, remoteGraph).withOption('force_remote'); + var localTags = {foo: 'foo_local', area: 'yes'}, // changed foo + remoteTags = {foo: 'foo_remote', area: 'yes'}, // changed foo + localNodes = ['p1', 'r1', 'r2', 'p3', 'p4', 'p1'], // changed p2 -> r1, r2 + remoteNodes = ['p1', 'r3', 'r4', 'p3', 'p4', 'p1'], // changed p2 -> r3, r4 + local = base.entity('w1').update({tags: localTags, nodes: localNodes}), + remote = base.entity('w1').update({tags: remoteTags, nodes: remoteNodes, version: '2'}), + localGraph = makeGraph([local, r1, r2]), + remoteGraph = makeGraph([remote, r3, r4]), + action = iD.actions.MergeRemoteChanges('w1', localGraph, remoteGraph).withOption('force_remote'), + result = action(localGraph); - 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); + expect(result.entity('w1').version).to.eql('2'); + expect(result.entity('w1').tags).to.eql(remoteTags); + expect(result.entity('w1').nodes).to.eql(remoteNodes); }); }); + 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]), + var localTags = {foo: 'foo_local', type: 'multipolygon'}, // changed foo + remoteTags = {foo: 'foo_remote', type: 'multipolygon'}, // changed foo + localMembers = [{id: 'w3', role: 'outer'}, {id: 'w2', role: 'inner'}], // changed outer to w3 + remoteMembers = [{id: 'w1', role: 'outer'}, {id: 'w4', role: 'inner'}], // changed inner to w4 + local = base.entity('r').update({tags: localTags, members: localMembers}), + remote = base.entity('r').update({tags: remoteTags, members: remoteMembers, version: '2'}), + localGraph = makeGraph([local, r1, r2, r3, r4, w3]), remoteGraph = makeGraph([remote, s1, s2, s3, s4, w4]), - action = iD.actions.MergeRemoteChanges('r', graph, remoteGraph).withOption('force_local'); + action = iD.actions.MergeRemoteChanges('r', localGraph, remoteGraph).withOption('force_local'), + result = action(localGraph); - graph = action(graph); - - expect(graph.entity('r').version).to.eql('2'); - expect(graph.entity('r').members).to.eql(localMembers); - expect(graph.entity('r').tags).to.eql(localRelTags); + expect(result.entity('r').version).to.eql('2'); + expect(result.entity('r').tags).to.eql(localTags); + expect(result.entity('r').members).to.eql(localMembers); }); 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]), + var localTags = {foo: 'foo_local', type: 'multipolygon'}, // changed foo + remoteTags = {foo: 'foo_remote', type: 'multipolygon'}, // changed foo + localMembers = [{id: 'w3', role: 'outer'}, {id: 'w2', role: 'inner'}], // changed outer to w3 + remoteMembers = [{id: 'w1', role: 'outer'}, {id: 'w4', role: 'inner'}], // changed inner to w4 + local = base.entity('r').update({tags: localTags, members: localMembers}), + remote = base.entity('r').update({tags: remoteTags, members: remoteMembers, version: '2'}), + localGraph = makeGraph([local, r1, r2, r3, r4, w3]), remoteGraph = makeGraph([remote, s1, s2, s3, s4, w4]), - action = iD.actions.MergeRemoteChanges('r', graph, remoteGraph).withOption('force_remote'); + action = iD.actions.MergeRemoteChanges('r', localGraph, remoteGraph).withOption('force_remote'), + result = action(localGraph); - graph = action(graph); - - expect(graph.entity('r').version).to.eql('2'); - expect(graph.entity('r').members).to.eql(remoteMembers); - expect(graph.entity('r').tags).to.eql(remoteRelTags); + expect(result.entity('r').version).to.eql('2'); + expect(result.entity('r').tags).to.eql(remoteTags); + expect(result.entity('r').members).to.eql(remoteMembers); }); }); });