diff --git a/README.md b/README.md
index a13ca7785..09fb76256 100644
--- a/README.md
+++ b/README.md
@@ -53,37 +53,41 @@ Documentation:
'));
+ liquid.registerFilter('plus', bindFixed((v, arg) => v + arg));
+ liquid.registerFilter('prepend', (v, arg) => arg + v);
+ liquid.registerFilter('remove', (v, arg) => v.split(arg).join(''));
+ liquid.registerFilter('remove_first', (v, l) => v.replace(l, ''));
+ liquid.registerFilter('replace', (v, pattern, replacement) =>
+ _.replace(v, new RegExp(_.escapeRegExp(pattern), 'g'), replacement));
+ liquid.registerFilter('replace_first', _.replace);
+ liquid.registerFilter('reverse', _.reverse);
+ liquid.registerFilter('round', _.round);
+ liquid.registerFilter('rstrip', _.trimEnd);
+ liquid.registerFilter('size', _.size);
+ liquid.registerFilter('slice', (v, begin, length) =>
+ v.substr(begin, length === undefined ? 1 : length));
+ liquid.registerFilter('sort', _.sortBy);
+ liquid.registerFilter('split', _.split);
+ liquid.registerFilter('strip', _.trim);
+ liquid.registerFilter('strip_html', v => _.replace(v, /<\/?\s*\w+\s*\/?>/g, ''));
+ liquid.registerFilter('strip_newlines', v => _.replace(v, /\n/g, ''));
+ liquid.registerFilter('times', (v, arg) => v * arg);
+ liquid.registerFilter('truncate', (v, l, o) => _.truncate(v, {
+ length: l,
+ omission: o === undefined ? '...' : o
+ }));
+ liquid.registerFilter('truncatewords', (v, l, o) => {
+ if (o === undefined) o = '...';
+ var arr = v.split(' ');
+ var ret = arr.slice(0, l).join(' ');
+ if (arr.length > l) ret += o;
+ return ret;
+ });
+ liquid.registerFilter('uniq', _.uniq);
+ liquid.registerFilter('upcase', _.toUpper);
+ liquid.registerFilter('url_encode', encodeURIComponent);
};
+
+function getFixed(v) {
+ var p = (v + "").split(".");
+ return (p.length > 1) ? p[1].length : 0;
+}
+
+function getMaxFixed(l, r) {
+ return Math.max(getFixed(l), getFixed(r));
+}
+
+function bindFixed(cb) {
+ return (l, r) => {
+ var f = getMaxFixed(l, r);
+ return cb(l, r).toFixed(f);
+ };
+}
diff --git a/index.js b/index.js
index 2f3f81c56..118e40410 100644
--- a/index.js
+++ b/index.js
@@ -28,21 +28,22 @@ function factory(){
engine.filter = Filter();
engine.tokenize = tokenizer.parse;
- var template = Template(engine.tag);
- engine.parse = template.parse;
- engine.parseTag = template.parseTag;
- engine.parseStream = template.parseStream;
+ engine.template = Template(engine.tag, engine.filter);
+ engine.parseStream = engine.template.parseStream;
var renderer = Render(engine.filter, engine.tag);
- engine.evalFilter = renderer.evalFilter;
engine.renderTemplates = renderer.renderTemplates;
engine.render = function(html, ctx) {
var tokens = engine.tokenize(html);
- var templates = engine.parse(tokens);
+ var templates = engine.template.parse(tokens);
engine.register = {};
return engine.renderTemplates(templates, scope.factory(ctx));
- },
+ };
+ engine.evalOutput = function(str, scope) {
+ var template = engine.template.parseOutput(str.trim());
+ return renderer.evalOutput(template, scope);
+ };
fs.readdirSync(tagsPath).map(function(f){
var match = /^(\w+)\.js$/.exec(f);
diff --git a/lexical.js b/lexical.js
index 39c9028b5..4900915a3 100644
--- a/lexical.js
+++ b/lexical.js
@@ -1,33 +1,43 @@
const _ = require('lodash');
+// quote related
var singleQuoted = /'[^']*'/;
var doubleQuoted = /"[^"]*"/;
+var quoteBalanced = new RegExp(`(?:${singleQuoted.source}|${doubleQuoted.source}|[^'"])*`);
-var number = /-?\d+\.?\d*|\.?\d+/;
+var number = /(?:-?\d+\.?\d*|\.?\d+)/;
var bool = /true|false/i;
var identifier = /[a-zA-Z_$][a-zA-Z_$0-9]*/;
var subscript = /\[\d+\]/;
-var quoted = new RegExp(`${singleQuoted.source}|${doubleQuoted.source}`);
-var literal = new RegExp(`${quoted.source}|${bool.source}|${number.source}`, 'i');
+var quoted = new RegExp(`(?:${singleQuoted.source}|${doubleQuoted.source})`);
+var literal = new RegExp(`(?:${quoted.source}|${bool.source}|${number.source})`, 'i');
var variable = new RegExp(`${identifier.source}(?:\\.${identifier.source}|${subscript.source})*`);
-var value = new RegExp(`${literal.source}|${variable.source}`, 'i');
-var range = new RegExp(`\\((?:${value.source})\\.\\.(?:${value.source})\\)`);
-var rangeCapture = new RegExp(`\\((${value.source})\\.\\.(${value.source})\\)`);
-var variableOrRange = new RegExp(`${variable.source}|${range.source}`);
+
+// range related
+var rangeLimit = new RegExp(`(?:${variable.source}|${number.source})`);
+var range = new RegExp(`\\(${rangeLimit.source}\\.\\.${rangeLimit.source}\\)`);
+var rangeCapture = new RegExp(`\\((${rangeLimit.source})\\.\\.(${rangeLimit.source})\\)`);
+
+var value = new RegExp(`(?:${literal.source}|${variable.source}|${range.source})`, 'i');
+
+// hash related
var hash = new RegExp(`(?:${identifier.source})\\s*:\\s*(?:${value.source})`, 'g');
var hashCapture = new RegExp(`(${identifier.source})\\s*:\\s*(${value.source})`, 'g');
-var filter = new RegExp(`(${identifier.source})(?:\\s*:\\s*(${value.source}))?`);
-var quoteBalanced = new RegExp(`(?:${singleQuoted.source}|${doubleQuoted.source}|[^'"])*`);
var tagLine = new RegExp(`^\\s*(${identifier.source})\\s*(.*)\\s*$`);
-var literalLine = new RegExp(`^(?:${literal.source})$`, 'i');
-var variableLine = new RegExp(`^(?:${variable.source})$`);
-var numberLine = new RegExp(`^(?:${number.source})$`);
-var boolLine = new RegExp(`^(?:${bool.source})$`, 'i');
-var quotedLine = new RegExp(`^(?:${quoted.source})$`);
-var rangeLine = new RegExp(`^(?:${rangeCapture.source})$`);
-var filterLine = new RegExp(`^(?:${filter.source})$`);
+var literalLine = new RegExp(`^${literal.source}$`, 'i');
+var variableLine = new RegExp(`^${variable.source}$`);
+var numberLine = new RegExp(`^${number.source}$`);
+var boolLine = new RegExp(`^${bool.source}$`, 'i');
+var quotedLine = new RegExp(`^${quoted.source}$`);
+var rangeLine = new RegExp(`^${rangeCapture.source}$`);
+
+// filter related
+var valueList = new RegExp(`${value.source}(\\s*,\\s*${value.source})*`);
+var filter = new RegExp(`${identifier.source}(?:\\s*:\\s*${valueList.source})?`, 'g');
+var filterCapture = new RegExp(`(${identifier.source})(?:\\s*:\\s*(${valueList.source}))?`);
+var filterLine = new RegExp(`^${filterCapture.source}$`);
var operators = [
/\s+or\s+/,
@@ -35,7 +45,6 @@ var operators = [
/==|!=|<=|>=|<|>|\s+contains\s+/
];
-
function isLiteral(str) {
return literalLine.test(str);
}
@@ -64,7 +73,7 @@ function parseLiteral(str) {
module.exports = {
quoted, number, bool, literal, filter,
hash, hashCapture,
- range, rangeCapture, variableOrRange,
+ range, rangeCapture,
identifier, value, quoteBalanced, operators,
quotedLine, numberLine, boolLine, rangeLine, literalLine, filterLine, tagLine,
isLiteral, isVariable, parseLiteral, isRange
diff --git a/package.json b/package.json
index 73ca0cc83..45efed133 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "shopify-liquid",
- "version": "1.0.3",
+ "version": "1.1.0",
"description": "A Shopify Liquid Implementation in Node.js",
"main": "index.js",
"scripts": {
diff --git a/parse-stream.js b/parse-stream.js
deleted file mode 100644
index e69de29bb..000000000
diff --git a/render.js b/render.js
index 9f09214a9..bfb7f7b6e 100644
--- a/render.js
+++ b/render.js
@@ -1,6 +1,6 @@
-const lexical = require('./lexical.js');
const error = require('./error.js');
const Exp = require('./expression.js');
+const assert = require('assert');
function stringify(val) {
if (typeof val === 'string') return val;
@@ -8,43 +8,51 @@ function stringify(val) {
}
function factory(Filter, Tag) {
+
function renderTemplates(templates, scope) {
- if (!scope) throw new Error('unable to evalTemplates: scope undefined');
- var html = '';
+ assert(scope, 'unable to evalTemplates: scope undefined');
+ var html = '', partial;
templates.some(template => {
if (scope.get('forloop.skip')) return true;
- if (template.type === 'tag') {
- if (template.name === 'continue') {
- scope.set('forloop.skip', true);
- return true;
- }
- if (template.name === 'break') {
- scope.set('forloop.stop', true);
- scope.set('forloop.skip', true);
- return true;
- }
- html += template.render(scope, this.register);
- } else if (template.type === 'html') {
- html += template.value;
- } else if (template.type === 'output') {
- var val = evalFilter(template.value, scope);
- html += stringify(val);
+ switch (template.type) {
+ case 'tag':
+ partial = renderTag(template, scope, this.register);
+ if(partial === undefined) return true;
+ html += partial;
+ break;
+ case 'html':
+ html += template.value;
+ break;
+ case 'output':
+ var val = evalOutput(template, scope);
+ html += stringify(val);
}
});
return html;
}
- function evalFilter(str, scope) {
- if (!scope) throw new Error('unable to evalFilter: scope undefined');
- var filters = str.split('|');
- var val = Exp.evalValue(filters.shift(), scope);
- return filters
- .map(str => Filter.construct(str))
+ function renderTag(template, scope, register) {
+ if (template.name === 'continue') {
+ scope.set('forloop.skip', true);
+ return;
+ }
+ if (template.name === 'break') {
+ scope.set('forloop.stop', true);
+ scope.set('forloop.skip', true);
+ return;
+ }
+ return template.render(scope, register);
+ }
+
+ function evalOutput(template, scope) {
+ assert(scope, 'unable to evalOutput: scope undefined');
+ var val = Exp.evalExp(template.initial, scope);
+ return template.filters
.reduce((v, filter) => filter.render(v, scope), val);
}
return {
- renderTemplates, evalFilter
+ renderTemplates, evalOutput, renderTag
};
}
diff --git a/tags/assign.js b/tags/assign.js
index d03e97a08..effc31ac4 100644
--- a/tags/assign.js
+++ b/tags/assign.js
@@ -12,7 +12,7 @@ module.exports = function(liquid) {
this.value = match[2];
},
render: function(scope, hash) {
- scope.set(this.key, Liquid.evalValue(this.value, scope));
+ scope.set(this.key, liquid.evalOutput(this.value, scope));
}
});
diff --git a/tags/for.js b/tags/for.js
index 475bde4be..32f6298ce 100644
--- a/tags/for.js
+++ b/tags/for.js
@@ -1,7 +1,7 @@
var Liquid = require('..');
var lexical = Liquid.lexical;
var re = new RegExp(`^(${lexical.identifier.source})\\s+in\\s+` +
- `(${lexical.variableOrRange.source})` +
+ `(${lexical.value.source})` +
`(?:\\s+${lexical.hash.source})*` +
`(?:\\s+(reversed))?$`);
diff --git a/tags/tablerow.js b/tags/tablerow.js
index 1204559bf..bae0b6cec 100644
--- a/tags/tablerow.js
+++ b/tags/tablerow.js
@@ -1,7 +1,7 @@
var Liquid = require('..');
var lexical = Liquid.lexical;
var re = new RegExp(`^(${lexical.identifier.source})\\s+in\\s+` +
- `(${lexical.variableOrRange.source})` +
+ `(${lexical.value.source})` +
`(?:\\s+${lexical.hash.source})*$`);
module.exports = function(liquid) {
@@ -14,11 +14,9 @@ module.exports = function(liquid) {
this.collection = match[2];
this.templates = [];
- this.elseTemplates = [];
var p, stream = liquid.parseStream(remainTokens)
.onStart(x => p = this.templates)
- .onTag('else', token => p = this.elseTemplates)
.onTag('endtablerow', token => stream.stop())
.onTemplate(tpl => p.push(tpl))
.onEnd(x => {
@@ -29,10 +27,7 @@ module.exports = function(liquid) {
},
render: function(scope, hash) {
- var collection = Liquid.evalExp(this.collection, scope);
- if (Liquid.isFalsy(collection)) {
- return liquid.renderTemplates(this.elseTemplates, scope);
- }
+ var collection = Liquid.evalExp(this.collection, scope) || [];
var html = '',
ctx = {},
@@ -40,12 +35,12 @@ module.exports = function(liquid) {
var offset = hash.offset || 0;
var limit = (hash.limit === undefined) ? collection.length : hash.limit;
- var cols = hash.cols;
+ var cols = hash.cols, row, col;
if (!cols) throw new Error(`illegal cols: ${cols}`);
collection.slice(offset, offset + limit).some((item, i) => {
- var row = Math.floor(i / cols) + 1,
- col = (i % cols) + 1;
+ row = Math.floor(i / cols) + 1;
+ col = (i % cols) + 1;
if(col === 1){
if(row !== 1){
html += '';
@@ -60,7 +55,8 @@ module.exports = function(liquid) {
html += '';
scope.pop(ctx);
});
- html += '
';
+ if(row > 0) html += '';
+ html += '';
return html;
}
});
diff --git a/template.js b/template.js
index 3d0611fc9..05574ef9d 100644
--- a/template.js
+++ b/template.js
@@ -2,7 +2,7 @@ const lexical = require('./lexical.js');
const error = require('./error.js');
const ParseError = require('./error.js').ParseError;
-module.exports = function(Tag) {
+module.exports = function(Tag, Filter) {
var stream = {
init: function(tokens) {
@@ -24,17 +24,11 @@ module.exports = function(Tag) {
start: function() {
this.trigger('start');
while (!this.stopRequested && (token = this.tokens.shift())) {
- var template;
- if (token.type == 'tag') {
- if (this.trigger(`tag:${token.name}`, token)) continue;
- if (token.name === 'continue' || token.name === 'break') {
- template = token;
- } else {
- template = parseTag(token, this.tokens);
- }
- } else {
- template = token;
+ if (token.type == 'tag' &&
+ this.trigger(`tag:${token.name}`, token)) {
+ continue;
}
+ var template = parseToken(token, this.tokens);
this.trigger('template', template);
}
if (!this.stopRequested) this.trigger('end');
@@ -59,32 +53,58 @@ module.exports = function(Tag) {
};
function parse(tokens) {
- var templates = [];
- var token;
+ var token, templates = [];
while (token = tokens.shift()) {
- if (token.type === 'tag') {
- var tagInstance = parseTag(token, tokens);
- templates.push(tagInstance);
- } else templates.push(token);
+ templates.push(parseToken(token, tokens));
}
return templates;
}
- function parseTag(token, tokens) {
+ function parseToken(token, tokens) {
try {
- return Tag.construct(token).parse(tokens);
+ switch (token.type) {
+ case 'tag':
+ return parseTag(token, tokens);
+ case 'output':
+ return parseOutput(token.value);
+ case 'html':
+ return token;
+ }
} catch (e) {
- throw new ParseError(e.message,
- token.input, token.line, e.stack);
+ throw new ParseError(e.message, token.input, token.line, e.stack);
}
}
+ function parseTag(token, tokens) {
+ if (token.name === 'continue' || token.name === 'break') return token;
+ return Tag.construct(token).parse(tokens);
+ }
+
+ function parseOutput(str) {
+ var match = lexical.value.exec(str);
+ if(!match) throw new Error(`illegal output string: ${str}`);
+
+ var initial = match[0];
+ str = str.substr(match.index + match[0].length);
+
+ var filters = [];
+ while(match = lexical.filter.exec(str)){
+ filters.push([match[0].trim()]);
+ }
+
+ return {
+ type: 'output',
+ initial: initial,
+ filters: filters.map(str => Filter.construct(str))
+ };
+ }
+
function parseStream(tokens) {
var s = Object.create(stream);
return s.init(tokens);
}
return {
- parse, parseTag, parseStream
+ parse, parseTag, parseStream, parseOutput
};
};
diff --git a/test/error.js b/test/error.js
index 1baa667fb..92f7e376a 100644
--- a/test/error.js
+++ b/test/error.js
@@ -38,6 +38,16 @@ describe('error', function() {
});
});
+ it('should throw ParseError when filter not exist', function() {
+ test(function(){
+ liquid.render('{{ a | xz }}', {});
+ }, function(err){
+ expect(err.name).to.equal('ParseError');
+ expect(err.message).to.equal('filter "xz" not found');
+ expect(err.input).to.equal('{{ a | xz }}');
+ expect(err.line).to.equal(1);
+ });
+ });
it('should throw ParseError when tag not exist', function() {
test(function(){
liquid.render('{% a %}', {});
diff --git a/test/filter.js b/test/filter.js
index b8fbaf296..2891fedfc 100644
--- a/test/filter.js
+++ b/test/filter.js
@@ -17,12 +17,30 @@ describe('filter', function() {
it('should throw when not registered', function() {
expect(function() {
filter.construct('foo');
- }).to.throw(/filter foo not found/);
+ }).to.throw(/filter "foo" not found/);
+ });
+
+ it('should parse argument syntax', function(){
+ filter.register('foo', x => x);
+ var f = filter.construct('foo: a, "b"');
+
+ expect(f.name).to.equal('foo');
+ expect(f.args).to.deep.equal(['a', '"b"']);
});
it('should register a simple filter', function(){
- filter.register('foo', x => x.toUpperCase());
- expect(filter.construct('foo').render('foo', scope)).to.equal('FOO');
+ filter.register('upcase', x => x.toUpperCase());
+ expect(filter.construct('upcase').render('foo', scope)).to.equal('FOO');
+ });
+
+ it('should register a argumented filter', function(){
+ filter.register('add', (a, b) => a + b);
+ expect(filter.construct('add: 2').render(3, scope)).to.equal(5);
+ });
+
+ it('should register a multi-argumented filter', function(){
+ filter.register('add', (a, b, c) => a + b + c);
+ expect(filter.construct('add: 2, "c"').render(3, scope)).to.equal("5c");
});
it('should call filter with corrct arguments', function(){
diff --git a/test/filters.js b/test/filters.js
index ff6c22b58..8dacb41fa 100644
--- a/test/filters.js
+++ b/test/filters.js
@@ -1,7 +1,8 @@
const chai = require("chai");
const expect = chai.expect;
-var liquid = require('..')(), ctx;
+var liquid = require('..')(),
+ ctx;
function test(src, dst) {
ctx = {
@@ -10,16 +11,21 @@ function test(src, dst) {
arr: [-2, 'a'],
obj: {
foo: 'bar'
- }
+ },
+ posts: [{
+ category: 'foo'
+ }, {
+ category: 'bar'
+ }]
};
expect(liquid.render(src, ctx)).to.equal(dst);
}
describe('filters', function() {
- it('should output object', function(){
+ it('should output object', function() {
test('{{obj}}', '{"foo":"bar"}');
});
- it('should output array', function(){
+ it('should output array', function() {
test('{{arr}}', '[-2,"a"]');
});
it('should support abs', function() {
@@ -67,9 +73,206 @@ describe('filters', function() {
'Have you read 'James & the Giant Peach'?');
test('{{ "Tetsuro Takara" | escape }}', 'Tetsuro Takara');
});
+
it('should support escape_once', function() {
test('{{ "1 < 2 & 3" | escape_once }}', '1 < 2 & 3');
test('{{ "1 < 2 & 3" | escape_once }}', '1 < 2 & 3');
});
-});
+ it('should support split/first', function() {
+ src = '{% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}' +
+ '{{ my_array | first }}';
+ test(src, 'apples');
+ });
+
+ it('should support floor', function() {
+ test('{{ 1.2 | floor }}', '1');
+ test('{{ 2.0 | floor }}', '2');
+ test('{{ 183.357 | floor }}', '183');
+ test('{{ "3.5" | floor }}', '3');
+ });
+
+ it('should support join', function() {
+ src = '{% assign beatles = "John, Paul, George, Ringo" | split: ", " %}' +
+ '{{ beatles | join: " and " }}';
+ test(src, 'John and Paul and George and Ringo');
+ });
+
+ it('should support split/last', function() {
+ src = '{% assign my_array = "zebra, octopus, giraffe, tiger" | split: ", " %}' +
+ '{{ my_array|last }}';
+ test(src, 'tiger');
+ });
+
+ it('should support lstrip', function() {
+ src = '{{ " So much room for activities! " | lstrip }}';
+ test(src, 'So much room for activities! ');
+ });
+
+ it('should support map', function() {
+ test('{{posts | map: "category"}}', '["foo","bar"]');
+ });
+
+ it('should support minus', function() {
+ test('{{ 4 | minus: 2 }}', '2');
+ test('{{ 16 | minus: 4 }}', '12');
+ test('{{ 183.357 | minus: 12 }}', '171.357');
+ });
+
+ it('should support modulo', function() {
+ test('{{ 3 | modulo: 2 }}', '1');
+ test('{{ 24 | modulo: 7 }}', '3');
+ test('{{ 183.357 | modulo: 12 }}', '3.357');
+ });
+
+ it('should support string_with_newlines', function() {
+ src = '{% capture string_with_newlines %}\n' +
+ 'Hello\n' +
+ 'there\n' +
+ '{% endcapture %}' +
+ '{{ string_with_newlines | newline_to_br }}';
+ dst = '
' +
+ 'Hello
' +
+ 'there
';
+ test(src, dst);
+ });
+
+ it('should support plus', function() {
+ test('{{ 4 | plus: 2 }}', '6');
+ test('{{ 16 | plus: 4 }}', '20');
+ test('{{ 183.357 | plus: 12 }}', '195.357');
+ });
+
+ it('should support prepend', function() {
+ test('{% assign url = "liquidmarkup.com" %}' +
+ '{{ "/index.html" | prepend: url }}',
+ 'liquidmarkup.com/index.html');
+ });
+
+ it('should support remove', function() {
+ test('{{ "I strained to see the train through the rain" | remove: "rain" }}',
+ 'I sted to see the t through the ');
+ });
+
+ it('should support remove_first', function() {
+ test('{{ "I strained to see the train through the rain" | remove_first: "rain" }}',
+ 'I sted to see the train through the rain');
+ });
+
+ it('should support replace', function() {
+ test('{{ "Take my protein pills and put my helmet on" | replace: "my", "your" }}',
+ 'Take your protein pills and put your helmet on');
+ });
+
+ it('should support replace_first', function() {
+ test('{% assign my_string = "Take my protein pills and put my helmet on" %}\n' +
+ '{{ my_string | replace_first: "my", "your" }}',
+ '\nTake your protein pills and put my helmet on');
+ });
+
+ it('should support reverse', function() {
+ test('{{ "Ground control to Major Tom." | split: "" | reverse | join: "" }}',
+ '.moT rojaM ot lortnoc dnuorG');
+ });
+
+ it('should support round', function() {
+ test('{{1.2|round}}', '1');
+ test('{{2.7|round}}', '3');
+ test('{{183.357|round: 2}}', '183.36');
+ });
+
+ it('should support rstrip', function() {
+ test('{{ " So much room for activities! " | rstrip }}',
+ ' So much room for activities!');
+ });
+
+ it('should support size', function() {
+ test('{{ "Ground control to Major Tom." | size }}', '28');
+ test('{% assign my_array = "apples, oranges, peaches, plums"' +
+ ' | split: ", " %}{{ my_array | size }}',
+ '4');
+ });
+
+ it('should support slice', function() {
+ test('{{ "Liquid" | slice: 0 }}', 'L');
+ test('{{ "Liquid" | slice: 2 }}', 'q');
+ test('{{ "Liquid" | slice: 2, 5 }}', 'quid');
+ test('{{ "Liquid" | slice: -3, 2 }}', 'ui');
+ });
+
+ it('should support sort', function() {
+ test('{% assign my_array = "zebra, octopus, giraffe, Sally Snake"' +
+ ' | split: ", " %}' +
+ '{{ my_array | sort | join: ", " }}',
+ 'Sally Snake, giraffe, octopus, zebra');
+ });
+
+ it('should support split', function() {
+ test('{% assign beatles = "John, Paul, George, Ringo" | split: ", " %}' +
+ '{% for member in beatles %}' +
+ '{{ member }} ' +
+ '{% endfor %}',
+ 'John Paul George Ringo ');
+ });
+
+ it('should support strip', function() {
+ test('{{ " So much room for activities! " | strip }}',
+ 'So much room for activities!');
+ });
+
+ it('should support strip_tml', function() {
+ test('{{ "Have you read Ulysses?" | strip_html }}',
+ 'Have you read Ulysses?');
+ test('{{"
< p >
' }], scope)).to.equal('
'); @@ -37,14 +47,16 @@ describe('render', function() { var time = sinon.spy(); filter.register('date', date); filter.register('time', time); - evalFilter('foo.bar[0] | date: "b" | time:2', scope); + var tpl = Template.parseOutput('foo.bar[0] | date: "b" | time:2'); + render.evalOutput(tpl, scope); expect(date).to.have.been.calledWith('a', 'b'); expect(time).to.have.been.calledWith('y', 2); }); - it('should eval filter', function() { + it('should eval output', function() { filter.register('date', (l, r) => l + r); filter.register('time', (l, r) => l + 3 * r); - expect(evalFilter('foo.bar[0] | date: "b" | time:2', scope)).to.equal('ab6'); + var tpl = Template.parseOutput('foo.bar[0] | date: "b" | time:2'); + expect(render.evalOutput(tpl, scope)).to.equal('ab6'); }); }); diff --git a/test/tags.js b/test/tags.js index 2ecfc7cbd..eec807fbd 100644 --- a/test/tags.js +++ b/test/tags.js @@ -28,7 +28,9 @@ describe('tags', function() { }; }); it('should support assign', function() { - test('{% assign foo="bar"%}{{foo}}', 'bar'); + test('{% assign foo="bar" %}{{foo}}', 'bar'); + test('{% assign foo=(1..3) %}{{foo}}', '[1,2,3]'); + test('{% assign foo="a b" | capitalize | split: " " | first %}{{foo}}', 'A'); }); it('should support case', function() { testThrow('{% case "foo"%}', /{% case "foo"%} not closed/); @@ -146,6 +148,12 @@ describe('tags', function() { test(src, dst); }); + it('should support empty tablerow', function() { + src = '{% tablerow i in (1..0) cols:2 %}{{ i }}{% endtablerow %}'; + dst = '