diff --git a/modules/core/context.js b/modules/core/context.js index 2efa2c4f1..9e730711a 100644 --- a/modules/core/context.js +++ b/modules/core/context.js @@ -488,6 +488,8 @@ export function coreContext() { context.graph = history.graph; context.changes = history.changes; context.intersects = history.intersects; + context.pauseChangeDispatch = history.pauseChangeDispatch; + context.resumeChangeDispatch = history.resumeChangeDispatch; validator = coreValidator(context); diff --git a/modules/core/history.js b/modules/core/history.js index fb7f9fa8c..ea4c7cdac 100644 --- a/modules/core/history.js +++ b/modules/core/history.js @@ -248,14 +248,18 @@ export function coreHistory(context) { pauseChangeDispatch: function() { - _pausedGraph = _stack[_index].graph; + if (!_pausedGraph) { + _pausedGraph = _stack[_index].graph; + } }, resumeChangeDispatch: function() { - var previous = _pausedGraph; - _pausedGraph = null; - return change(previous, true); + if (_pausedGraph) { + var previous = _pausedGraph; + _pausedGraph = null; + return change(previous, true); + } }, diff --git a/test/spec/core/history.js b/test/spec/core/history.js index c9872a66f..4fbd67ec3 100644 --- a/test/spec/core/history.js +++ b/test/spec/core/history.js @@ -1,6 +1,11 @@ -describe('iD.History', function () { - var context, history, spy, - action = function() { return iD.coreGraph(); }; +describe('iD.coreHistory', function () { + var context, history, spy; + var actionNoop = function(g) { return g; }; + var actionAddNode = function (nodeID) { + return function(g) { + return g.replace(iD.osmNode({ id: nodeID })); + }; + }; beforeEach(function () { context = iD.coreContext(); @@ -33,7 +38,7 @@ describe('iD.History', function () { describe('#perform', function () { it('returns a difference', function () { - expect(history.perform(action).changes()).to.eql({}); + expect(history.perform(actionNoop).changes()).to.eql({}); }); it('updates the graph', function () { @@ -43,13 +48,13 @@ describe('iD.History', function () { }); it('pushes an undo annotation', function () { - history.perform(action, 'annotation'); + history.perform(actionNoop, 'annotation'); expect(history.undoAnnotation()).to.equal('annotation'); }); it('emits a change event', function () { history.on('change', spy); - var difference = history.perform(action); + var difference = history.perform(actionNoop); expect(spy).to.have.been.calledWith(difference); expect(spy.callCount).to.eql(1); }); @@ -77,7 +82,7 @@ describe('iD.History', function () { describe('#replace', function () { it('returns a difference', function () { - expect(history.replace(action).changes()).to.eql({}); + expect(history.replace(actionNoop).changes()).to.eql({}); }); it('updates the graph', function () { @@ -87,14 +92,14 @@ describe('iD.History', function () { }); it('replaces the undo annotation', function () { - history.perform(action, 'annotation1'); - history.replace(action, 'annotation2'); + history.perform(actionNoop, 'annotation1'); + history.replace(actionNoop, 'annotation2'); expect(history.undoAnnotation()).to.equal('annotation2'); }); it('emits a change event', function () { history.on('change', spy); - var difference = history.replace(action); + var difference = history.replace(actionNoop); expect(spy).to.have.been.calledWith(difference); }); @@ -110,49 +115,49 @@ describe('iD.History', function () { describe('#pop', function () { it('returns a difference', function () { - history.perform(action, 'annotation'); + history.perform(actionNoop, 'annotation'); expect(history.pop().changes()).to.eql({}); }); it('updates the graph', function () { - history.perform(action, 'annotation'); + history.perform(actionNoop, 'annotation'); history.pop(); expect(history.undoAnnotation()).to.be.undefined; }); it('does not push the redo stack', function () { - history.perform(action, 'annotation'); + history.perform(actionNoop, 'annotation'); history.pop(); expect(history.redoAnnotation()).to.be.undefined; }); it('emits a change event', function () { - history.perform(action); + history.perform(actionNoop); history.on('change', spy); var difference = history.pop(); expect(spy).to.have.been.calledWith(difference); }); it('pops n times', function () { - history.perform(action, 'annotation1'); - history.perform(action, 'annotation2'); - history.perform(action, 'annotation3'); + history.perform(actionNoop, 'annotation1'); + history.perform(actionNoop, 'annotation2'); + history.perform(actionNoop, 'annotation3'); history.pop(2); expect(history.undoAnnotation()).to.equal('annotation1'); }); it('pops 0 times', function () { - history.perform(action, 'annotation1'); - history.perform(action, 'annotation2'); - history.perform(action, 'annotation3'); + history.perform(actionNoop, 'annotation1'); + history.perform(actionNoop, 'annotation2'); + history.perform(actionNoop, 'annotation3'); history.pop(0); expect(history.undoAnnotation()).to.equal('annotation3'); }); it('pops 1 time if argument is invalid', function () { - history.perform(action, 'annotation1'); - history.perform(action, 'annotation2'); - history.perform(action, 'annotation3'); + history.perform(actionNoop, 'annotation1'); + history.perform(actionNoop, 'annotation2'); + history.perform(actionNoop, 'annotation3'); history.pop('foo'); expect(history.undoAnnotation()).to.equal('annotation2'); history.pop(-1); @@ -162,40 +167,40 @@ describe('iD.History', function () { describe('#overwrite', function () { it('returns a difference', function () { - history.perform(action, 'annotation'); - expect(history.overwrite(action).changes()).to.eql({}); + history.perform(actionNoop, 'annotation'); + expect(history.overwrite(actionNoop).changes()).to.eql({}); }); it('updates the graph', function () { - history.perform(action, 'annotation'); + history.perform(actionNoop, 'annotation'); var node = iD.osmNode(); history.overwrite(function (graph) { return graph.replace(node); }); expect(history.graph().entity(node.id)).to.equal(node); }); it('replaces the undo annotation', function () { - history.perform(action, 'annotation1'); - history.overwrite(action, 'annotation2'); + history.perform(actionNoop, 'annotation1'); + history.overwrite(actionNoop, 'annotation2'); expect(history.undoAnnotation()).to.equal('annotation2'); }); it('does not push the redo stack', function () { - history.perform(action, 'annotation'); - history.overwrite(action, 'annotation2'); + history.perform(actionNoop, 'annotation'); + history.overwrite(actionNoop, 'annotation2'); expect(history.redoAnnotation()).to.be.undefined; }); it('emits a change event', function () { - history.perform(action, 'annotation'); + history.perform(actionNoop, 'annotation'); history.on('change', spy); - var difference = history.overwrite(action, 'annotation2'); + var difference = history.overwrite(actionNoop, 'annotation2'); expect(spy).to.have.been.calledWith(difference); }); it('performs multiple actions', function () { var action1 = sinon.stub().returns(iD.coreGraph()); var action2 = sinon.stub().returns(iD.coreGraph()); - history.perform(action, 'annotation'); + history.perform(actionNoop, 'annotation'); history.overwrite(action1, action2, 'annotation2'); expect(action1).to.have.been.called; expect(action2).to.have.been.called; @@ -209,26 +214,26 @@ describe('iD.History', function () { }); it('pops the undo stack', function () { - history.perform(action, 'annotation'); + history.perform(actionNoop, 'annotation'); history.undo(); expect(history.undoAnnotation()).to.be.undefined; }); it('pushes the redo stack', function () { - history.perform(action, 'annotation'); + history.perform(actionNoop, 'annotation'); history.undo(); expect(history.redoAnnotation()).to.equal('annotation'); }); it('emits an undone event', function () { - history.perform(action); + history.perform(actionNoop); history.on('undone', spy); history.undo(); expect(spy).to.have.been.called; }); it('emits a change event', function () { - history.perform(action); + history.perform(actionNoop); history.on('change', spy); var difference = history.undo(); expect(spy).to.have.been.calledWith(difference); @@ -241,7 +246,7 @@ describe('iD.History', function () { }); it('does redo into an annotated state', function () { - history.perform(action, 'annotation'); + history.perform(actionNoop, 'annotation'); history.on('redone', spy); history.undo(); history.redo(); @@ -250,7 +255,7 @@ describe('iD.History', function () { }); it('does not redo into a non-annotated state', function () { - history.perform(action); + history.perform(actionNoop); history.on('redone', spy); history.undo(); history.redo(); @@ -258,7 +263,7 @@ describe('iD.History', function () { }); it('emits a change event', function () { - history.perform(action); + history.perform(actionNoop); history.undo(); history.on('change', spy); var difference = history.redo(); @@ -268,16 +273,16 @@ describe('iD.History', function () { describe('#pauseChangeDispatch / #resumeChangeDispatch', function() { it('prevents change events from getting dispatched', function() { - history.perform(action, 'base'); + history.perform(actionNoop, 'base'); history.on('change', spy); history.pauseChangeDispatch(); - history.perform(action, 'perform'); + history.perform(actionNoop, 'perform'); expect(spy).to.have.been.notCalled; - history.replace(action, 'replace'); + history.replace(actionNoop, 'replace'); expect(spy).to.have.been.notCalled; - history.overwrite(action, 'replace'); + history.overwrite(actionNoop, 'replace'); expect(spy).to.have.been.notCalled; history.undo(); expect(spy).to.have.been.notCalled; @@ -286,8 +291,31 @@ describe('iD.History', function () { history.pop(); expect(spy).to.have.been.notCalled; - var difference = history.resumeChangeDispatch(); - expect(spy).to.have.been.calledOnceWith(difference); + var diff = history.resumeChangeDispatch(); + expect(spy).to.have.been.calledOnceWith(diff); + }); + + it('does nothing if resume called before pause', function() { + history.perform(actionNoop, 'base'); + history.on('change', spy); + + history.resumeChangeDispatch(); + expect(spy).to.have.been.notCalled; + }); + + it('uses earliest difference if pause called multiple times', function() { + history.perform(actionNoop, 'base'); + history.on('change', spy); + + history.pauseChangeDispatch(); + history.perform(actionAddNode('a'), 'perform'); + + history.pauseChangeDispatch(); + history.perform(actionAddNode('b'), 'perform'); + + var diff = history.resumeChangeDispatch(); + expect(spy).to.have.been.calledOnceWith(diff); + expect(diff.changes()).to.include.keys(['a', 'b']); }); }); @@ -328,8 +356,8 @@ describe('iD.History', function () { describe('#reset', function () { it('clears the version stack', function () { - history.perform(action, 'annotation'); - history.perform(action, 'annotation'); + history.perform(actionNoop, 'annotation'); + history.perform(actionNoop, 'annotation'); history.undo(); history.reset(); expect(history.undoAnnotation()).to.be.undefined; @@ -345,16 +373,16 @@ describe('iD.History', function () { describe('#checkpoint', function () { it('saves and resets to checkpoints', function () { - history.perform(action, 'annotation1'); - history.perform(action, 'annotation2'); - history.perform(action, 'annotation3'); + history.perform(actionNoop, 'annotation1'); + history.perform(actionNoop, 'annotation2'); + history.perform(actionNoop, 'annotation3'); history.checkpoint('check1'); - history.perform(action, 'annotation4'); - history.perform(action, 'annotation5'); + history.perform(actionNoop, 'annotation4'); + history.perform(actionNoop, 'annotation5'); history.checkpoint('check2'); - history.perform(action, 'annotation6'); - history.perform(action, 'annotation7'); - history.perform(action, 'annotation8'); + history.perform(actionNoop, 'annotation6'); + history.perform(actionNoop, 'annotation7'); + history.perform(actionNoop, 'annotation8'); history.reset('check1'); expect(history.undoAnnotation()).to.equal('annotation3'); @@ -375,8 +403,7 @@ describe('iD.History', function () { describe('#toJSON', function() { it('doesn\'t generate unsaveable changes', function() { - var node_1 = iD.osmNode({id: 'n-1'}); - history.perform(iD.actionAddEntity(node_1)); + history.perform(actionAddNode('n-1')); history.perform(iD.actionDeleteNode('n-1')); expect(history.toJSON()).to.be.not.ok; });