fix existing tags

new tags: for,cycle
This commit is contained in:
harttle
2016-06-17 08:39:23 +08:00
parent 8b9e9f759a
commit 0b7da2abf7
24 changed files with 432 additions and 195 deletions
+9 -9
View File
@@ -25,18 +25,18 @@ Documentation: <https://shopify.github.io/liquid/basics/introduction/#tags>
- [x] if [Document](https://shopify.github.io/liquid/tags/control-flow/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/if.js) [Test][tt]
- [x] unless [Document](https://shopify.github.io/liquid/tags/control-flow/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/unless.js) [Test][tt]
- [x] elsif [Document/else](https://shopify.github.io/liquid/tags/control-flow/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/if.js) [Test][tt]
- [ ] for [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/for.js) [Test][tt]
- [ ] break [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
- [ ] continue [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
- [ ] for [Document: limit,offset,range,reversed)](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
- [ ] cycle [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
- [ ] cycle [Document: group)](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
- [x] for [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/for.js) [Test][tt]
- [x] break [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/for.js) [Test][tt]
- [x] continue [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/for.js) [Test][tt]
- [x] for [Document: limit,offset,range,reversed)](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/for.js) [Test][tt]
- [x] cycle [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/cycle.js) [Test][tt]
- [x] cycle [Document: group)](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/cycle.js) [Test][tt]
- [ ] tablerow [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
- [ ] tablerow [Document: cols,limit,offset,range](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
- [x] assign [Document](https://shopify.github.io/liquid/tags/variable/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/assign.js) [Test][tt]
- [x] capture [Document](https://shopify.github.io/liquid/tags/variable/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
- [x] increment [Document](https://shopify.github.io/liquid/tags/variable/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
- [x] decrement [Document](https://shopify.github.io/liquid/tags/variable/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
- [x] capture [Document](https://shopify.github.io/liquid/tags/variable/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/capture.js) [Test][tt]
- [x] increment [Document](https://shopify.github.io/liquid/tags/variable/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/increment.js) [Test][tt]
- [x] decrement [Document](https://shopify.github.io/liquid/tags/variable/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/decrement.js) [Test][tt]
## Filters
+54
View File
@@ -0,0 +1,54 @@
const syntax = require('./syntax.js');
const Exp = require('./expression.js');
const lexical = require('./lexical.js');
const _ = require('lodash');
function evalExp(exp, scope) {
if (!scope) throw new Error('unable to evalExp: scope undefined');
var operatorREs = lexical.operators;
for (var i = 0; i < operatorREs.length; i++) {
var operatorRE = operatorREs[i];
var expRE = new RegExp(`^(${lexical.quoteBalanced.source})(${operatorRE.source})(${lexical.quoteBalanced.source})$`);
var match = exp.match(expRE);
if (match) {
var l = evalExp(match[1], scope);
var op = syntax.operators[match[2].trim()];
var r = evalExp(match[3], scope);
return op(l, r);
}
}
if (match = exp.match(lexical.rangeLine)) {
var low = evalValue(match[1], scope),
high = evalValue(match[2], scope) + 1;
return _.range(low, high);
}
return evalValue(exp, scope);
}
function evalValue(str, scope) {
str = str && str.trim();
if (!str) return undefined;
if (lexical.isLiteral(str)) {
var a = lexical.parseLiteral(str);
return lexical.parseLiteral(str);
}
if (lexical.isVariable(str)) {
return scope.get(str);
}
}
function isTruthy(val) {
if (val instanceof Array) return !!val.length;
return !!val;
}
function isFalsy(val) {
return !isTruthy(val);
}
module.exports = {
evalExp, evalValue, isTruthy, isFalsy
};
+2 -1
View File
@@ -1,9 +1,10 @@
const lexical = require('./lexical.js');
const _ = require('lodash');
const Exp = require('./expression.js');
var _filterInstance = {
render: function(output, scope) {
var args = this.args.map(arg => scope.get(arg));
var args = this.args.map(arg => Exp.evalValue(arg, scope));
args.unshift(output);
return this.filter.apply(null, args);
}
+7 -3
View File
@@ -8,6 +8,7 @@ const Tag = require('./tag.js');
const Filter = require('./filter.js');
const error = require('./error.js');
const Template = require('./template');
const Expression = require('./expression.js');
const tagsPath = path.join(__dirname, "tags");
@@ -33,14 +34,13 @@ function factory(){
engine.parseStream = template.parseStream;
var renderer = Render(engine.filter, engine.tag);
engine.evaluate = renderer.evalExp;
engine.evalExp = renderer.evalExp;
engine.evalFilter = renderer.evalFilter;
engine.renderTemplates = renderer.renderTemplates;
engine.render = function(html, ctx) {
var tokens = engine.tokenize(html);
var templates = engine.parse(tokens);
engine.register = {};
return engine.renderTemplates(templates, scope.factory(ctx));
},
@@ -55,7 +55,11 @@ function factory(){
factory.lexical = lexical;
factory.error = error;
factory.isTruthy = Render.isTruthy;
factory.isTruthy = Expression.isTruthy;
factory.isFalsy = Expression.isFalsy;
factory.stringify = Render.stringify;
factory.evalExp = Expression.evalExp;
factory.evalValue = Expression.evalValue;
module.exports = factory;
+18 -13
View File
@@ -5,15 +5,18 @@ var doubleQuoted = /"[^"]*"/;
var number = /-?\d+\.?\d*|\.?\d+/;
var bool = /true|false/i;
var range = /\((\d+)\.\.(\d+)\)/;
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}|${range.source}|${bool.source}|${number.source}`, 'i');
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 hash = new RegExp(`(${identifier.source})\\s*:\\s*(${value.source})`, 'g');
var range = new RegExp(`\\((?:${value.source})\\.\\.(?:${value.source})\\)`);
var rangeCapture = new RegExp(`\\((${value.source})\\.\\.(${value.source})\\)`);
var variableOrRange = new RegExp(`${variable.source}|${range.source}`);
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}|[^'"])*`);
@@ -23,7 +26,7 @@ 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(`^(?:${range.source})$`);
var rangeLine = new RegExp(`^(?:${rangeCapture.source})$`);
var filterLine = new RegExp(`^(?:${filter.source})$`);
var operators = [
@@ -33,15 +36,19 @@ var operators = [
];
function isLiteral (str) {
function isLiteral(str) {
return literalLine.test(str);
}
function isVariable (str) {
function isRange(str) {
return rangeLine.test(str);
}
function isVariable(str) {
return variableLine.test(str);
}
function parseLiteral (str) {
function parseLiteral(str) {
var res;
if (res = str.match(numberLine)) {
return Number(str);
@@ -52,15 +59,13 @@ function parseLiteral (str) {
if (res = str.match(quotedLine)) {
return str.slice(1, -1);
}
if (res = str.match(rangeLine)) {
return _.range(res[1], res[2]);
}
}
module.exports = {
quoted, number, bool, range, literal, hash, filter,
quoted, number, bool, literal, filter,
hash, hashCapture,
range, rangeCapture, variableOrRange,
identifier, value, quoteBalanced, operators,
quotedLine, numberLine, boolLine, rangeLine, literalLine, filterLine, tagLine,
isLiteral, isVariable, parseLiteral
isLiteral, isVariable, parseLiteral, isRange
};
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "shopify-liquid",
"version": "1.0.2",
"version": "1.0.3",
"description": "A Shopify Liquid Implementation in Node.js",
"main": "index.js",
"scripts": {
+17 -31
View File
@@ -1,66 +1,52 @@
const lexical = require('./lexical.js');
const syntax = require('./syntax.js');
const error = require('./error.js');
const Exp = require('./expression.js');
function stringify(val) {
if (typeof val === 'string') return val;
return JSON.stringify(val);
}
function isTruthy(val) {
if (val instanceof Array) return !!val.length;
return !!val;
}
function factory(Filter, Tag) {
function renderTemplates(templates, scope) {
var html = '';
var template;
while (template = templates.shift()) {
templates.some(template => {
if (scope.get('forloop.skip')) return true;
if (template.type === 'tag') {
html += template.render(scope);
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);
}
}
});
return html;
}
function evalExp(exp, scope) {
if (!scope) error('unable to evalExp: scope undefined');
var operatorREs = lexical.operators;
for (var i = 0; i < operatorREs.length; i++) {
var operatorRE = operatorREs[i];
var expRE = new RegExp(`^(${lexical.quoteBalanced.source})(${operatorRE.source})(${lexical.quoteBalanced.source})$`);
var match = exp.match(expRE);
if (match) {
var l = evalExp(match[1], scope);
var op = syntax.operators[match[2].trim()];
var r = evalExp(match[3], scope);
return op(l, r);
}
}
return evalFilter(exp, scope);
}
function evalFilter(str, scope) {
if (!scope) error('unable to evalFilter: scope undefined');
if (!scope) throw new Error('unable to evalFilter: scope undefined');
var filters = str.split('|');
var val = scope.get(filters.shift());
var val = Exp.evalValue(filters.shift(), scope);
return filters
.map(str => Filter.construct(str))
.reduce((v, filter) => filter.render(v, scope), val);
}
return {
renderTemplates, evalFilter, evalExp
renderTemplates, evalFilter
};
};
factory.isTruthy = isTruthy;
factory.stringify = stringify;
module.exports = factory;
+6 -15
View File
@@ -4,27 +4,18 @@ const error = require('./error.js');
var scope = {
get: function(str) {
str = str && str.trim();
if(!str) return '';
if (lexical.isLiteral(str)) {
var a = lexical.parseLiteral(str);
return lexical.parseLiteral(str);
}
if (lexical.isVariable(str)) {
for (var i = this.scopes.length - 1; i >= 0; i--) {
var v = _.get(this.scopes[i], str);
if (v !== undefined) return v;
}
for (var i = this.scopes.length - 1; i >= 0; i--) {
var v = _.get(this.scopes[i], str);
if (v !== undefined) return v;
}
return '';
},
set: function(k, v){
this.scopes[this.scopes.length-1][k] = v;
set: function(k, v) {
_.set(this.scopes[this.scopes.length - 1], k, v);
return this;
},
push: function(ctx) {
if(!ctx) error(`trying to push ${ctx} into scopes`);
if (!ctx) error(`trying to push ${ctx} into scopes`);
return this.scopes.push(ctx);
},
pop: function() {
+11 -7
View File
@@ -1,24 +1,28 @@
const lexical = require('./lexical.js');
const Exp = require('./expression.js');
var _tagInstance = {
render: function(scope) {
render: function(scope, register) {
var reg = register[this.name];
if(!reg) reg = register[this.name] = {}
var obj = hash(this.token.args, scope);
return this.tagImpl.render(scope, obj) || '';
return this.tagImpl.render(scope, obj, reg) || '';
},
parse: function(tokens){
this.tagImpl.parse(this.token, tokens);
if(this.tagImpl.parse){
this.tagImpl.parse(this.token, tokens);
}
return this;
}
};
function hash(markup, scope) {
var obj = {};
lexical.hash.lastIndex = 0;
while (match = lexical.hash.exec(markup)) {
lexical.hashCapture.lastIndex = 0;
while (match = lexical.hashCapture.exec(markup)) {
var k = match[1],
v = match[2];
if (!k) continue;
obj[k] = scope.get(v);
obj[k] = Exp.evalValue(v, scope);
}
return obj;
}
+7 -3
View File
@@ -5,10 +5,14 @@ var re = new RegExp(`(${lexical.identifier.source})\\s*=(.*)`);
module.exports = function(liquid) {
liquid.registerTag('assign', {
render: function(tokens, scope, token, hash) {
parse: function(token){
var match = token.args.match(re);
var k = match[1], v = match[2];
scope.set(k, liquid.evaluate(v, scope));
if(!match) error(`illegal token ${token.raw}`, token);
this.key = match[1];
this.value = match[2];
},
render: function(scope, hash) {
scope.set(this.key, Liquid.evalValue(this.value, scope));
}
});
+34 -23
View File
@@ -1,34 +1,45 @@
var Liquid = require('..');
var lexical = Liquid.lexical;
var caseRE = new RegExp(`^\\s*case\\s+(${lexical.value.source})`);
var whenRE = new RegExp(`^\\s*when\\s+(${lexical.value.source})`);
module.exports = function(liquid) {
liquid.registerTag('case', {
needClose: true,
render: function(tokens, scope, token, hash) {
var match = token.value.match(caseRE);
var cond = liquid.evaluate(match[1], scope);
var partialTokens = [],
matching = false;
for (var i = 0; i < tokens.length; i++) {
var tk = tokens[i];
if (tk.type === 'tag' && tk.name === 'when') {
if (matching) break;
match = tk.value.match(whenRE);
if(!match) continue;
parse: function(tagToken, remainTokens) {
this.cond = tagToken.args;
this.cases = [];
this.elseTemplates = [];
var val = liquid.evaluate(match[1], scope);
if (val === cond) matching = true;
} else if (tk.type === 'tag' && tk.name === 'else') {
if (matching) break;
else matching = true;
} else if (matching) partialTokens.push(tk);
var p = [],
stream = liquid.parseStream(remainTokens)
.onTag('when', token => {
if (!this.cases[token.args]) {
this.cases.push({
val: token.args,
templates: p = []
});
}
})
.onTag('else', token => p = this.elseTemplates)
.onTag('endcase', token => stream.stop())
.onTemplate(tpl => p.push(tpl))
.onEnd(x => {
throw new Error(`tag ${tagToken.raw} not closed`);
});
stream.start();
},
render: function(scope, hash) {
for (var i = 0; i < this.cases.length; i++) {
var branch = this.cases[i];
var val = Liquid.evalExp(branch.val, scope);
var cond = Liquid.evalExp(this.cond, scope);
if (val === cond) {
return liquid.renderTemplates(branch.templates, scope);
}
}
return liquid.renderTokens(partialTokens, scope);
return liquid.renderTemplates(this.elseTemplates, scope);
}
});
};
+43
View File
@@ -0,0 +1,43 @@
var Liquid = require('..');
var lexical = Liquid.lexical;
var groupRE = new RegExp(`^(?:(${lexical.value.source})\\s*:\\s*)?(.*)$`);
var candidatesRE = new RegExp(lexical.value.source, 'g');
module.exports = function(liquid) {
liquid.registerTag('cycle', {
parse: function(tagToken, remainTokens) {
var match = groupRE.exec(tagToken.args);
if(!match) throw new Error(`illegal tag: ${tagToken.raw}`);
this.group = match[1] || '';
var candidates = match[2];
this.candidates = [];
while(match = candidatesRE.exec(candidates)){
this.candidates.push(match[0]);
}
if (!this.candidates.length){
throw new Error(`illegal tag: ${tagToken.raw}`);
}
},
render: function(scope, hash, register) {
var fingerprint = Liquid.evalValue(this.group, scope) + ':' +
this.candidates.join(',');
var idx = register[fingerprint];
if(idx === undefined){
idx = register[fingerprint] = 0;
}
var candidate = this.candidates[idx];
idx = (idx + 1) % this.candidates.length;
register[fingerprint] = idx;
return Liquid.evalValue(candidate, scope);
}
});
};
+8 -5
View File
@@ -5,12 +5,15 @@ const error = Liquid.error;
module.exports = function(liquid) {
liquid.registerTag('decrement', {
render: function(tokens, scope, token, hash) {
parse: function(token) {
var match = token.args.match(lexical.identifier);
if(!match) error(`illegal identifier ${token.args}`, token);
var k = match[0], v = scope.get(k);
if(typeof v !== 'number') v = 0;
scope.set(k, v-1);
if (!match) error(`illegal identifier ${token.args}`, token);
this.variable = match[0];
},
render: function(scope, hash) {
var v = scope.get(this.variable);
if (typeof v !== 'number') v = 0;
scope.set(this.variable, v - 1);
}
});
+37 -5
View File
@@ -1,15 +1,19 @@
var Liquid = require('..');
var lexical = Liquid.lexical;
var re = new RegExp(`^(${lexical.identifier.source})\\s+in\\s+(.+)$`);
var re = new RegExp(`^(${lexical.identifier.source})\\s+in\\s+` +
`(${lexical.variableOrRange.source})` +
`(?:\\s+${lexical.hash.source})*` +
`(?:\\s+(reversed))?$`);
module.exports = function(liquid) {
liquid.registerTag('for', {
parse: function(tagToken, remainTokens) {
var match = re.exec(tagToken.args);
if(!match) throw new Error(`illegal tag: ${tagToken.raw}`);
if (!match) throw new Error(`illegal tag: ${tagToken.raw}`);
this.variable = match[1];
this.collection = match[2];
this.reversed = !!match[3];
this.templates = [];
this.elseTemplates = [];
@@ -27,12 +31,40 @@ module.exports = function(liquid) {
},
render: function(scope, hash) {
var collection = liquid.evaluate(this.collection, scope);
if(!(collection instanceof Array) || !collection.length){
var collection = Liquid.evalExp(this.collection, scope);
if (Liquid.isFalsy(collection)) {
return liquid.renderTemplates(this.elseTemplates, scope);
}
liquid.renderTemplates(this.templates, scope);
var html = '',
ctx = {},
length = collection.length;
var offset = hash.offset || 0;
var limit = (hash.limit === undefined) ? collection.length : hash.limit;
var collection = collection.slice(offset, offset + limit);
if(this.reversed) collection.reverse();
collection.some((item, i) => {
ctx[this.variable] = item;
ctx.forloop = {
first: i === 0,
index: i + 1,
index0: i,
last: i === length - 1,
length: length,
rindex: length - i,
rindex0: length - i - 1,
stop: false,
skip: false
};
scope.push(ctx);
html += liquid.renderTemplates(this.templates, scope);
var breakloop = scope.get('forloop.stop');
scope.pop(ctx);
if (breakloop) return true;
});
return html;
}
});
};
+2 -1
View File
@@ -5,6 +5,7 @@ module.exports = function(liquid) {
liquid.registerTag('if', {
parse: function(tagToken, remainTokens) {
this.branches = [];
this.elseTemplates = [];
@@ -34,7 +35,7 @@ module.exports = function(liquid) {
render: function(scope, hash) {
for (var i = 0; i < this.branches.length; i++) {
var branch = this.branches[i];
var cond = liquid.evaluate(branch.cond, scope);
var cond = Liquid.evalExp(branch.cond, scope);
if (Liquid.isTruthy(cond)) {
return liquid.renderTemplates(branch.templates, scope);
}
+8 -5
View File
@@ -5,12 +5,15 @@ const error = Liquid.error;
module.exports = function(liquid) {
liquid.registerTag('increment', {
render: function(tokens, scope, token, hash) {
parse: function(token) {
var match = token.args.match(lexical.identifier);
if(!match) error(`illegal identifier ${token.args}`, token);
var k = match[0], v = scope.get(k);
if(typeof v !== 'number') v = 0;
scope.set(k, v+1);
if (!match) error(`illegal identifier ${token.args}`, token);
this.variable = match[0];
},
render: function(scope, hash) {
var v = scope.get(this.variable);
if (typeof v !== 'number') v = 0;
scope.set(this.variable, v + 1);
}
});
+1 -1
View File
@@ -34,7 +34,7 @@ module.exports = function(liquid) {
render: function(scope, hash) {
for (var i = 0; i < this.branches.length; i++) {
var branch = this.branches[i];
var cond = liquid.evaluate(branch.cond, scope);
var cond = Liquid.evalExp(branch.cond, scope);
cond = Liquid.isTruthy(cond);
if (i === 0) cond = !cond;
if (cond) {
+6 -1
View File
@@ -26,7 +26,12 @@ module.exports = function(Tag) {
var template;
if (token.type == 'tag') {
if (this.trigger(`tag:${token.name}`, token)) continue;
template = parseTag(token, this.tokens);
if (token.name === 'continue' || token.name === 'break'){
template = token;
}
else{
template = parseTag(token, this.tokens);
}
} else {
template = token;
}
+60
View File
@@ -0,0 +1,60 @@
const chai = require("chai");
const sinonChai = require("sinon-chai");
const sinon = require("sinon");
const expect = chai.expect;
chai.use(sinonChai);
var expression = require('../expression.js');
var Scope = require('../scope.js');
var evalExp = expression.evalExp;
var evalValue = expression.evalValue;
describe('expression', function() {
var scope;
beforeEach(function() {
scope = Scope.factory({
one: 1,
two: 2,
x: 'XXX'
});
});
it('should eval literals', function() {
expect(evalValue('2.3')).to.equal(2.3);
expect(evalValue('"foo"')).to.equal("foo");
});
it('should throw on illegal expression', function() {
expect(function() {
evalExp('1 contains "x"', scope);
}).to.throw();
});
it('should eval variables', function() {
expect(evalValue('23', scope)).to.equal(23);
expect(evalValue('one', scope)).to.equal(1);
expect(evalValue('x', scope)).to.equal('XXX');
});
it('should eval simple expression', function() {
expect(evalExp('1<2', scope)).to.equal(true);
expect(evalExp('2<=2', scope)).to.equal(true);
expect(evalExp('one<=two', scope)).to.equal(true);
expect(evalExp('x contains "x"', scope)).to.equal(false);
expect(evalExp('x contains "X"', scope)).to.equal(true);
expect(evalExp('x contains z', scope)).to.equal(true);
expect(evalExp('"<=" == "<="', scope)).to.equal(true);
});
it('should eval complex expression', function() {
expect(evalExp('1<2 and x contains "x"', scope)).to.equal(false);
expect(evalExp('1<2 or x contains "x"', scope)).to.equal(true);
});
it("should eval range expression", function() {
expect(evalExp('(2..4)', scope)).to.deep.equal([2,3,4]);
expect(evalExp('(two..4)', scope)).to.deep.equal([2,3,4]);
});
});
+6 -11
View File
@@ -17,6 +17,12 @@ describe('identifier', function() {
identifier.isLiteral('23').should.equal(true);
});
it("should test range literal", function() {
identifier.isRange("(12..32)").should.equal(true);
identifier.isRange("(12..foo)").should.equal(true);
identifier.isRange("(foo.bar..foo)").should.equal(true);
});
it('should test string literal', function() {
identifier.isLiteral('""').should.equal(true);
identifier.isLiteral('"a\'b"').should.equal(true);
@@ -24,10 +30,6 @@ describe('identifier', function() {
identifier.isLiteral("'a bcd'").should.equal(true);
});
it("should test range literal", function() {
identifier.isLiteral("(12..32)").should.equal(true);
});
it("should test variable", function() {
identifier.isVariable("foo").should.equal(true);
identifier.isVariable("foo.bar.foo").should.equal(true);
@@ -65,11 +67,4 @@ describe('identifier', function() {
identifier.parseLiteral('"ab\'c"').should.equal("ab\'c");
});
it("should parse range literal", function() {
var arr = identifier.parseLiteral("(12..32)");
arr.length.should.equal(20);
arr[0].should.equal(12);
arr[1].should.equal(13);
arr[19].should.equal(31);
});
});
-19
View File
@@ -10,7 +10,6 @@ var Scope = require('../scope.js');
var filter = require('../filter')();
var Render = require('../render.js')(filter, tag);
var render = Render.renderTemplates;
var evalExp = Render.evalExp;
var evalFilter = Render.evalFilter;
describe('render', function() {
@@ -18,9 +17,6 @@ describe('render', function() {
beforeEach(function() {
scope = Scope.factory({
one: 1,
two: 2,
x: 'XXX',
foo: {
bar: ['a', 2]
}
@@ -51,19 +47,4 @@ describe('render', function() {
filter.register('time', (l, r) => l + 3 * r);
expect(evalFilter('foo.bar[0] | date: "b" | time:2', scope)).to.equal('ab6');
});
it('should eval expression', function() {
expect(evalExp('1<2', scope)).to.equal(true);
expect(evalExp('2<=2', scope)).to.equal(true);
expect(evalExp('one<=two', scope)).to.equal(true);
expect(function() {
evalExp('1 contains "x"', scope);
}).to.throw();
expect(evalExp('x contains "x"', scope)).to.equal(false);
expect(evalExp('x contains "X"', scope)).to.equal(true);
expect(evalExp('x contains z', scope)).to.equal(true);
expect(evalExp('1<2 and x contains "x"', scope)).to.equal(false);
expect(evalExp('1<2 or x contains "x"', scope)).to.equal(true);
expect(evalExp('"<=" == "<="', scope)).to.equal(true);
});
});
+5 -6
View File
@@ -13,12 +13,6 @@ describe('scope', function() {
});
});
it('should parse literal', function() {
scope.get('2.3').should.equal(2.3);
scope.get('(2..4)').should.deep.equal([2,3]);
scope.get('"foo"').should.equal("foo");
});
it('should get property', function() {
scope.get('foo').should.equal('bar');
});
@@ -28,6 +22,11 @@ describe('scope', function() {
scope.get('foo').should.equal('FOO');
});
it('should set child property', function() {
scope.set('oo.bar', 'FOO');
scope.get('oo.bar').should.equal('FOO');
});
it('should get desendent property', function() {
scope.get('bar[0]').should.equal('a');
scope.get('bar[1].b').should.deep.equal([1, 2]);
+2 -2
View File
@@ -52,7 +52,7 @@ describe('tag', function() {
type: 'tag',
value: 'foo',
name: 'foo'
}).render(scope);
}).render(scope, {});
expect(spy).to.have.been.called;
});
@@ -68,7 +68,7 @@ describe('tag', function() {
name: 'foo',
args: 'aa:foo bb: arr[0] cc: 2.3'
};
tag.construct(token).render(scope);
tag.construct(token).render(scope, {});
expect(spy).to.have.been.calledWithMatch(scope, {
aa: 'bar',
bb: 2,
+88 -33
View File
@@ -2,7 +2,7 @@ const chai = require("chai");
const expect = chai.expect;
var liquid = require('..')(),
ctx;
ctx, src, dst;
function test(src, dst) {
expect(liquid.render(src, ctx)).to.equal(dst);
@@ -22,30 +22,29 @@ describe('tags', function() {
leq: '<=',
empty: '',
foo: 'bar',
arr: [-2, 'a', {
foo: 'bar'
}],
arr: [-2, 'a'],
alpha: ['a', 'b', 'c'],
emptyArray: []
};
});
//it('should support assign', function() {
//test('{% assign foo="bar"%}{{foo}}', 'bar');
//});
//it('should support case', function() {
//testThrow('{% case "foo"%}', /case "foo" not closed/);
//test('{% case "foo"%}' +
//'{% when "foo" %}foo{% when "bar"%}bar' +
//'{%endcase%}', 'foo');
//test('{% case empty %}' +
//'{% when "foo" %}foo{% when ""%}bar' +
//'{%endcase%}', 'bar');
//test('{% case false %}' +
//'{% when "foo" %}foo{% when ""%}bar' +
//'{%endcase%}', '');
//test('{% case "a" %}' +
//'{% when "b" %}b{% when "c"%}c{%else %}d' +
//'{%endcase%}', 'd');
//});
it('should support assign', function() {
test('{% assign foo="bar"%}{{foo}}', 'bar');
});
it('should support case', function() {
testThrow('{% case "foo"%}', /{% case "foo"%} not closed/);
test('{% case "foo"%}' +
'{% when "foo" %}foo{% when "bar"%}bar' +
'{%endcase%}', 'foo');
test('{% case empty %}' +
'{% when "foo" %}foo{% when ""%}bar' +
'{%endcase%}', 'bar');
test('{% case false %}' +
'{% when "foo" %}foo{% when ""%}bar' +
'{%endcase%}', '');
test('{% case "a" %}' +
'{% when "b" %}b{% when "c"%}c{%else %}d' +
'{%endcase%}', 'd');
});
it('should support if', function() {
testThrow('{% if false%}yes', /tag {% if false%} not closed/);
@@ -68,17 +67,73 @@ describe('tags', function() {
testThrow('{% capture = %}{%endcapture%}', /= not valid identifier/);
});
//it('should support for', function() {
//test('{%for i in arr%}{{"a" | capitalize}}{%endcapture%}{{f}}', 'A');
//});
it('should support for', function() {
test('{%for c in alpha%}{{c}}{%endfor%}', 'abc');
});
//it('should support increment', function() {
//test('{% increment foo %}{%increment foo%}{{foo}}', '2');
//test('{% increment one %}{{one}}', '2');
//});
it('should support for with forloop', function() {
src = '{%for c in alpha%}' +
'{{forloop.first}}.{{forloop.index}}.{{forloop.index0}}.' +
'{{forloop.last}}.{{forloop.length}}.' +
'{{forloop.rindex}}.{{forloop.rindex0}}' +
'{{c}}\n' +
'{%endfor%}';
dst = 'true.1.0.false.3.3.2a\n' +
'false.2.1.false.3.2.1b\n' +
'false.3.2.true.3.1.0c\n';
test(src, dst);
});
//it('should support decrement', function() {
//test('{% decrement foo %}{%decrement foo%}{{foo}}', '-2');
//test('{% decrement one %}{{one}}', '0');
//});
it('should support for with continue and break', function() {
src = '{% for i in (1..5) %}' +
'{% if i == 4 %}{% continue %}' +
'{% else %}{{ i }}' +
'{% endif %}' +
'{% endfor %}';
test(src, '1235');
src = '{% for i in (one..5) %}' +
'{% if i == 4 %}{% break %}{% endif %}' +
'{{ i }}' +
'{% endfor %}';
test(src, '123');
});
it('should support for with limit and offset', function() {
src = '{% for i in (1..5) limit:2 %}{{ i }}{% endfor %}';
test(src, '12');
src = '{% for i in (1..10) limit:2 offset:5%}{{ i }}{% endfor %}';
test(src, '67');
});
it('should support for reversed', function() {
src = '{% for i in (1..5) limit:2 reversed %}{{ i }}{% endfor %}';
test(src, '21');
});
it('should support cycle', function() {
src = "{% cycle '1', '2', '3' %}";
test(src + src + src + src, '1231');
});
it('should support cycle in for block', function() {
src = '{% for i in (1..5) %}{% cycle one, "e"%}{% endfor %}';
test(src, '1e1e1');
});
it('should support cycle group', function() {
src = "{% cycle one: '1', '2', '3'%}" +
"{% cycle 1: '1', '2', '3'%}" +
"{% cycle 2: '1', '2', '3'%}";
test(src, '121');
});
it('should support increment', function() {
test('{% increment foo %}{%increment foo%}{{foo}}', '2');
test('{% increment one %}{{one}}', '2');
});
it('should support decrement', function() {
test('{% decrement foo %}{%decrement foo%}{{foo}}', '-2');
test('{% decrement one %}{{one}}', '0');
});
});