diff --git a/index.js b/index.js index b49435af3..efb65cdd0 100644 --- a/index.js +++ b/index.js @@ -104,7 +104,7 @@ var _engine = { opts = opts || {} var self = this return function (filePath, ctx, callback) { - assert(_.isArray(this.root) || _.isString(this.root), + assert(Array.isArray(this.root) || _.isString(this.root), 'illegal views root, are you using express.js?') opts.root = this.root self.renderFile(filePath, ctx, opts) @@ -132,7 +132,7 @@ function factory (options) { } function normalizeStringArray (value) { - if (_.isArray(value)) return value + if (Array.isArray(value)) return value if (_.isString(value)) return [value] return [] } diff --git a/src/render.js b/src/render.js index 7a45ea65a..9d7e1094d 100644 --- a/src/render.js +++ b/src/render.js @@ -26,11 +26,11 @@ var render = { function renderTemplate (template) { if (template.type === 'tag') { return this.renderTag(template, scope) - .then(partial => partial === undefined ? '' : partial) + .then(partial => partial === undefined ? '' : partial) } else if (template.type === 'output') { return Promise.resolve() - .then(() => this.evalOutput(template, scope)) - .then(partial => partial === undefined ? '' : stringify(partial)) + .then(() => this.evalOutput(template, scope)) + .then(partial => partial === undefined ? '' : stringify(partial)) } else { // template.type === 'html' return Promise.resolve(template.value) } @@ -50,8 +50,8 @@ var render = { evalOutput: function (template, scope) { assert(scope, 'unable to evalOutput: scope undefined') return template.filters.reduce( - (prev, filter) => filter.render(prev, scope), - Syntax.evalExp(template.initial, scope)) + (prev, filter) => filter.render(prev, scope), + Syntax.evalExp(template.initial, scope)) } } diff --git a/src/util/underscore.js b/src/util/underscore.js index f3c9a442b..2101595b9 100644 --- a/src/util/underscore.js +++ b/src/util/underscore.js @@ -60,10 +60,6 @@ function _assignBinary (dst, src) { return dst } -function isArray (value) { - return value instanceof Array -} - function echo (prefix) { return v => { console.log('[' + prefix + ']', v) @@ -91,16 +87,8 @@ function uniq (arr) { * @return {Boolean} Returns true if value is an object, else false. */ function isObject (value) { - return value !== null && typeof value === 'object' -} - -/* - * Checks if value's prototype is Object or undefined. - * @param {any} value The value to check. - * @return {Boolean} Returns true if value is a plain object, else false. - */ -function isPlainObject(value) { - return value && !value.constructor || value.constructor === Object + var type = typeof value + return value != null && (type === 'object' || type === 'function') } /* @@ -126,9 +114,7 @@ function range (start, stop, step) { } exports.isString = isString -exports.isArray = isArray exports.isObject = isObject -exports.isPlainObject = isPlainObject exports.isError = isError exports.range = range diff --git a/tags/for.js b/tags/for.js index 42cdb8dd3..8aa4bb6d5 100644 --- a/tags/for.js +++ b/tags/for.js @@ -1,7 +1,7 @@ const Liquid = require('..') const lexical = Liquid.lexical const mapSeries = require('../src/util/promise.js').mapSeries -const isPlainObject = require('../src/util/underscore.js').isPlainObject +const _ = require('../src/util/underscore.js') const RenderBreakError = Liquid.Types.RenderBreakError const assert = require('../src/util/assert.js') const re = new RegExp(`^(${lexical.identifier.source})\\s+in\\s+` + @@ -39,8 +39,12 @@ module.exports = function (liquid) { render: function (scope, hash) { var collection = Liquid.evalExp(this.collection, scope) - if (isPlainObject(collection)) { - collection = Object.keys(collection) + if (!Array.isArray(collection)) { + if (_.isString(collection) && collection.length > 0) { + collection = [collection] + } else if (_.isObject(collection)) { + collection = Object.keys(collection) + } } if (!Array.isArray(collection) || !collection.length) { return liquid.renderer.renderTemplates(this.elseTemplates, scope) diff --git a/test/tags/for.js b/test/tags/for.js index 2abdfa961..6de383d08 100644 --- a/test/tags/for.js +++ b/test/tags/for.js @@ -9,32 +9,64 @@ describe('tags/for', function () { liquid = Liquid() ctx = { one: 1, + // eslint-disable-next-line + strObj: new String(''), + emptyObj: {}, + nullProtoObj: Object.create(null), + obj: {foo: 'bar', coo: 'haa'}, alpha: ['a', 'b', 'c'], emptyArray: [] } }) - it('should support for', function () { + it('should support array', function () { var src = '{%for c in alpha%}{{c}}{%endfor%}' return expect(liquid.parseAndRender(src, ctx)) .to.eventually.equal('abc') }) + it('should support object', function () { + var src = '{%for key in obj%}{{key}}-{%else%}b{%endfor%}' + return expect(liquid.parseAndRender(src, ctx)) + .to.eventually.equal('foo-coo-') + }) + it('should throw when for not closed', function () { var src = '{%for c in alpha%}{{c}}' return expect(liquid.parseAndRender(src, ctx)) .to.be.rejectedWith(/tag .* not closed/) }) - it('should return else when for in empty array', function () { - var src = '{%for c in emptyArray%}a{%else%}b{%endfor%}' - return expect(liquid.parseAndRender(src, ctx)) - .to.eventually.equal('b') - }) + describe('else', function () { + it('should goto else for empty array', function () { + var src = '{%for c in emptyArray%}a{%else%}b{%endfor%}' + return expect(liquid.parseAndRender(src, ctx)) + .to.eventually.equal('b') + }) - it('should support for else', function () { - var src = '{%for c in ""%}a{%else%}b{%endfor%}' - return expect(liquid.parseAndRender(src, ctx)) - .to.eventually.equal('b') + it('should goto else for empty string', function () { + var src = '{%for c in ""%}a{%else%}b{%endfor%}' + return expect(liquid.parseAndRender(src, ctx)) + .to.eventually.equal('b') + }) + + it('should goto else for empty string object', function () { + // it should be false although `new String` is none-conform + var src = '{%for c in strObj%}a{%else%}b{%endfor%}' + return expect(liquid.parseAndRender(src, ctx)) + .to.eventually.equal('b') + }) + + it('should goto else for empty object', function () { + var src = '{%for c in emptyObj%}a{%else%}b{%endfor%}' + return expect(liquid.parseAndRender(src, ctx)) + .to.eventually.equal('b') + }) + + it('should goto else for null-prototyped object', function () { + var src = '{%for c in nullProtoObj%}a{%else%}b{%endfor%}' + return expect(liquid.parseAndRender(src, ctx)) + .to.eventually.equal('b') + }) }) it('should support for with forloop', function () { diff --git a/test/util/underscore.js b/test/util/underscore.js index 10efcbd62..06d2600b6 100644 --- a/test/util/underscore.js +++ b/test/util/underscore.js @@ -62,14 +62,6 @@ describe('util/underscore', function () { expect(spy).to.have.been.calledOnce }) }) - describe('.isArray()', function () { - it('should return true for []', function () { - expect(_.isArray([])).to.be.true - }) - it('should return false for "foo"', function () { - expect(_.isArray('foo')).to.be.false - }) - }) describe('.echo()', function () { it('should be transparent', function () { expect(_.echo('foo')('bar')).to.equal('bar')