Skip to content

Commit

Permalink
[ns.Update] Перезапрос моделей в случае их невалидности #464
Browse files Browse the repository at this point in the history
  • Loading branch information
doochik committed Sep 17, 2014
1 parent 3d54f43 commit af7b006
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 4 deletions.
68 changes: 65 additions & 3 deletions src/ns.update.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@

this.promise = new Vow.Promise();

/**
* Количество перезапросов из-за невалидных моделей.
* @type {number}
* @private
*/
this._restartCount = 1;

options = options || {};

/**
Expand Down Expand Up @@ -85,6 +92,13 @@
*/
ns.Update.prototype._EVENTS_ORDER = ['ns-view-hide', 'ns-view-htmldestroy', 'ns-view-htmlinit', 'ns-view-async', 'ns-view-show', 'ns-view-touch'];

/**
* Лимит на перезапрос моделей.
* @type {number}
* @constant
*/
ns.Update.prototype.RESTART_LIMIT = 2;

/**
* Регистрирует указанное событие, добавляя к нему признаки ns.update
* @private
Expand Down Expand Up @@ -147,7 +161,7 @@
};

if (ns.Update.handleError(error, this)) {
return [].concat(err.invalid, err.valid);
return Vow.resolve([].concat(err.invalid, err.valid));

} else {
return Vow.reject(error);
Expand Down Expand Up @@ -201,6 +215,12 @@
this.log('collected incomplete views', views);

var models = views2models(views.sync);
/**
* Массив моделей, которые должны быть валидны перед отрисовкой.
* @private
*/
this._models = models;

this.log('collected needed models', models);
this.switchTimer('collectModels', 'requestSyncModels');

Expand Down Expand Up @@ -328,11 +348,53 @@
* потому что у видов будет уже другое состояние, если что-то поменяется между generateHTML и insertNodes
* @private
*/
ns.Update.prototype._updateDOM = function() {
ns.Update.prototype._startUpdateDOM = function() {
if (this._expired()) {
return this._rejectWithStatus(this.STATUS.EXPIRED);
}

// Проверяем валидность моделей перед стартом.
// Из-за асинхронности модели могут оказаться невалидными.
var models = {
valid: [],
invalid: []
};
var modelHasErrors = false;
for (var i = 0, j = this._models.length; i < j; i++) {
var model = this._models[i];
if (model.isValid()) {
models.valid.push(model);

} else {
models.invalid.push(model);
if (model.status === model.STATUS.ERROR) {
modelHasErrors = true;
break;
}
}
}

// если все хорошо, то переходим к обновлению DOM
if (models.invalid.length === 0) {
return this._updateDOM();
}

// если в моделях есть ошибки или мы превысили лимит перезапусков, то фейлимся
if (modelHasErrors || this._restartCount >= this.RESTART_LIMIT) {
// имитируем ошибку "Невалидные модели"
return this._onRequestModelsError(models)
// если ns.Update.handleError решит, что все ок, то запускаем обновление DOM
.then(this._updateDOM, null, this);

} else {
// если модели невалидные, но в них нет ошибок, то пробуем перезапуститься
this._restartCount++;
return this._requestModels(this._models)
.then(this._startUpdateDOM, null, this);
}
};

ns.Update.prototype._updateDOM = function() {
var html = this._renderUpdateTree();
var node = ns.html2node(html || '');
return this._insertNodes(node);
Expand Down Expand Up @@ -409,7 +471,7 @@
// начинаем цепочку с промиса, чтобы ловить ошибки в том числе и из _requestAllModels
Vow.invoke(this._requestAllModels.bind(this))
.then(saveAsyncPromises, null, this)
.then(this._updateDOM, null, this)
.then(this._startUpdateDOM, null, this)
.then(fulfillWithAsyncPromises, this._reject, this);

return this.promise;
Expand Down
81 changes: 81 additions & 0 deletions test/spec/ns.update.edge-cases.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,85 @@ describe('ns.Update. Синтетические случаи', function() {
expect(ns.request.models).to.have.callCount(0);
});
});

describe('Инвалидация моделей в момент запроса приводит к перезапросу моделей', function() {

beforeEach(function() {
this.sinon.server.autoRespond = true;
this.sinon.server.respond(function(xhr) {
xhr.respond(
200,
{"Content-Type": "application/json"},
JSON.stringify({
models: [
{ data: true }
]
})
);
});


ns.layout.define('page', {
'view1': {
'view2': {}
}
});

ns.Model.define('model1');
ns.Model.define('model2');

ns.View.define('view1', { models: ['model1'] });
ns.View.define('view2', { models: ['model2'] });

ns.Model.get('model1').setData({});

// имитируем, что приходим в _updateDOM с невалидными моделями
var that = this;
this.restartLimit = 1;
var restartCount = 0;
ns.Update.prototype.__stubStartUpdateDOM = ns.Update.prototype._startUpdateDOM;
this.sinon.stub(ns.Update.prototype, '_startUpdateDOM', function() {
if (restartCount < that.restartLimit) {
ns.Model.get('model1').invalidate({});
restartCount++;
}

return this.__stubStartUpdateDOM();
});

this.view = ns.View.create('view1');
this.layout = ns.layout.page('page');
});

afterEach(function() {
delete ns.Update.prototype.__stubStartUpdateDOM;
});

it('должен перезапросить и отрисовать виды', function() {
return new ns.Update(this.view, this.layout).render();
});

it('должен завершить update с ошибкой при превышении лимита', function() {
this.restartLimit = 2;
return new ns.Update(this.view, this.layout)
.render()
.then(function() {
throw new Error('fulfilled');
}, function(err) {
expect(err.error).to.be.equal(ns.U.STATUS.MODELS);
});
});

it('должен отрисовать виды с ошибкой при превышении лимита, если разрешил handleError', function() {
this.restartLimit = 2;
this.sinon.stub(ns.Update, 'handleError').returns(true);

return new ns.Update(this.view, this.layout)
.render()
.then(function() {
expect(this.view.node.outerHTML).to.contain('test ns-view-error-content');
}, null, this);
});

});
});
1 change: 1 addition & 0 deletions test/tests.yate
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ match .* ns-view-async-content {

match .* ns-view-error-content {
"test ns-view-error-content"
apply . ns-view-content
}

// Это шаблон для проверки того, что yate сбилжен для тестов.
Expand Down
6 changes: 5 additions & 1 deletion yate/noscript.yate
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,8 @@ match .* ns-view-async-content {}

// Часть обязательных моделей не удалось получить или для них вернулась ошибка
// @public
match .* ns-view-error-content {}
match .* ns-view-error-content {
// Надо все равно отрисовать дерево,
// чтобы не нарваться на ошибку "Can't find node for <view>"
apply . ns-view-content
}

This comment has been minimized.

Copy link
@chestozo

chestozo Sep 17, 2014

Member

Разве надо ренедерить content? Уверен?

This comment has been minimized.

Copy link
@doochik

doochik Sep 17, 2014

Author Contributor

А блин, да. :(
Надо просто по дереву error-content'ов пройтись

This comment has been minimized.

Copy link
@chestozo

chestozo Sep 17, 2014

Member

А не получится такого:

error-content
|__error-content
|__content

если для какого-то вложенного вида всё хорошо (все модели на месте)?

0 comments on commit af7b006

Please sign in to comment.