diff --git a/src/ns.update.js b/src/ns.update.js index 5bb38809..8bfbb430 100644 --- a/src/ns.update.js +++ b/src/ns.update.js @@ -321,6 +321,10 @@ this.view._getUpdateTree(tree); } + if (tree.__hasInvalidModels__) { + this.abort(); + } + this.log('created render tree', tree); this.stopTimer('collectViews'); diff --git a/src/ns.view.js b/src/ns.view.js index c2331038..ff4e8f87 100644 --- a/src/ns.view.js +++ b/src/ns.view.js @@ -876,7 +876,12 @@ */ ns.View.prototype._getUpdateTree = function(tree) { if ( !this.isValid() ) { - tree.views[this.id] = this._getViewTree(); + var viewTree = this._getViewTree(); + if (viewTree.models.__hasInvalidModels__) { + delete viewTree.models.__hasInvalidModels__; + tree.__hasInvalidModels__ = true; + } + tree.views[this.id] = viewTree; } else { this._apply(function(view) { view._getUpdateTree(tree); @@ -1047,6 +1052,13 @@ // structure for convenient matching modelsData[id][id] = model.getData(); } else { + // Special case - some of the models was invalidated - if it was during ns.Update - it will be aborted. + // @see #649 + if (model.status === 'invalid') { + // Special property - will be deleted in caller method _getUpdateTree. + modelsData.__hasInvalidModels__ = true; + } + // insuccessful model status modelsData[id].status = 'error'; // structure for convenient matching diff --git a/test/spec/ns.update.edge-cases.js b/test/spec/ns.update.edge-cases.js index d2b81ba1..bef20d48 100644 --- a/test/spec/ns.update.edge-cases.js +++ b/test/spec/ns.update.edge-cases.js @@ -263,4 +263,82 @@ describe('ns.Update. Синтетические случаи', function() { expect(ns.request.models).to.have.callCount(0); }); }); + + xdescribe('Если на момент обновления DOM-а одна из моделей в status=invalid => ns.Update должен абортиться =>', function() { + beforeEach(function() { + ns.Model.define('m1'); + ns.Model.get('m1').setData({}); + + ns.View.define('app'); + ns.View.define('v1', { models: [ 'm1' ] }); + + ns.layout.define('app', { + 'app': { + 'v1': {} + } + }); + + this.sinon.server.autoRespond = true; + this.sinon.server.respond(function(xhr) { + xhr.respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + models: [ + { data: true } + ] + }) + ); + }); + + this.view = ns.View.create('app'); + this.layout = ns.layout.page('app'); + + this.firstUpdateDone = this.sinon.spy(); + + return new ns.Update(this.view, this.layout, {}).render() + .then(this.firstUpdateDone); + }); + + it('первый ns.Update успешно выполняется', function() { + expect(this.firstUpdateDone).to.have.callCount(1); + }); + + describe('после запроса моделей кто-то успевает вызвать invalidate у модели m1 =>', function() { + beforeEach(function() { + var origRequestAllModels = ns.Update.prototype._requestAllModels; + ns.Update.prototype._requestAllModels = function() { + var resultPromise = origRequestAllModels.apply(this, arguments); + resultPromise.then(function() { + ns.Model.get('m1').invalidate(); + }); + return resultPromise; + }; + + this.update = new ns.Update(this.view, this.layout, {}); + }); + + it('второй ns.Update не должен завершиться', function(finish) { + this.update.render() + .then(function() { + finish('second ns.Update should not resolve'); + }, function(result) { + try { + expect(result.error).to.be.equal(ns.U.STATUS.EXPIRED); + finish(); + } catch(e) { + finish(e); + } + }); + }) + + // it('', function() { + // expect(this.secondUpdateDone).to.have.callCount(0); + // }); + + // it('у второго ns.Update должен вызваться abort', function() { + // expect(this.update.abort).to.have.callCount(1); + // }); + }); + }); });