jinja-js
Version:
JavaScript templating engine based on Jinja2
494 lines (435 loc) • 15.8 kB
JavaScript
var require = require('./testutils').require,
expect = require('expect.js'),
swig = require('../../lib/swig'),
tags = require('../../lib/tags'),
parser = require('../../lib/parser');
describe('Tags', function () {
it('undefined tag throws error', function () {
expect(function () { parser.parse('{% foobar %}', {}); })
.to.throwException();
});
it('creates a token', function () {
expect(parser.parse('{% blah %}', { blah: {} }))
.to.eql([{
type: parser.TOKEN_TYPES.LOGIC,
line: 1,
name: 'blah',
args: [],
compile: {},
strip: { before: false, after: false, start: false, end: false },
parent: []
}]);
});
it('appends arguments', function () {
expect(parser.parse('{% blah "foobar" %}', { blah: {} }))
.to.eql([{
type: parser.TOKEN_TYPES.LOGIC,
line: 1,
name: 'blah',
args: ['"foobar"'],
compile: {},
strip: { before: false, after: false, start: false, end: false },
parent: []
}]);
});
it('appends multiple arguments', function () {
expect(parser.parse('{% blah "foobar" barfoo %}', { blah: {} }))
.to.eql([{
type: parser.TOKEN_TYPES.LOGIC,
line: 1,
name: 'blah',
args: ['"foobar"', 'barfoo'],
compile: {},
strip: { before: false, after: false, start: false, end: false },
parent: []
}]);
});
it('can require end tags', function () {
expect(parser.parse('{% blah %}{% endblah %}', { blah: { ends: true } }))
.to.eql([{
type: parser.TOKEN_TYPES.LOGIC,
line: 1,
name: 'blah',
args: [],
compile: { ends: true },
tokens: [],
strip: { before: false, after: false, start: false, end: false },
parent: []
}]);
});
it('can span multiple lines', function () {
expect(parser.parse('{% blah \n arg1 %}{% endblah\n %}', { blah: { ends: true } }))
.to.eql([{
type: parser.TOKEN_TYPES.LOGIC,
line: 1,
name: 'blah',
args: ['arg1'],
compile: { ends: true },
tokens: [],
strip: { before: false, after: false, start: false, end: false },
parent: []
}]);
});
it('includes the line number in the token', function () {
expect(parser.parse('hi!\n\n\n{% blah %}{% endblah %}', { blah: { ends: true } })[1])
.to.eql({
type: parser.TOKEN_TYPES.LOGIC,
line: 4,
name: 'blah',
args: [],
compile: { ends: true },
tokens: [],
strip: { before: false, after: false, start: false, end: false },
parent: []
});
});
it('throws if should have end but no end found', function () {
var fn = function () {
parser.parse('{% blah %}', { blah: { ends: true }});
};
expect(fn).to.throwException();
});
it('throws if not end but end found', function () {
var fn = function () {
parser.parse('{% blah %}{% endblah %}', { blah: {}});
};
expect(fn).to.throwException();
});
it('throws on unbalanced end tag', function () {
var fn = function () {
parser.parse('{% blah %}{% endfoo %}', { blah: { ends: true }, foo: { ends: true }});
};
expect(fn).to.throwException();
});
it('tag with contents', function () {
expect(parser.parse('{% blah %}hello{% endblah %}', { blah: { ends: true } }))
.to.eql([{
type: parser.TOKEN_TYPES.LOGIC,
line: 1,
name: 'blah',
args: [],
compile: { ends: true },
tokens: ['hello'],
strip: { before: false, after: false, start: false, end: false },
parent: []
}]);
});
describe('arguments', function () {
it('can be a string', function () {
expect(parser.parse('{% foo "hi" %}', { foo: {} })[0].args)
.to.eql(['"hi"']);
});
it('can be a string with spaces', function () {
expect(parser.parse('{% foo "hi mom" %}', { foo: {} })[0].args)
.to.eql(['"hi mom"']);
});
it('random chars', function () {
expect(parser.parse('{% foo "{" "[" & ^ ; * , a "]" "}" %}', { foo: {} })[0].args)
.to.eql([
'"{"', '"["', '&', '^', ';', '*', ',', 'a', '"]"', '"}"'
]);
});
it('string with comma', function () {
expect(parser.parse('{% foo "hi,mom" %}', { foo: {} })[0].args)
.to.eql(['"hi,mom"']);
});
it('double-quote string with comma', function () {
expect(parser.parse('{% foo "hi\", \"mom" %}', { foo: {} })[0].args)
.to.eql(['"hi\", \"mom"']);
});
it('single-quote string with comma', function () {
expect(parser.parse("{% foo 'hi\', \'mom' %}", { foo: {} })[0].args)
.to.eql(['\'hi\', \'mom\'']);
});
it('empty array', function () {
expect(parser.parse("{% foo [] %}", { foo: {} })[0].args)
.to.eql(['[]']);
});
it('empty object', function () {
expect(parser.parse("{% foo {} %}", { foo: {} })[0].args)
.to.eql(['{}']);
});
it('array', function () {
expect(parser.parse("{% foo ['hi', 'your', 'mom'] %}", { foo: {} })[0].args)
.to.eql(['[\'hi\', \'your\', \'mom\']']);
});
it('object', function () {
expect(parser.parse("{% foo { 'foo': 1, 'bar': true } %}", { foo: {} })[0].args)
.to.eql(['{ \'foo\': 1, \'bar\': true }']);
});
it('combo', function () {
expect(parser.parse("{% foo { 'bar': true } 'foo' [] %}", { foo: {} })[0].args)
.to.eql(['{ \'bar\': true }', "'foo'", '[]']);
});
});
it('throws on bad argument format', function () {
var fn = function () {
parser.parse('{% foo "blah is foo %}', { foo: {} });
};
expect(fn).to.throwException();
});
});
describe('Whitespace', function () {
beforeEach(function () {
var tags = {
foo: function (indent) {
return parser.compile.apply(this, [indent + ' ']);
},
bar: function (indent) {
return '';
}
};
tags.foo.ends = true;
swig.init({
tags: tags,
allowErrors: true
});
});
it('whitespace before', function () {
expect(swig.compile('{% foo -%} foo{% endfoo %}')())
.to.eql('foo');
});
it('whitespace after', function () {
expect(swig.compile('{% foo %}foo {%- endfoo %}')())
.to.eql('foo');
});
it('whitespace before and after', function () {
expect(swig.compile('{% foo -%}\n\r\t foo\n\r\t {%- endfoo %}')())
.to.eql('foo');
});
it('previous whitespace removed', function () {
expect(swig.compile('a {%- bar %}b')())
.to.eql('ab');
});
it('all whitespace removed', function () {
expect(swig.compile('a {%- foo -%} b {%- endfoo -%} c')())
.to.eql('abc');
});
it('whitespace before only up until tag', function () {
expect(swig.compile('{% foo -%} {% bar %} foo{% endfoo %}')())
.to.eql(' foo');
});
it('whitespace after only up until tag', function () {
expect(swig.compile('{% foo%}foo {% bar %}{%- endfoo %}')())
.to.eql('foo ');
});
});
describe('Comments', function () {
it('empty strings are ignored', function () {
expect(parser.parse('')).to.eql([]);
expect(parser.parse(' ')).to.eql([]);
expect(parser.parse(' \n')).to.eql([]);
});
it('comments are ignored', function () {
expect(parser.parse('{# foobar #}')).to.eql([]);
});
it('can contain anything and will be ignored', function () {
expect(parser.parse('{# foo {% blah %} #}')).to.eql([]);
expect(parser.parse('{# this is a multiline\n comment #}')).to.eql([]);
});
});
describe('Variables', function () {
beforeEach(function () {
swig.init({ allowErrors: true });
});
var token = [{
type: parser.TOKEN_TYPES.VAR,
name: 'foobar',
filters: [],
escape: false,
args: null
}];
it('can include spaces between open and close tags', function () {
expect(parser.parse('{{ foobar }}')).to.eql(token);
});
it('does not require spaces', function () {
expect(parser.parse('{{foobar}}')).to.eql(token);
});
it('can span lines', function () {
expect(parser.parse('{{\n foobar \n}}')).to.eql(token);
});
describe('accepts varying notation', function () {
it('can use dot notation', function () {
expect(swig.compile('{{ a.b.c }}')({ a: { b: { c: 'hi' }} }))
.to.equal('hi');
});
it('can use bracket notation', function () {
expect(swig.compile('{{ a[0][1] }}')({ a: [['no', 'yes']] }))
.to.equal('yes');
});
it('can mix notation styles', function () {
expect(swig.compile('{{ a[0].b[1] }}')({ a: [{ b: ['no', 'yes'] }] }))
.to.equal('yes');
});
it('can use really complex notations', function () {
expect(swig.compile('{{ a[0][h.g.i]["c"].b[d] }}')({ a: { '0': { q: { c: { b: { foo: 'hi!' }}}}}, h: { g: { i: 'q' } }, d: 'foo' })
)
.to.equal('hi!');
});
it('can use bracket notation with keys containing periods', function () {
expect(swig.compile('{{ a["b.c"] }}')({ a: {"b.c": "hi"} }))
.to.equal('hi');
});
});
describe('can use filters', function () {
it('by name only', function () {
expect(parser.parse('{{ foobar|awesome }}')).to.eql([{
type: parser.TOKEN_TYPES.VAR,
name: 'foobar',
escape: false,
args: null,
filters: [{ name: 'awesome', args: '' }]
}]);
});
it('with arguments', function () {
expect(parser.parse('{{ foobar|awesome("param", ctxvar, 2) }}')).to.eql([{
type: parser.TOKEN_TYPES.VAR,
name: 'foobar',
escape: false,
args: null,
filters: [{ name: 'awesome', args: '"param", ctxvar, 2' }]
}]);
});
it('chained', function () {
expect(parser.parse('{{ foobar|baz(1)|rad|awesome("param", 2) }}')).to.eql([{
type: parser.TOKEN_TYPES.VAR,
name: 'foobar',
escape: false,
args: null,
filters: [
{ name: 'baz', args: '1' },
{ name: 'rad', args: '' },
{ name: 'awesome', args: '"param", 2' }
]
}]);
});
it('does not carry filters to subsequent vars', function () {
expect(parser.parse('{{ foo|baz }}{{ bar }}')).to.eql([
{ type: parser.TOKEN_TYPES.VAR, name: 'foo', filters: [{ name: 'baz', args: '' }], escape: false, args: null },
{ type: parser.TOKEN_TYPES.VAR, name: 'bar', filters: [], escape: false, args: null }
]);
});
it('accepts varying characters in arguments', function () {
expect(parser.parse("{{ foo|blah('01a|\"\\',;?./¨œ∑´®†][{}]') }}")[0].filters)
.to.eql([{ name: 'blah', args: "\'01a|\"\\\\\',;?./¨œ∑´®†][{}]\'" }]);
expect(parser.parse("{{ foo|blah('01a') }}")[0].filters)
.to.eql([{ name: 'blah', args: "'01a'" }]);
expect(parser.parse("{{ foo|blah('|') }}")[0].filters)
.to.eql([{ name: 'blah', args: "'|'" }]);
expect(parser.parse("{{ foo|blah('\"') }}")[0].filters)
.to.eql([{ name: 'blah', args: "'\"'" }]);
expect(parser.parse('{{ foo|blah("\'") }}')[0].filters)
.to.eql([{ name: 'blah', args: '"\'"' }]);
expect(parser.parse("{{ foo|blah(',') }}")[0].filters)
.to.eql([{ name: 'blah', args: "','" }]);
expect(parser.parse("{{ foo|blah(';') }}")[0].filters)
.to.eql([{ name: 'blah', args: "';'" }]);
expect(parser.parse("{{ foo|blah('?./¨œ∑´®†') }}")[0].filters)
.to.eql([{ name: 'blah', args: "'?./¨œ∑´®†'" }]);
expect(parser.parse("{{ foo|blah('[]') }}")[0].filters)
.to.eql([{ name: 'blah', args: "'[]'" }]);
expect(parser.parse("{{ foo|blah('[') }}")[0].filters)
.to.eql([{ name: 'blah', args: "'['" }]);
expect(parser.parse("{{ foo|blah(']') }}")[0].filters)
.to.eql([{ name: 'blah', args: "']'" }]);
expect(parser.parse("{{ foo|blah('{}') }}")[0].filters)
.to.eql([{ name: 'blah', args: "'{}'" }]);
expect(parser.parse("{{ foo|blah('{') }}")[0].filters)
.to.eql([{ name: 'blah', args: "'{'" }]);
expect(parser.parse("{{ foo|blah('}') }}")[0].filters)
.to.eql([{ name: 'blah', args: "'}'" }]);
// Edge cases
expect(parser.parse("{{ foo|blah('a\", \"b') }}")[0].filters)
.to.eql([{ name: 'blah', args: "'a\", \"b'" }]);
expect(parser.parse("{{ foo|blah('a\",\"b') }}")[0].filters)
.to.eql([{ name: 'blah', args: "'a\",\"b'" }]);
expect(parser.parse('{{ foo|blah("a\', \'b") }}')[0].filters)
.to.eql([{ name: 'blah', args: '"a\', \'b"' }]);
expect(parser.parse('{{ foo|blah("a\',\'b") }}')[0].filters)
.to.eql([{ name: 'blah', args: '"a\',\'b"' }]);
});
it('properly escapes arguments', function () {
expect(parser.parse('{{ foo|blah("\\s") }}')).to.eql([{
type: parser.TOKEN_TYPES.VAR,
name: 'foo',
filters: [{ name: 'blah', args: '"\\\\s"' }],
escape: false,
args: null
}]);
});
it('can have arguments spanning multiple lines', function () {
swig.init({
filters: {
blah: function (value, a, b, c) {
expect(a).to.equal('a');
expect(b).to.equal('b');
expect(c).to.equal('c');
return [a, b, c].join('');
}
}
});
expect(swig.compile("{{ foo\n|\nblah(\n'a', \n'b',\n'c'\n) }}")({ foo: true }))
.to.equal('abc');
});
});
});
describe('Compiling', function () {
beforeEach(function () {
swig.init({ allowErrors: true });
});
it('does not throw if compiler is broken', function () {
var fn = function () {
var tpl = {
tokens: [{
type: parser.TOKEN_TYPES.VAR,
name: 'foobar',
filters: [],
escape: false,
args: null
}]
};
eval('var _output = "";' + parser.compile.call(tpl, ''));
};
expect(fn).to.not.throwException();
});
it('does not throw', function () {
var fn = function () {
var tpl = { tokens: parser.parse('{% if foo %}blah{% endif %}', tags) };
eval('var _output = "";' + parser.compile.call(tpl, ''));
};
expect(fn).to.not.throwException();
});
it('throws on extends with multiple arguments', function () {
var fn = function () {
eval('var _output = "";' + parser.compile.call({
tokens: parser.parse('{% extends "foo" "bar" %}', tags),
type: parser.TEMPLATE
}, ''));
};
expect(fn).to.throwException();
});
it('throws if extends is not the first token', function () {
var fn = function () {
eval('var _output = "";' + parser.compile.call({ tokens: parser.parse('blah!{% extends "foobar" %}', tags), type: parser.TEMPLATE }, ''));
};
expect(fn).to.throwException();
});
describe('block naming', function () {
it('does not throw on alpha block name', function () {
var fn = function () {
eval('var _output = "";' + parser.compile.call({ tokens: parser.parse('{% block foobar %}{% endblock %}', tags), type: parser.TEMPLATE }, ''));
};
expect(fn).to.not.throwException();
});
it('throws on non alpha block name', function () {
var fn = function () {
eval('var _output = "";' + parser.compile.call({ tokens: parser.parse('{% block 123 %}{% endblock %}', tags), type: parser.TEMPLATE }, ''));
};
expect(fn).to.throwException();
});
});
it('does not remove duplicate strings when extended', function () {
swig.compile('"{{ e }}"{{ f }}"{{ g }}"{{ h }}', { filename: 'foo' });
expect(swig.compile('{% extends "foo" %}')({ e: 'e', f: 'f', g: 'g', h: 'h' })).to.equal('"e"f"g"h');
});
});