Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ns.Update] Перезапрос моделей в случае их невалидности #464 #466

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}