Skip to content

Commit

Permalink
Merge pull request #186 from yandex-ui/errors.handling
Browse files Browse the repository at this point in the history
ns.update и ns.view: отображение ошибок
  • Loading branch information
edoroshenko committed Jan 14, 2014
2 parents b16e603 + bd5a6f4 commit c6ba4ef
Show file tree
Hide file tree
Showing 13 changed files with 234 additions and 89 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
dist
node_modules
*.yate.obj
test/tests.yate.js
9 changes: 7 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
NPM_BIN=$(CURDIR)/node_modules/.bin
export NPM_BIN

yate: test/tests.yate.js

test/tests.yate.js: test/tests.yate
node_modules/.bin/yate test/tests.yate > test/tests.yate.js

node_modules: package.json
npm install
touch node_modules

clean-node_modules:
rm -rf node_modules

grunt: node_modules
grunt: node_modules yate
$(NPM_BIN)/grunt

.PHONY: clean-node_modules grunt
.PHONY: clean-node_modules grunt yate
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"noscript.borschik.js"
],
"scripts": {
"prepublish": "mkdir -p dist && borschik -i noscript.borschik.js -o dist/noscript.js -m no && borschik -i noscript.borschik.js -o dist/noscript.min.js -m yes",
"prepublish": "make yate && mkdir -p dist && borschik -i noscript.borschik.js -o dist/noscript.js -m no && borschik -i noscript.borschik.js -o dist/noscript.min.js -m yes",
"test": "grunt && jscs ."
}
}
Expand Down
66 changes: 44 additions & 22 deletions src/ns.update.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* jshint unused: false */

(function() {

/**
Expand Down Expand Up @@ -108,33 +110,35 @@ ns.Update.prototype.start = function(async) {

var syncModelsPromise = ns.request.models(models)
.done(function(models) {
if (that._expired()) {
that.error({
error: that.STATUS.EXPIRED
});
var error = null;
models = models || [];

} else {
//FIXME: we should delete this loop when ns.request will can reject promise
// check that all models is valid
for (var i = 0, j = models.length; i < j; i++) {
if (!models[i].isValid()) {
that.error({
error: that.STATUS.MODELS,
models: models
});
return;
}
}
if (that._expired()) {
error = {
error: that.STATUS.EXPIRED,
models: models
};
} else if (models.some(function(m) { return !m.isValid(); })) {
error = {
error: that.STATUS.MODELS,
models: models
};
}

that._update(async);
// resolve main promise and return promises for async views
that.done({
async: asyncUpdaterPromises
});
// Try handle error if any.
if (error && !ns.Update.handleError(error, that)) {
that.error(error);
return;
}

that._update(async);
// resolve main promise and return promises for async views
that.done({
async: asyncUpdaterPromises
});
})
.fail(function(models) {
//FIXME: ns.request.models can't reject promise this time, we should fix it
// NOTE here we do not even try to handle the error. Or we should do it?
that.error({
error: that.STATUS.MODELS,
models: models
Expand Down Expand Up @@ -363,6 +367,24 @@ ns.Update.prototype.addToQueue = function(newUpdate) {
return true;
};

/**
* Whether this update is a global update (main update) or not.
* @returns Boolean.
*/
ns.Update.prototype.isGlobal = function() {
return this.EXEC_FLAG === ns.U.EXEC.GLOBAL;
};

/**
* Global error handler.
* @param {Object} error Error summary object `{ error: string, models: Array.<ns.Model> }`.
* @param {ns.Update} update Update instance so that we can abort it if we want to.
* @returns Boolean If `true` - update can continue, otherwise update cannot continue.
*/
ns.Update.handleError = function(error, update) {
return false;
};

// ----------------------------------------------------------------------------------------------------------------- //

function views2models(views) {
Expand Down
15 changes: 15 additions & 0 deletions src/ns.view.js
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,7 @@ ns.View.prototype._getViewTree = function(layout, params) {
tree: {},
// всегда собираем данные, в том числе закешированные модели для async-view
models: this._getModelsData(),
errors: this._getModelsError(),
// есть view находится в режиме async, то модели проверять не надо
is_models_valid: this.asyncState || this.isModelsValid(),
// добавляем собственные параметры блока
Expand Down Expand Up @@ -1168,6 +1169,20 @@ ns.View.prototype._getModelsData = function() {
return r;
};

ns.View.prototype._getModelsError = function() {
var r = {};

var models = this.models;
for (var id in models) {
var error = models[id].getError();
if (error) {
r[id] = error;
}
}

return r;
};

/**
* Returns model.
* @param {String} id Model ID
Expand Down
10 changes: 10 additions & 0 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@
<script src="../src/ns.viewCollection.js"></script>
<!-- noscript (end) -->


<!-- yate -->
<script src="../node_modules/yate/lib/runtime.js"></script>
<script src="../yate/noscript-yate-externals.js"></script>
<script src="tests.yate.externals.js"></script>
<script src="tests.yate.js"></script>


<!-- fixtures (start) -->
<script src="fixtures/data.js"></script>
<!-- fixtures (end) -->
Expand All @@ -65,6 +73,7 @@

<!-- tests (start) -->
<!--<script src="spec/ns.request.js"></script>-->
<script src="spec/yate.check.js"></script>
<script src="spec/ns.js"></script>
<script src="spec/ns.action.js"></script>
<script src="spec/ns.box.js"></script>
Expand All @@ -83,6 +92,7 @@
<script src="spec/ns.view.events.js"></script>
<script src="spec/ns.viewCollection.js"></script>
<script src="spec/ns.viewCollection.events.js"></script>
<script src="spec/ns.view.errors.js"></script>
<!-- tests (end) -->
</head>
<body>
Expand Down
91 changes: 91 additions & 0 deletions test/spec/ns.view.errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
describe('ns.View error handling', function() {
describe('ns.Update.handleError', function() {

beforeEach(function() {
ns.Model.define('letter', { params: { id: null } });

ns.View.define('app');
ns.View.define('letter', { models: [ 'letter' ] });

ns.layout.define('app', {
'app': 'letter'
});

this.APP = ns.View.create('app');
this.update = new ns.Update(
this.APP,
ns.layout.page('app', {}),
{}
);

// Fake http server.
this.server = sinon.fakeServer.create();
this.server.autoRespond = true;
this.server.respondWith(/.*/, function(xhr, id) {
xhr.respond(
200,
{ "Content-Type": "application/json" },
JSON.stringify({ models: [ { error: 'letter not found' } ] })
);
});
});

afterEach(function() {
ns.clean();
this.server.restore();
delete this.APP;
delete this.update;
});

it('render .ns-view-error-content template', function(finish) {
var that = this;
var update = this.update;
ns.Update.handleError = function(_error, _update) {
expect(_update).to.be(update);
expect(_error.error).to.be('models');
return true;
};
this.update.start().done(function() {
expect($('.ns-view-letter', that.APP.node).html()).to.be('view-error-content');
finish();
});
});

it('pass error objects while rendering html', function(finish) {
ns.Update.handleError = function(_error, _update) {
return true;
};
this.update.start().done(function() {
var renderJSON = {
'views': {
'app': {
'is_models_valid': true,
'models': {},
'errors': {},
'views': {
'letter': {
'is_models_valid': false,
'models': {},
'errors': {
'letter': 'letter not found'
}
}
}
}
}
};
expect(ns.tmpl.calledWithMatch(renderJSON)).to.be.ok();
finish();
});
});

it('if ns.Update.handleError() returns `false` update is rejected', function(finish) {
ns.Update.handleError = function(_error, _update) {
return false;
};
this.update.start().fail(function() {
finish();
});
})
});
});
18 changes: 14 additions & 4 deletions test/spec/ns.viewCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -598,10 +598,20 @@ describe('ns.ViewCollection', function() {
});

