diff --git a/js/id/core/history.js b/js/id/core/history.js index e4f76aae8..f71365d37 100644 --- a/js/id/core/history.js +++ b/js/id/core/history.js @@ -174,7 +174,9 @@ iD.History = function(context) { toJSON: function() { if (stack.length <= 1) return; - var allEntities = {}; + var allEntities = {}, + baseEntities = {}, + base = stack[0]; var s = stack.map(function(i) { var modified = [], deleted = []; @@ -187,6 +189,10 @@ iD.History = function(context) { } else { deleted.push(id); } + // make sure that the originals of changed or deleted entities get merged + // into the base of the stack after restoring the data from JSON. + if (id in base.graph.entities && !base.graph.entities.hasOwnProperty(id)) + baseEntities[id] = base.graph.entities[id]; }); var x = {}; @@ -200,8 +206,9 @@ iD.History = function(context) { }); return JSON.stringify({ - version: 2, + version: 3, entities: _.values(allEntities), + baseEntities: _.values(baseEntities), stack: s, nextIDs: iD.Entity.id.next, index: index @@ -214,13 +221,22 @@ iD.History = function(context) { iD.Entity.id.next = h.nextIDs; index = h.index; - if (h.version === 2) { + if (h.version === 2 || h.version === 3) { var allEntities = {}; h.entities.forEach(function(entity) { allEntities[iD.Entity.key(entity)] = iD.Entity(entity); }); + if (h.version === 3) { + // this merges originals for changed entities into the base of + // the stack even if the current stack doesn't have them (for + // example when iD has been restarted in a different region) + var baseEntities = h.baseEntities.map(iD.Entity); + stack[0].graph.rebase(baseEntities, _.pluck(stack, 'graph')); + tree.rebase(baseEntities); + } + stack = h.stack.map(function(d) { var entities = {}, entity; @@ -292,8 +308,6 @@ iD.History = function(context) { var json = context.storage(getKey('saved_history')); if (json) history.fromJSON(json); - - context.storage(getKey('saved_history', null)); }, _getKey: getKey diff --git a/test/spec/core/history.js b/test/spec/core/history.js index 0df9fc5d8..064e286ec 100644 --- a/test/spec/core/history.js +++ b/test/spec/core/history.js @@ -229,13 +229,19 @@ describe("iD.History", function () { }); describe("#toJSON", function() { - it("generates v2 JSON", function() { - var node = iD.Node({id: 'n-1'}); - history.merge([iD.Node({id: 'n1'})]); - history.perform(iD.actions.AddEntity(node)); + it("generates v3 JSON", function() { + var node_1 = iD.Node({id: 'n-1'}), + node1 = iD.Node({id: 'n1'}), + node2 = iD.Node({id: 'n2'}), + node3 = iD.Node({id: 'n3'}); + history.merge([node1, node2, node3]); + history.perform(iD.actions.AddEntity(node_1)); // addition + history.perform(iD.actions.ChangeTags('n2', {k: 'v'})); // modification + history.perform(iD.actions.DeleteNode('n3')); // deletion var json = JSON.parse(history.toJSON()); - expect(json.version).to.eql(2); - expect(json.entities).to.eql([node]); + expect(json.version).to.eql(3); + expect(json.entities).to.eql([node_1, node2.update({tags: {k: 'v'}})]); + expect(json.baseEntities).to.eql([node2, node3]); }); }); @@ -350,5 +356,71 @@ describe("iD.History", function () { expect(history.imageryUsed()).to.eql(["Bing"]); expect(iD.Entity.id.next).to.eql({node: -1, way: -2, relation: -3}); }); + + it("restores from v3 JSON (creation)", function() { + var json = { + "version": 3, + "entities": [ + {"loc": [1, 2], "id": "n-1"} + ], + "baseEntities": [], + "stack": [ + {}, + {"modified": ["n-1v0"], "imageryUsed": ["Bing"], "annotation": "Added a point."} + ], + "nextIDs": {"node": -2, "way": -1, "relation": -1}, + "index": 1 + }; + history.fromJSON(JSON.stringify(json)); + expect(history.graph().entity('n-1')).to.eql(iD.Node({id: 'n-1', loc: [1, 2]})); + expect(history.undoAnnotation()).to.eql("Added a point."); + expect(history.imageryUsed()).to.eql(["Bing"]); + expect(iD.Entity.id.next).to.eql({node: -2, way: -1, relation: -1}); + expect(history.difference().created().length).to.eql(1); + }); + + it("restores from v3 JSON (modification)", function() { + var json = { + "version": 3, + "entities": [ + {"loc": [1, 2], "id": "n-1"}, + {"loc": [2, 3], "id": "n-1", "v": 1} + ], + "baseEntities": [{"loc": [1, 2], "id": "n-1"}], + "stack": [ + {}, + {"modified": ["n-1v0"], "imageryUsed": ["Bing"], "annotation": "Added a point."}, + {"modified": ["n-1v1"], "imageryUsed": ["Bing"], "annotation": "Moved a point."} + ], + "nextIDs": {"node": -2, "way": -1, "relation": -1}, + "index": 2 + }; + history.fromJSON(JSON.stringify(json)); + expect(history.graph().entity('n-1')).to.eql(iD.Node({id: 'n-1', loc: [2, 3], v: 1})); + expect(history.undoAnnotation()).to.eql("Moved a point."); + expect(history.imageryUsed()).to.eql(["Bing"]); + expect(iD.Entity.id.next).to.eql({node: -2, way: -1, relation: -1}); + expect(history.difference().modified().length).to.eql(1); + }); + + it("restores from v3 JSON (deletion)", function() { + var json = { + "version": 3, + "entities": [], + "baseEntities": [{"loc": [1, 2], "id": "n1"}], + "stack": [ + {}, + {"deleted": ["n1"], "imageryUsed": ["Bing"], "annotation": "Deleted a point."} + ], + "nextIDs": {"node": -1, "way": -2, "relation": -3}, + "index": 1 + }; + history.fromJSON(JSON.stringify(json)); + expect(history.graph().hasEntity('n1')).to.be.undefined; + expect(history.undoAnnotation()).to.eql("Deleted a point."); + expect(history.imageryUsed()).to.eql(["Bing"]); + expect(iD.Entity.id.next).to.eql({node: -1, way: -2, relation: -3}); + expect(history.difference().deleted().length).to.eql(1); + }); }); });