diff --git a/filters.js b/filters.js index 727459a76..e95f7bae8 100644 --- a/filters.js +++ b/filters.js @@ -1,93 +1,82 @@ const strftime = require('./src/strftime.js'); const _ = require('./src/util/underscore.js'); -module.exports = function(liquid) { - liquid.registerFilter('abs', v => Math.abs(v)); - liquid.registerFilter('append', (v, arg) => v + arg); - liquid.registerFilter('capitalize', str => - stringify(str).charAt(0).toUpperCase() + str.slice(1)); - liquid.registerFilter('ceil', v => Math.ceil(v)); +var escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', +}; +var unescapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + ''': "'", +}; - liquid.registerFilter('date', (v, arg) => { +var filters = { + 'abs': v => Math.abs(v), + 'append': (v, arg) => v + arg, + 'capitalize': str => stringify(str).charAt(0).toUpperCase() + str.slice(1), + 'ceil': v => Math.ceil(v), + 'date': (v, arg) => { if (v === 'now') v = new Date(); return strftime(v, arg); - }); + }, + 'default': (v, arg) => arg || v, + 'divided_by': (v, arg) => Math.floor(v / arg), + 'downcase': v => v.toLowerCase(), + 'escape': escape, - liquid.registerFilter('default', (v, arg) => arg || v); - liquid.registerFilter('divided_by', (v, arg) => Math.floor(v / arg)); - liquid.registerFilter('downcase', v => v.toLowerCase()); - - var escapeMap = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - }; - - function escape(str) { - return stringify(str).replace(/&|<|>|"|'/g, m => escapeMap[m]); - } - liquid.registerFilter('escape', escape); - - var unescapeMap = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - ''': "'", - }; - - function unescape(str) { - return stringify(str).replace(/&(amp|lt|gt|#34|#39);/g, m => unescapeMap[m]); - } - liquid.registerFilter('escape_once', str => escape(unescape(str))); - liquid.registerFilter('first', v => v[0]); - liquid.registerFilter('floor', v => Math.floor(v)); - liquid.registerFilter('join', (v, arg) => v.join(arg)); - liquid.registerFilter('last', v => v[v.length - 1]); - liquid.registerFilter('lstrip', v => stringify(v).replace(/^\s+/, '')); - liquid.registerFilter('map', (arr, arg) => arr.map(v => v[arg])); - liquid.registerFilter('minus', bindFixed((v, arg) => v - arg)); - liquid.registerFilter('modulo', bindFixed((v, arg) => v % arg)); - liquid.registerFilter('newline_to_br', v => v.replace(/\n/g, '
')); - 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) => - stringify(v).split(pattern).join(replacement)); - liquid.registerFilter('replace_first', (v, arg1, arg2) => stringify(v).replace(arg1, arg2)); - liquid.registerFilter('reverse', v => v.reverse()); - liquid.registerFilter('round', (v, arg) => { + 'escape_once': str => escape(unescape(str)), + 'first': v => v[0], + 'floor': v => Math.floor(v), + 'join': (v, arg) => v.join(arg), + 'last': v => v[v.length - 1], + 'lstrip': v => stringify(v).replace(/^\s+/, ''), + 'map': (arr, arg) => arr.map(v => v[arg]), + 'minus': bindFixed((v, arg) => v - arg), + 'modulo': bindFixed((v, arg) => v % arg), + 'newline_to_br': v => v.replace(/\n/g, '
'), + 'plus': bindFixed((v, arg) => v + arg), + 'prepend': (v, arg) => arg + v, + 'remove': (v, arg) => v.split(arg).join(''), + 'remove_first': (v, l) => v.replace(l, ''), + 'replace': (v, pattern, replacement) => + stringify(v).split(pattern).join(replacement), + 'replace_first': (v, arg1, arg2) => stringify(v).replace(arg1, arg2), + 'reverse': v => v.reverse(), + 'round': (v, arg) => { var amp = Math.pow(10, arg || 0); return Math.round(v * amp, arg) / amp; - }); - liquid.registerFilter('rstrip', str => stringify(str).replace(/\s+$/, '')); - liquid.registerFilter('size', v => v.length); - liquid.registerFilter('slice', (v, begin, length) => - v.substr(begin, length === undefined ? 1 : length)); - liquid.registerFilter('sort', (v, arg) => v.sort(arg)); - liquid.registerFilter('split', (v, arg) => stringify(v).split(arg)); - liquid.registerFilter('strip', (v) => stringify(v).trim()); - liquid.registerFilter('strip_html', v => stringify(v).replace(/<\/?\s*\w+\s*\/?>/g, '')); - liquid.registerFilter('strip_newlines', v => stringify(v).replace(/\n/g, '')); - liquid.registerFilter('times', (v, arg) => v * arg); - liquid.registerFilter('truncate', (v, l, o) => { + }, + 'rstrip': str => stringify(str).replace(/\s+$/, ''), + 'size': v => v.length, + 'slice': (v, begin, length) => + v.substr(begin, length === undefined ? 1 : length), + 'sort': (v, arg) => v.sort(arg), + 'split': (v, arg) => stringify(v).split(arg), + 'strip': (v) => stringify(v).trim(), + 'strip_html': v => stringify(v).replace(/<\/?\s*\w+\s*\/?>/g, ''), + 'strip_newlines': v => stringify(v).replace(/\n/g, ''), + 'times': (v, arg) => v * arg, + 'truncate': (v, l, o) => { v = stringify(v); o = (o === undefined) ? '...' : o; l = l || 16; if (v.length <= l) return v; return v.substr(0, l - o.length) + o; - }); - liquid.registerFilter('truncatewords', (v, l, o) => { + }, + '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', function(arr) { + }, + 'uniq': function(arr) { var u = {}; return (arr || []).filter(val => { if (u.hasOwnProperty(val)) { @@ -96,11 +85,19 @@ module.exports = function(liquid) { u[val] = true; return true; }); - }); - liquid.registerFilter('upcase', str => stringify(str).toUpperCase()); - liquid.registerFilter('url_encode', encodeURIComponent); + }, + 'upcase': str => stringify(str).toUpperCase(), + 'url_encode': encodeURIComponent }; +function escape(str) { + return stringify(str).replace(/&|<|>|"|'/g, m => escapeMap[m]); +} + +function unescape(str) { + return stringify(str).replace(/&(amp|lt|gt|#34|#39);/g, m => unescapeMap[m]); +} + function getFixed(v) { var p = (v + "").split("."); return (p.length > 1) ? p[1].length : 0; @@ -121,3 +118,10 @@ function bindFixed(cb) { return cb(l, r).toFixed(f); }; } + +function registerAll(liquid) { + return _.forOwn(filters, (func, name) => liquid.registerFilter(name, func)); +} + +registerAll.filters = filters; +module.exports = registerAll; diff --git a/src/lexical.js b/src/lexical.js index c91cc5639..a80b51a3c 100644 --- a/src/lexical.js +++ b/src/lexical.js @@ -1,14 +1,16 @@ // quote related var singleQuoted = /'[^']*'/; var doubleQuoted = /"[^"]*"/; -var quoteBalanced = new RegExp(`(?:${singleQuoted.source}|${doubleQuoted.source}|[^'"])*`); +var quoted = new RegExp(`${singleQuoted.source}|${doubleQuoted.source}`); +var quoteBalanced = new RegExp(`(?:${quoted.source}|[^'"])*`); -var number = /(?:-?\d+\.?\d*|\.?\d+)/; +// values +var integer = /-?\d+/; +var number = /-?\d+\.?\d*|\.?\d+/; var bool = /true|false/; -var identifier = /[a-zA-Z_$][a-zA-Z_$0-9]*/; -var subscript = /\[\d+\]/; +var identifier = /[\w-]+/; +var subscript = new RegExp(`\\[(?:${quoted.source}|[\\w-\\.]+)\\]`); -var quoted = new RegExp(`(?:${singleQuoted.source}|${doubleQuoted.source})`); var literal = new RegExp(`(?:${quoted.source}|${bool.source}|${number.source})`); var variable = new RegExp(`${identifier.source}(?:\\.${identifier.source}|${subscript.source})*`); @@ -69,7 +71,7 @@ function parseLiteral(str) { } module.exports = { - quoted, number, bool, literal, filter, + quoted, number, bool, literal, filter, integer, hash, hashCapture, range, rangeCapture, identifier, value, quoteBalanced, operators, diff --git a/src/util/underscore.js b/src/util/underscore.js index d558e25f3..8bfa1ca80 100644 --- a/src/util/underscore.js +++ b/src/util/underscore.js @@ -1,5 +1,29 @@ -function isString(value){ +/* + * Checks if value is classified as a String primitive or object. + * @param {any} value The value to check. + * @return {Boolean} Returns true if value is a string, else false. + */ +function isString(value) { return value instanceof String || typeof value === 'string'; } +/* + * Iterates over own enumerable string keyed properties of an object and invokes iteratee for each property. + * The iteratee is invoked with three arguments: (value, key, object). + * Iteratee functions may exit iteration early by explicitly returning false. + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @return {Object} Returs object. + */ +function forOwn(object, iteratee) { + object = object || {}; + for (var k in object) { + if (object.hasOwnProperty(k)) { + if (iteratee(object[k], k, object) === false) break; + } + } + return object; +} + exports.isString = isString; +exports.forOwn = forOwn; diff --git a/test/error.js b/test/error.js index e4530f893..363203c65 100644 --- a/test/error.js +++ b/test/error.js @@ -16,10 +16,10 @@ function test(promise, cb){ describe('error', function() { it('should throw TokenizationError when tag illegal', function() { - return test(engine.parseAndRender('{% -a %}', {}), function(err){ + return test(engine.parseAndRender('{% . a %}', {}), function(err){ expect(err.name).to.equal('TokenizationError'); - expect(err.message).to.equal('illegal tag: {% -a %}'); - expect(err.input).to.equal('{% -a %}'); + expect(err.message).to.equal('illegal tag: {% . a %}'); + expect(err.input).to.equal('{% . a %}'); expect(err.line).to.equal(1); }); }); diff --git a/test/filters.js b/test/filters.js index 370c16355..9b699ac90 100644 --- a/test/filters.js +++ b/test/filters.js @@ -1,9 +1,8 @@ const chai = require("chai"); -const expect = chai.expect; -chai.use(require("chai-as-promised")); - +const chaiAsPromised = require("chai-as-promised"); var liquid = require('..')(), ctx; +chai.use(chaiAsPromised); function test(src, dst) { ctx = { @@ -20,57 +19,74 @@ function test(src, dst) { category: 'bar' }] }; - return expect(liquid.parseAndRender(src, ctx)).to.eventually.equal(dst); + return liquid.parseAndRender(src, ctx).should.eventually.equal(dst); } describe('filters', function() { - it('should support abs 1', () => test('{{ -3 | abs }}', '3')); - it('should support abs 2', () => test('{{ arr[0] | abs }}', '2')); + describe('abs', function() { + it('should return 3 for -3', () => test('{{ -3 | abs }}', '3')); + it('should return 2 for arr[0]', () => test('{{ arr[0] | abs }}', '2')); + }); - it('should support append 1', () => test('{{ -3 | append: "abc" }}', '-3abc')); - it('should support append 2', () => test('{{ "a" | append: foo }}', 'abar')); + describe('append', function() { + it('should return "-3abc" for -3, "abc"', + () => test('{{ -3 | append: "abc" }}', '-3abc')); + it('should return "abar" for "a",foo', () => test('{{ "a" | append: foo }}', 'abar')); + }); it('should support capitalize', () => test('{{ "i am good" | capitalize }}', 'I am good')); - it('should support ceil 1', () => test('{{ 1.2 | ceil }}', '2')); - it('should support ceil 2', () => test('{{ 2.0 | ceil }}', '2')); - it('should support ceil 3', () => test('{{ "3.5" | ceil }}', '4')); - it('should support ceil 4', () => test('{{ 183.357 | ceil }}', '184')); + describe('ceil', function() { + it('should return "2" for 1.2', () => test('{{ 1.2 | ceil }}', '2')); + it('should return "2" for 2.0', () => test('{{ 2.0 | ceil }}', '2')); + it('should return "4" for 3.5', () => test('{{ "3.5" | ceil }}', '4')); + it('should return "184" for 183.357', () => test('{{ 183.357 | ceil }}', '184')); + }); - describe('date', function(){ - it('should support %a %b %d %Y', function() { + describe('date', function() { + it('should support date: %a %b %d %Y', function() { var str = ctx.date.toDateString(); return test('{{ date | date:"%a %b %d %Y"}}', str); }); - it('should support "now"', function() { - var year = (new Date()).getFullYear(); - var src = '{{ "now" | date: "%Y"}}'; - return expect(liquid.parseAndRender(src)).to.eventually.match(/\d{4}/); + it('should create a new Date when given "now"', function() { + return test('{{ "now" | date: "%Y"}}', (new Date).getFullYear().toString()); }); }); it('should support default', () => test('{{false |default: "a"}}', 'a')); - it('should support divided_by 1', () => test('{{4 | divided_by: 2}}', '2')); - it('should support divided_by 2', () => test('{{16 | divided_by: 4}}', '4')); - it('should support divided_by 3', () => test('{{5 | divided_by: 3}}', '1')); - - it('should support downcase 1', () => test('{{ "Parker Moore" | downcase }}', 'parker moore')); - it('should support downcase 2', () => test('{{ "apple" | downcase }}', 'apple')); - - it('should support escape 1', function() { - return test('{{ "Have you read \'James & the Giant Peach\'?" | escape }}', - 'Have you read 'James & the Giant Peach'?'); - }); - it('should support escape 2', function() { - return test('{{ "Tetsuro Takara" | escape }}', 'Tetsuro Takara'); - }); - it('should support excape function', function() { - return test('{{ func | escape }}', 'function () {}'); + describe('divided_by', function() { + it('should return 2 for 4,2', () => test('{{4 | divided_by: 2}}', '2')); + it('should return 4 for 16,4', () => test('{{16 | divided_by: 4}}', '4')); + it('should return 1 for 5,3', () => test('{{5 | divided_by: 3}}', '1')); }); - it('should support escape_once 1', () => test('{{ "1 < 2 & 3" | escape_once }}', '1 < 2 & 3')); - it('should support escape_once 2', () => test('{{ "1 < 2 & 3" | escape_once }}', '1 < 2 & 3')); + describe('downcase', function() { + it('should return "parker moore" for "Parker Moore"', + () => test('{{ "Parker Moore" | downcase }}', 'parker moore')); + it('should return "apple" for "apple"', + () => test('{{ "apple" | downcase }}', 'apple')); + }); + + describe('escape', function() { + it('should escape \' and &', function() { + return test('{{ "Have you read \'James & the Giant Peach\'?" | escape }}', + 'Have you read 'James & the Giant Peach'?'); + }); + it('should escape normal string', function() { + return test('{{ "Tetsuro Takara" | escape }}', 'Tetsuro Takara'); + }); + it('should escape function', function() { + return test('{{ func | escape }}', 'function () {}'); + }); + }); + + describe('escape_once', function() { + it('should do escape', () => + test('{{ "1 < 2 & 3" | escape_once }}', '1 < 2 & 3')); + it('should not escape twice', + () => test('{{ "1 < 2 & 3" | escape_once }}', '1 < 2 & 3')); + }); it('should support split/first', function() { var src = '{% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}' + @@ -78,10 +94,12 @@ describe('filters', function() { return test(src, 'apples'); }); - it('should support floor 1', () => test('{{ 1.2 | floor }}', '1')); - it('should support floor 2', () => test('{{ 2.0 | floor }}', '2')); - it('should support floor 3', () => test('{{ 183.357 | floor }}', '183')); - it('should support floor 4', () => test('{{ "3.5" | floor }}', '3')); + describe('floor', function() { + it('should return "1" for 1.2', () => test('{{ 1.2 | floor }}', '1')); + it('should return "2" for 2.0', () => test('{{ 2.0 | floor }}', '2')); + it('should return "183" for 183.357', () => test('{{ 183.357 | floor }}', '183')); + it('should return "3" for 3.5', () => test('{{ "3.5" | floor }}', '3')); + }); it('should support join', function() { var src = '{% assign beatles = "John, Paul, George, Ringo" | split: ", " %}' + @@ -104,13 +122,19 @@ describe('filters', function() { return test('{{posts | map: "category"}}', '["foo","bar"]'); }); - it('should support minus 1', () => test('{{ 4 | minus: 2 }}', '2')); - it('should support minus 2', () => test('{{ 16 | minus: 4 }}', '12')); - it('should support minus 3', () => test('{{ 183.357 | minus: 12 }}', '171.357')); + describe('minus', function() { + it('should return "2" for 4,2', () => test('{{ 4 | minus: 2 }}', '2')); + it('should return "12" for 16,4', () => test('{{ 16 | minus: 4 }}', '12')); + it('should return "171.357" for 183.357,12', + () => test('{{ 183.357 | minus: 12 }}', '171.357')); + }); - it('should support modulo 1', () => test('{{ 3 | modulo: 2 }}', '1')); - it('should support modulo 2', () => test('{{ 24 | modulo: 7 }}', '3')); - it('should support modulo 3', () => test('{{ 183.357 | modulo: 12 }}', '3.357')); + describe('modulo', function() { + it('should return "1" for 3,2', () => test('{{ 3 | modulo: 2 }}', '1')); + it('should return "3" for 24,7', () => test('{{ 24 | modulo: 7 }}', '3')); + it('should return "3.357" for 183.357,12', + () => test('{{ 183.357 | modulo: 12 }}', '3.357')); + }); it('should support string_with_newlines', function() { var src = '{% capture string_with_newlines %}\n' + @@ -124,9 +148,12 @@ describe('filters', function() { return test(src, dst); }); - it('should support plus 1', () => test('{{ 4 | plus: 2 }}', '6')); - it('should support plus 2', () => test('{{ 16 | plus: 4 }}', '20')); - it('should support plus 3', () => test('{{ 183.357 | plus: 12 }}', '195.357')); + describe('plus', function() { + it('should return "6" for 4,2', () => test('{{ 4 | plus: 2 }}', '6')); + it('should return "20" for 16,4', () => test('{{ 16 | plus: 4 }}', '20')); + it('should return "195.357" for 183.357,12', + () => test('{{ 183.357 | plus: 12 }}', '195.357')); + }); it('should support prepend', function() { return test('{% assign url = "liquidmarkup.com" %}' + @@ -160,26 +187,34 @@ describe('filters', function() { '.moT rojaM ot lortnoc dnuorG'); }); - it('should support round 1', () => test('{{1.2|round}}', '1')); - it('should support round 2', () => test('{{2.7|round}}', '3')); - it('should support round 3', () => test('{{183.357|round: 2}}', '183.36')); + describe('round', function() { + it('should return "1" for 1.2', () => test('{{1.2|round}}', '1')); + it('should return "3" for 2.7', () => test('{{2.7|round}}', '3')); + it('should return "183.36" for 183.357,2', + () => test('{{183.357|round: 2}}', '183.36')); + }); it('should support rstrip', function() { return test('{{ " So much room for activities! " | rstrip }}', ' So much room for activities!'); }); - it('should support size 1', () => test('{{ "Ground control to Major Tom." | size }}', '28')); - it('should support size 2', function() { - return test('{% assign my_array = "apples, oranges, peaches, plums"' + - ' | split: ", " %}{{ my_array | size }}', - '4'); + describe('size', function() { + it('should return string length', + () => test('{{ "Ground control to Major Tom." | size }}', '28')); + it('should return array size', function() { + return test('{% assign my_array = "apples, oranges, peaches, plums"' + + ' | split: ", " %}{{ my_array | size }}', + '4'); + }); }); - it('should support slice 1', () => test('{{ "Liquid" | slice: 0 }}', 'L')); - it('should support slice 2', () => test('{{ "Liquid" | slice: 2 }}', 'q')); - it('should support slice 3', () => test('{{ "Liquid" | slice: 2, 5 }}', 'quid')); - it('should support slice 4', () => test('{{ "Liquid" | slice: -3, 2 }}', 'ui')); + describe('slice', function() { + it('should slice first char by 0', () => test('{{ "Liquid" | slice: 0 }}', 'L')); + it('should slice third char by 2', () => test('{{ "Liquid" | slice: 2 }}', 'q')); + it('should slice substr by 2,5', () => test('{{ "Liquid" | slice: 2, 5 }}', 'quid')); + it('should slice substr by -3,2', () => test('{{ "Liquid" | slice: -3, 2 }}', 'ui')); + }); it('should support sort', function() { return test('{% assign my_array = "zebra, octopus, giraffe, Sally Snake"' + @@ -201,12 +236,14 @@ describe('filters', function() { 'So much room for activities!'); }); - it('should support strip_tml 1', function() { - return test('{{ "Have you read Ulysses?" | strip_html }}', - 'Have you read Ulysses?'); - }); - it('should support strip_tml 2', function() { - return test('{{"

< p >

" | strip_html }}', ''); + describe('strip_html', function() { + it('should strip all tags', function() { + return test('{{ "Have you read Ulysses?" | strip_html }}', + 'Have you read Ulysses?'); + }); + it('should strip until empty', function() { + return test('{{"

< p >

" | strip_html }}', ''); + }); }); it('should support strip_newlines', function() { @@ -216,42 +253,49 @@ describe('filters', function() { 'Hellothere'); }); - it('should support times 1', () => test('{{ 3 | times: 2 }}', '6')); - it('should support times 2', () => test('{{ 24 | times: 7 }}', '168')); - it('should support times 3', () => test('{{ 183.357 | times: 12 }}', '2200.284')); - - it('should support truncate 1', function() { - return test('{{ "Ground control to Major Tom." | truncate: 20 }}', - 'Ground control to...'); - }); - it('should support truncate 2', function() { - return test('{{ "Ground control to Major Tom." | truncate: 80 }}', - 'Ground control to Major Tom.'); - }); - it('should support truncate 3', function() { - return test('{{ "Ground control to Major Tom." | truncate: 25,", and so on" }}', - 'Ground control, and so on'); - }); - it('should support truncate 4', function() { - return test('{{ "Ground control to Major Tom." | truncate: 20, "" }}', - 'Ground control to Ma'); + describe('times', function() { + it('should return "6" for 3,2', () => test('{{ 3 | times: 2 }}', '6')); + it('should return "168" for 24,7', () => test('{{ 24 | times: 7 }}', '168')); + it('should return "2200.284" for 183.357,12', + () => test('{{ 183.357 | times: 12 }}', '2200.284')); }); - it('should support truncatewords 1', function() { - return test('{{ "Ground control to Major Tom." | truncatewords: 3 }}', - 'Ground control to...'); + describe('truncate', function() { + it('should truncate when string too long', function() { + return test('{{ "Ground control to Major Tom." | truncate: 20 }}', + 'Ground control to...'); + }); + it('should not truncate when string not long enough', function() { + return test('{{ "Ground control to Major Tom." | truncate: 80 }}', + 'Ground control to Major Tom.'); + }); + it('should truncate with custom ellipsis', function() { + return test('{{ "Ground control to Major Tom." | truncate: 25,", and so on" }}', + 'Ground control, and so on'); + }); + it('should truncate with empty custom ellipsis', function() { + return test('{{ "Ground control to Major Tom." | truncate: 20, "" }}', + 'Ground control to Ma'); + }); }); - it('should support truncatewords 2', function() { - return test('{{ "Ground control to Major Tom." | truncatewords: 8 }}', - 'Ground control to Major Tom.'); - }); - it('should support truncatewords 3', function() { - return test('{{ "Ground control to Major Tom." | truncatewords: 3, "--" }}', - 'Ground control to--'); - }); - it('should support truncatewords 4', function() { - return test('{{ "Ground control to Major Tom." | truncatewords: 3, "" }}', - 'Ground control to'); + + describe('truncatewords', function() { + it('should truncate when too many words', function() { + return test('{{ "Ground control to Major Tom." | truncatewords: 3 }}', + 'Ground control to...'); + }); + it('should not truncate when not enough words', function() { + return test('{{ "Ground control to Major Tom." | truncatewords: 8 }}', + 'Ground control to Major Tom.'); + }); + it('should truncate with custom ellipsis', function() { + return test('{{ "Ground control to Major Tom." | truncatewords: 3, "--" }}', + 'Ground control to--'); + }); + it('should truncate with empty custom ellipsis', function() { + return test('{{ "Ground control to Major Tom." | truncatewords: 3, "" }}', + 'Ground control to'); + }); }); it('should support uniq', function() { @@ -262,6 +306,10 @@ describe('filters', function() { it('should support upcase', () => test('{{ "Parker Moore" | upcase }}', 'PARKER MOORE')); - it('should support url_encode 1', () => test('{{ "john@liquid.com" | url_encode }}', 'john%40liquid.com')); - it('should support url_encode 2', () => test('{{ "Tetsuro Takara" | url_encode }}', 'Tetsuro%20Takara')); + describe('url_encode', function() { + it('should encode @', + () => test('{{ "john@liquid.com" | url_encode }}', 'john%40liquid.com')); + it('should encode ', + () => test('{{ "Tetsuro Takara" | url_encode }}', 'Tetsuro%20Takara')); + }); }); diff --git a/test/lexical.js b/test/lexical.js index fd561385d..d7352b755 100644 --- a/test/lexical.js +++ b/test/lexical.js @@ -39,10 +39,25 @@ describe('lexical', function() { lexical.isLiteral("'a bcd'").should.equal(true); }); - it("should test variable", function() { - lexical.isVariable("foo").should.equal(true); - lexical.isVariable("foo.bar.foo").should.equal(true); - lexical.isVariable("foo[0].b").should.equal(true); + describe('.isVariable()', function() { + it('should return true for foo', function(){ + lexical.isVariable("foo").should.equal(true); + }); + it('should return true for.bar.foo', function(){ + lexical.isVariable("foo.bar.foo").should.equal(true); + }); + it('should return true for foo[0].b', function(){ + lexical.isVariable("foo[0].b").should.equal(true); + }); + it('should return true for 0a', function(){ + lexical.isVariable("0a").should.equal(true); + }); + it('should return true for foo[a.b]', function(){ + lexical.isVariable("foo[a.b]").should.equal(true); + }); + it('should return true for foo[a.b]', function(){ + lexical.isVariable("foo['a[0]']").should.equal(true); + }); }); it('should test none literal', function() { @@ -55,7 +70,6 @@ describe('lexical', function() { lexical.isVariable("a.").should.equal(false); lexical.isVariable(".b").should.equal(false); lexical.isVariable(".").should.equal(false); - lexical.isVariable("0a").should.equal(false); lexical.isVariable("[0][12].bar[0]").should.equal(false); }); diff --git a/test/liquid.js b/test/liquid.js index e25a80709..bd7c2c3bf 100644 --- a/test/liquid.js +++ b/test/liquid.js @@ -70,6 +70,11 @@ describe('liquid', function() { var template = engine.parse('

{{arr | join: "_"}}

'); return engine.render(template, ctx).should.eventually.equal('

-2_a

'); }); + it('should render accessive filters', function() { + var src = '{% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}' + + '{{ my_array | first }}'; + return expect(engine.parseAndRender(src)).to.eventually.equal('apples'); + }); describe('#renderFile()', function() { it('should render file', function() { return engine.renderFile('/root/files/foo.html', ctx).should.eventually.equal('foo'); @@ -83,17 +88,17 @@ describe('liquid', function() { it('should use default extname', function() { return engine.renderFile('files/name', ctx).should.eventually.equal('My name is harttle.'); }); - it('should accept root with no trailing slash', function(){ + it('should accept root with no trailing slash', function() { engine = Liquid({ root: '/root', extname: '.html' }); return expect(engine.renderFile('files/foo.html')).to.eventually.equal('foo'); }); - it('should accept dot path', function(){ + it('should accept dot path', function() { return expect(engine.renderFile('./files/foo.html')).to.eventually.equal('foo'); }); - it('should accept double-dot path', function(){ + it('should accept double-dot path', function() { return expect(engine.renderFile('files/foo/../foo.html')).to.eventually.equal('foo'); }); }); diff --git a/test/scope.js b/test/scope.js index b3a11acdc..c15ee7e3e 100644 --- a/test/scope.js +++ b/test/scope.js @@ -1,27 +1,25 @@ -var chai = require("chai"); -var should = chai.should(); -var expect = chai.expect; +const chai = require("chai"); +const expect = chai.expect; var Scope = require('../src/scope.js'); describe('scope', function() { var scope, ctx; - beforeEach(function(){ + beforeEach(function() { ctx = { - foo: 'bar', - bar: ['a', {b: [1, 2]}] + foo: 'bar' }; scope = Scope.factory(ctx); }); - it('should get property', function() { - scope.get('foo').should.equal('bar'); + it('should get direct property', function() { + expect(scope.get('foo')).equal('bar'); }); it('should get undefined property', function() { - function fn(){ - scope.get('notdefined'); - } + function fn() { + scope.get('notdefined'); + } expect(fn).to.not.throw(); expect(scope.get('notdefined')).to.equal(undefined); expect(scope.get('')).to.equal(undefined); @@ -29,47 +27,46 @@ describe('scope', function() { }); it('should throw undefined in strict mode', function() { - scope = Scope.factory(ctx, { - strict: true - }); - function fn(){ - scope.get('notdefined'); - } - expect(fn).to.throw(/undefined variable: notdefined/); + scope = Scope.factory(ctx, { + strict: true + }); + + function fn() { + scope.get('notdefined'); + } + expect(fn).to.throw(/undefined variable: notdefined/); }); - it('should get all property', function() { - scope.get().should.deep.equal(ctx); + it('should get all properties when arguments empty', function() { + expect(scope.get()).deep.equal(ctx); }); - it('should set property', function() { - scope.set('foo', 'FOO'); - scope.get('foo').should.equal('FOO'); - }); - - it('should set child property', function() { + it('should access child property via dot syntax', function() { scope.set('oo.bar', 'FOO'); - scope.get('oo.bar').should.equal('FOO'); + expect(scope.get('oo.bar')).to.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]); - scope.get('bar[1].b[1]').should.equal(2); + it('should access child property via [] syntax', function() { + scope.set('bar', ['a', {'b': [1,2]}]); + expect(scope.get('bar[0]')).to.equal('a'); + expect(scope.get('bar[1].b')).to.deep.equal([1, 2]); + expect(scope.get('bar[1].b[1]')).to.equal(2); }); it('should push scope', function() { - scope.push({foo: 'foo', foo1: 'foo1'}); - scope.get('foo').should.equal('foo'); - scope.get('foo1').should.equal('foo1'); - scope.get('bar[1].b[1]').should.equal(2); + scope.set('bar', 'bar'); + scope.push({ + foo: 'foo' + }); + expect(scope.get('foo')).to.equal('foo'); + expect(scope.get('bar')).to.equal('bar'); }); it('should pop scope', function() { - scope.push({foo: 'foo', foo1: 'foo1'}); + scope.push({ + foo: 'foo' + }); scope.pop(); expect(scope.get('foo')).to.equal('bar'); - expect(scope.get('foo1')).to.equal(undefined); - expect(scope.get('bar[1].b[1]')).to.equal(2); }); }); diff --git a/test/tag.js b/test/tag.js index 062b5635f..a07739054 100644 --- a/test/tag.js +++ b/test/tag.js @@ -1,8 +1,7 @@ -var chai = require("chai"); -var sinonChai = require("sinon-chai"); -var sinon = require("sinon"); -var expect = chai.expect; -chai.use(sinonChai); +const chai = require("chai"); +const sinon = require("sinon"); +const expect = chai.expect; +chai.use(require("sinon-chai")); var tag = require('../src/tag.js')(); var Scope = require('../src/scope.js'); diff --git a/test/util/underscore.js b/test/util/underscore.js index 83a622acd..8848d7633 100644 --- a/test/util/underscore.js +++ b/test/util/underscore.js @@ -1,17 +1,40 @@ const chai = require("chai"); +const sinon = require('sinon'); const expect = chai.expect; -const _ = require('../../src/util/underscore.js'); +chai.use(require("sinon-chai")); + +var _ = require('../../src/util/underscore.js'); describe('util/underscore', function() { - describe('.isString()', function(){ - it('should return true for literal string', function(){ + describe('.isString()', function() { + it('should return true for literal string', function() { expect(_.isString('foo')).to.be.true; }); - it('should return true String instance', function(){ + it('should return true String instance', function() { expect(_.isString(new String('foo'))).to.be.true; }); - it('should return false for 123 ', function(){ + it('should return false for 123 ', function() { expect(_.isString(123)).to.be.false; }); }); + describe('.forOwn()', function() { + it('should iterate all properties', function() { + var spy = sinon.spy(); + var obj = { + foo: "bar" + }; + _.forOwn(obj, spy); + expect(spy).to.have.been.calledWith('bar', 'foo', obj); + }); + it('should not iterate over properties on prototype', function() { + var spy = sinon.spy(); + var obj = Object.create({ + bar: 'foo' + }); + obj.foo = 'bar'; + _.forOwn(obj, spy); + expect(spy).to.have.been.calledOnce; + expect(spy).to.have.been.calledWith('bar', 'foo', obj); + }); + }); });