feature: rich info error

This commit is contained in:
harttle
2016-11-09 01:58:49 +08:00
parent 295eee8134
commit 5c0209ec8b
12 changed files with 473 additions and 146 deletions
+14 -12
View File
@@ -41,12 +41,14 @@ describe('tag', function() {
tag.register('foo', {
render: spy
});
tag.construct({
type: 'tag',
value: 'foo',
name: 'foo'
}, []).render(scope, {});
expect(spy).to.have.been.called;
return tag
.construct({
type: 'tag',
value: 'foo',
name: 'foo'
}, [])
.render(scope, {})
.then(() => expect(spy).to.have.been.called);
});
it('should call tag.render with resolved hash', function() {
@@ -61,11 +63,11 @@ describe('tag', function() {
name: 'foo',
args: 'aa:foo bb: arr[0] cc: 2.3'
};
tag.construct(token, []).render(scope, {});
expect(spy).to.have.been.calledWithMatch(scope, {
aa: 'bar',
bb: 2,
cc: 2.3
});
return tag.construct(token, []).render(scope, {})
.then(() => expect(spy).to.have.been.calledWithMatch(scope, {
aa: 'bar',
bb: 2,
cc: 2.3
}));
});
});
+7 -4
View File
@@ -52,27 +52,30 @@ describe('tags/for', function() {
.to.eventually.equal('12345');
});
it('should support for with break', function() {
src = '{% for i in (one..5) %}' +
var src = '{% for i in (one..5) %}' +
'{% if i == 4 %}{% break %}{% endif %}' +
'{{ i }}' +
'{% endfor %}';
//return liquid.parseAndRender(src, ctx).catch(e => {
//console.log(e.stack);
//});
return expect(liquid.parseAndRender(src, ctx))
.to.eventually.equal('123');
});
it('should support for with limit', function() {
src = '{% for i in (1..5) limit:2 %}{{ i }}{% endfor %}';
var src = '{% for i in (1..5) limit:2 %}{{ i }}{% endfor %}';
return expect(liquid.parseAndRender(src, ctx))
.to.eventually.equal('12');
});
it('should support for with limit and offset', function() {
src = '{% for i in (1..10) limit:2 offset:5%}{{ i }}{% endfor %}';
var src = '{% for i in (1..10) limit:2 offset:5%}{{ i }}{% endfor %}';
return expect(liquid.parseAndRender(src, ctx))
.to.eventually.equal('67');
});
it('should support for reversed', function() {
src = '{% for i in (1..5) limit:2 reversed %}{{ i }}{% endfor %}';
var src = '{% for i in (1..5) limit:2 reversed %}{{ i }}{% endfor %}';
return expect(liquid.parseAndRender(src, ctx))
.to.eventually.equal('21');
});
+276 -58
View File
@@ -13,92 +13,310 @@ describe('error', function() {
describe('TokenizationError', function() {
it('should throw TokenizationError when tag illegal', function() {
return engine.parseAndRender('{% . a %}', {}).catch(function(err) {
expect(err.name).to.equal('TokenizationError');
expect(err.message).to.equal('illegal tag: {% . a %}');
expect(err.input).to.equal('{% . a %}');
expect(err.line).to.equal(1);
});
return expect(engine.parseAndRender('{% . a %}', {})).to.eventually
.be.rejected
.then(function(err) {
expect(err.name).to.equal('TokenizationError');
expect(err.message).to.contain('illegal tag syntax');
});
});
it('should throw TokenizationError when tag syntax illegal', function() {
return engine.parseAndRender('{% . a }', {}).catch(function(err) {
expect(err.name).to.equal('TokenizationError');
expect(err.message).to.equal('illegal tag: {% . a }');
expect(err.input).to.equal('{% . a }');
expect(err.line).to.equal(1);
});
it('should contain template content in err.message', function() {
var html = ['1st', '2nd', 'X{% . a %} Y', '4th'];
var message = [
'illegal tag syntax',
' 1| 1st',
' 2| 2nd',
'>> 3| X{% . a %} Y',
' 4| 4th'
];
return expect(engine.parseAndRender(html.join('\n'))).to.eventually
.be.rejected
.then(function(err) {
expect(err.message).to.equal(message.join('\n'));
expect(err.name).to.equal('TokenizationError');
});
});
it('should throw TokenizationError when filter syntax illegal', function() {
return engine.parseAndRender('{{ a|| }}', {}).catch(function(err) {
expect(err.name).to.equal('TokenizationError');
expect(err.message).to.equal('{{ a|| }');
expect(err.input).to.equal('{{ a|| }');
expect(err.line).to.equal(1);
it('should contain the whole template content in err.input', function() {
var html = 'bar\nfoo{% . a %}\nfoo';
return expect(engine.parseAndRender(html)).to.eventually
.be.rejected
.then(function(err) {
expect(err.input).to.equal(html);
});
});
it('should contain line number in err.line', function() {
return expect(engine.parseAndRender('1\n2\n{% . a %}\n4', {})).to.eventually
.be.rejected
.then(function(err) {
expect(err.name).to.equal('TokenizationError');
expect(err.line).to.equal(3);
});
});
it('should contain stack in err.stack', function() {
return expect(engine.parseAndRender('{% . a %}')).to.eventually
.be.rejected
.then(function(err) {
expect(err.stack).to.contain('illegal tag syntax');
expect(err.stack).to.contain('at Object.parse');
});
});
it('should contain file path in err.file', function() {
var html = '<html>\n<head>\n\n{% . a %}\n\n';
mock({
"/foo.html": html
});
return expect(engine.renderFile('/foo.html')).to.eventually
.be.rejected
.then(function(err) {
mock.restore();
expect(err.name).to.equal('TokenizationError');
expect(err.file).to.equal('/foo.html');
});
});
});
describe('TypeError', function() {
describe('RenderError', function() {
beforeEach(function() {
engine = require('../..')();
engine.registerTag('throwingTag', {
render: function() {
throw new Error('intended render error');
}
});
engine.registerTag('rejectingTag', {
render: function() {
return Promise.reject(new Error('intended render reject'));
}
});
engine.registerFilter('throwingFilter', () => {
throw new Error('throwed by filter');
});
});
it('should throw RenderError when tag throws', function() {
var src = '{%throwingTag%}';
return expect(engine.parseAndRender(src)).to.eventually
.be.rejected
.then(function(err) {
expect(err.name).to.equal('RenderError');
expect(err.message).to.contain('intended render error');
});
});
it('should throw RenderError when tag rejects', function() {
var src = '{%rejectingTag%}';
return expect(engine.parseAndRender(src)).to.eventually
.be.rejected
.then(function(err) {
expect(err.name).to.equal('RenderError');
expect(err.message).to.contain('intended render reject');
});
});
it('should throw RenderError when filter throws', function() {
var src = '{{1|throwingFilter}}';
return expect(engine.parseAndRender(src)).to.eventually
.be.rejected
.then(function(err) {
expect(err.name).to.equal('RenderError');
expect(err.message).to.contain('throwed by filter');
});
});
it('should not throw when variable undefined by default', function() {
return expect(engine.parseAndRender('X{{a}}Y')).to.eventually.equal('XY');
});
it('should throw TypeError when variable not defined', function() {
it('should throw RenderError when variable not defined', function() {
return expect(strictEngine.parseAndRender('{{a}}')).to.eventually
.be.rejected
.then(function(e){
expect(e).to.have.property('name', 'TypeError');
expect(e).to.have.property('message', 'undefined variable: a');
.then(function(e) {
expect(e).to.have.property('name', 'RenderError');
expect(e.message).to.contain('undefined variable: a');
});
});
it('should throw TypeError when filter not defined', function() {
it('should throw RenderError when filter not defined', function() {
return expect(strictEngine.parseAndRender('{{1 | a}}')).to.eventually
.be.rejected
.then(function(e){
expect(e).to.have.property('name', 'TypeError');
expect(e).to.have.property('message', 'undefined filter: a');
.then(function(e) {
expect(e).to.have.property('name', 'RenderError');
expect(e.message).to.contain('undefined filter: a');
});
});
it('should contain template content in err.message', function() {
var html = ['1st', '2nd', '3rd', 'X{%throwingTag%} Y', '5th', '6th', '7th'];
var message = [
'intended render error',
' 2| 2nd',
' 3| 3rd',
'>> 4| X{%throwingTag%} Y',
' 5| 5th',
' 6| 6th',
' 7| 7th'
];
return expect(engine.parseAndRender(html.join('\n'))).to.eventually
.be.rejected
.then(function(err) {
expect(err.message).to.equal(message.join('\n'));
expect(err.name).to.equal('RenderError');
});
});
it('should contain the whole template content in err.input', function() {
var html = 'bar\nfoo{%throwingTag%}\nfoo';
return expect(engine.parseAndRender(html)).to.eventually
.be.rejected
.then(function(err) {
expect(err.input).to.equal(html);
expect(err.name).to.equal('RenderError');
});
});
it('should contain line number in err.line', function() {
var src = '1\n2\n{{1|throwingFilter}}\n4';
return expect(engine.parseAndRender(src)).to.eventually
.be.rejected
.then(function(err) {
expect(err.line).to.equal(3);
expect(err.name).to.equal('RenderError');
});
});
it('should contain stack in err.stack', function() {
return expect(engine.parseAndRender('{%rejectingTag%}')).to.eventually
.be.rejected
.then(function(err) {
expect(err.stack).to.contain('intended render reject');
expect(err.stack).to.contain('at Object.engine.registerTag.render');
});
});
it('should contain file path in err.file', function() {
var html = '<html>\n<head>\n\n{% throwingTag %}\n\n';
mock({
"/foo.html": html
});
return expect(engine.renderFile('/foo.html')).to.eventually
.be.rejected
.then(function(err) {
mock.restore();
expect(err.name).to.equal('RenderError');
expect(err.file).to.equal('/foo.html');
});
});
});
describe('ParseError', function() {
it('should throw correct error info', function() {
var src = '{%if true%}\naaa{%endif%}\n{% -a %}\n3';
return engine.parseAndRender(src).catch(function(err) {
expect(err.name).to.equal('ParseError');
expect(err.input).to.equal('{% -a %}');
expect(err.line).to.equal(3)
expect(err.stack).to.contain('From AssertionError: tag -a not found');
beforeEach(function() {
engine = require('../..')();
engine.registerTag('throwsOnParse', {
parse: function() {
throw new Error('intended parse error');
}
});
});
it('should throw correct error info for files', function() {
mock({
"/foo.html": '<html>\n<head>\n\n{% raw %}\n\n'
});
return engine.renderFile('/foo.html').catch(function(err) {
mock.restore();
expect(err.name).to.equal('ParseError');
expect(err.input).to.equal('{% raw %}');
expect(err.line).to.equal(4);
expect(err.file).to.equal('/foo.html');
});
it('should throw ParseError when tag not closed', function() {
return expect(engine.parseAndRender('{% if %}')).to.eventually
.be.rejected
.then(function(err) {
expect(err.name).to.equal('ParseError');
expect(err.message).to.contain('tag {% if %} not closed');
});
});
it('should throw ParseError when tag parse throws', function() {
var src = '{%throwsOnParse%}';
return expect(engine.parseAndRender(src)).to.eventually
.be.rejected
.then(function(err) {
expect(err.name).to.equal('ParseError');
expect(err.message).to.contain('intended parse error');
});
});
it('should throw ParseError when tag not found', function() {
var src = '{%if true%}\naaa{%endif%}\n{% -a %}\n3';
return expect(engine.parseAndRender(src)).to.eventually
.be.rejected
.then(function(err) {
expect(err.name).to.equal('ParseError');
expect(err.message).to.contain('tag -a not found');
});
});
it('should throw ParseError when tag not exist', function() {
return engine.parseAndRender('{% a %}').catch(function(err) {
expect(err.name).to.equal('ParseError');
expect(err.message).to.equal('tag a not found');
expect(err.input).to.equal('{% a %}');
expect(err.line).to.equal(1);
});
return expect(engine.parseAndRender('{% a %}')).to.eventually
.be.rejected
.then(function(err) {
expect(err.name).to.equal('ParseError');
expect(err.message).to.contain('tag a not found');
});
});
it('should throw ParseError when tag not closed', function() {
return engine.parseAndRender('{% if %}').catch(function(err) {
expect(err.name).to.equal('ParseError');
expect(err.message).to.equal('tag {% if %} not closed');
expect(err.input).to.equal('{% if %}');
expect(err.line).to.equal(1);
it('should contain template content in err.message', function() {
var html = ['1st', '2nd', '3rd', 'X{% a %} {% enda %} Y', '5th', '6th', '7th'];
var message = [
'tag a not found',
' 2| 2nd',
' 3| 3rd',
'>> 4| X{% a %} {% enda %} Y',
' 5| 5th',
' 6| 6th',
' 7| 7th'
];
return expect(engine.parseAndRender(html.join('\n'))).to.eventually
.be.rejected
.then(function(err) {
expect(err.message).to.equal(message.join('\n'));
expect(err.name).to.equal('ParseError');
});
});
it('should handle err.message when context not enough', function() {
var html = ['1st', 'X{% a %} {% enda %} Y', '3rd', '4th'];
var message = [
'tag a not found',
' 1| 1st',
'>> 2| X{% a %} {% enda %} Y',
' 3| 3rd',
' 4| 4th'
];
return expect(engine.parseAndRender(html.join('\n'))).to.eventually
.be.rejected
.then(function(err) {
expect(err.message).to.equal(message.join('\n'));
});
});
it('should contain the whole template content in err.input', function() {
var html = 'bar\nfoo{% a %}\nfoo';
return expect(engine.parseAndRender(html)).to.eventually
.be.rejected
.then(function(err) {
expect(err.input).to.equal(html);
});
});
it('should contain line number in err.line', function() {
var html = '<html>\n<head>\n\n{% raw %}\n\n';
return expect(engine.parseAndRender(html)).to.eventually
.be.rejected
.then(function(err) {
expect(err.line).to.equal(4);
});
});
it('should contain stack in err.stack', function() {
return expect(engine.parseAndRender('{% -a %}')).to.eventually
.be.rejected
.then(function(err) {
expect(err.stack).to.contain('AssertionError: tag -a not found');
expect(err.stack).to.contain('at Object._tagInstance.parse');
});
});
it('should contain file path in err.file', function() {
var html = '<html>\n<head>\n\n{% raw %}\n\n';
mock({
"/foo.html": html
});
return expect(engine.renderFile('/foo.html')).to.eventually
.be.rejected
.then(function(err) {
mock.restore();
expect(err.name).to.equal('ParseError');
expect(err.file).to.equal('/foo.html');
});
});
});
});
+17
View File
@@ -1,11 +1,28 @@
const chai = require("chai");
const sinon = require('sinon');
const expect = chai.expect;
const Errors = require('../../src/util/error.js');
chai.use(require("sinon-chai"));
var _ = require('../../src/util/underscore.js');
describe('util/underscore', function() {
describe('.isError()', function() {
it('should return true for new Error', function() {
expect(_.isError(new Error())).to.be.true;
});
it('should return true for RenderError', function() {
var tpl = {
token: {
input: 'xx'
}
};
expect(_.isError(new Errors.RenderError(new Error(), tpl))).to.be.true;
});
it('should return true for RenderBreakError', function() {
expect(_.isError(new Errors.RenderBreakError())).to.be.true;
});
});
describe('.isString()', function() {
it('should return true for literal string', function() {
expect(_.isString('foo')).to.be.true;