it('should correctly update nested nodes', function() {
expect(this.collectionViewNode.childNodes).to.have.length(2);
expect(this.collectionViewNode.firstChild.childNodes).to.have.length(2);
expect(this.collectionViewNode.firstChild.firstChild.childNodes).to.have.length(1);
expect(this.collectionViewNode.lastChild.childNodes).to.have.length(0);
var cols = {};
cols['1'] = $('.ns-view-v-collection-2[data-key="view=v-collection-2&id=1"]', this.collectionViewNode);
cols['2'] = $('.ns-view-v-collection-2[data-key="view=v-collection-2&id=2"]', this.collectionViewNode);
cols['1.1'] = cols['1'].find('.ns-view-v-collection-2[data-key="view=v-collection-2&id=1.1"]');
cols['1.2'] = cols['1'].find('.ns-view-v-collection-2[data-key="view=v-collection-2&id=1.2"]');
cols['1.1.1'] = cols['1.1'].find('.ns-view-v-collection-2[data-key="view=v-collection-2&id=1.1.1"]');
cols['2.x'] = cols['2'].find('.ns-view-v-collection-2');

expect(cols['1'].length).to.be(1);
expect(cols['2'].length).to.be(1);
expect(cols['1.1'].length).to.be(1);
expect(cols['1.2'].length).to.be(1);
expect(cols['1.1.1'].length).to.be(1);
expect(cols['2.x'].length).to.be(0);
});

after(function() {
Expand Down
3 changes: 3 additions & 0 deletions test/spec/yate.check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
it('Check yate templates are ready [run `make yate` if it fails]', function() {
expect(yr.run('main', {}, 'check-yate-is-ready')).to.be('Ready');
});
46 changes: 2 additions & 44 deletions test/stub/ns.tmpl.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,7 @@
beforeEach(function() {

function genViewHTML(id, view) {

var html = '';
var clazz = 'ns-view-' + id;
if (view.async) {
clazz += ' ns-async';
}
if (view.collection) {
clazz += ' ns-view-container-desc';
}
if (view.placeholder) {
clazz += ' ns-view-placeholder';
}
html += '<div class="' + clazz + '" data-key="' + view.key + '" data-random="' + Math.random() + '">';
// don't create child views in async state
if (!view.async) {
html += genHTML(view.views);
}
html += '</div>';

return html;
}

function genHTML(views) {
var html = '';
for (var id in views) {
var view = views[id];

// collection
if (Array.isArray(view)) {
view.forEach(function(collectionItem) {
html += genViewHTML(id, collectionItem);
});

} else {
html += genViewHTML(id, view);
}

}
return html;
}

var ns_tmpl = ns.tmpl;
sinon.stub(ns, 'tmpl', function(json) {
return ns.html2node('<div class="root">' + genHTML(json.views) + '</div>');
return ns_tmpl.apply(ns, arguments);
});
});

Expand Down
24 changes: 24 additions & 0 deletions test/tests.yate
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module "main"

include "../yate/noscript.yate"

external scalar rand()

match .* ns-view-add-attrs {
@data-random="{ rand() }"
}

match .* ns-view-desc {
@data-random="{ rand() }"
apply /.views.* ns-view
}

match .* ns-view-async-content {
@class += ' ns-async'
"async-view-content"
}

// Это шаблон для проверки того, что yate сбилжен для тестов.
match / check-yate-is-ready {
"Ready"
}
5 changes: 5 additions & 0 deletions test/tests.yate.externals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
var externals = yr.externals = yr.externals || {};

externals.rand = function() {
return Math.random();
};
Loading

0 comments on commit c6ba4ef

Please sign in to comment.