From fa5b731daf2c9209993bd48e5c8a3162119f0001 Mon Sep 17 00:00:00 2001 From: harttle Date: Sat, 11 Aug 2018 17:04:04 +0800 Subject: [PATCH] fix string type checking and circular reference, working on #81 --- src/render.js | 16 ++++++++-------- src/util/underscore.js | 23 +++++++++++++++++++++- test/render.js | 43 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 71 insertions(+), 11 deletions(-) diff --git a/src/render.js b/src/render.js index 2428d64aa..563cb610f 100644 --- a/src/render.js +++ b/src/render.js @@ -1,6 +1,7 @@ const Syntax = require('./syntax.js') const mapSeries = require('./util/promise.js').mapSeries const RenderBreakError = require('./util/error.js').RenderBreakError +const _ = require('./util/underscore.js') const RenderError = require('./util/error.js').RenderError const assert = require('./util/assert.js') @@ -27,9 +28,7 @@ var render = { return this.renderTag(template, scope) .then(partial => partial === undefined ? '' : partial) } else if (template.type === 'value') { - return Promise.resolve() - .then(() => this.evalValue(template, scope)) - .then(partial => partial === undefined ? '' : stringify(partial)) + return this.renderValue(template, scope) } else { // template.type === 'html' return Promise.resolve(template.value) } @@ -46,6 +45,12 @@ var render = { return template.render(scope) }, + renderValue: function (template, scope) { + return Promise.resolve() + .then(() => this.evalValue(template, scope)) + .then(partial => partial === undefined ? '' : _.stringify(partial)) + }, + evalValue: function (template, scope) { assert(scope, 'unable to evalValue: scope undefined') return template.filters.reduce( @@ -59,9 +64,4 @@ function factory () { return instance } -function stringify (val) { - if (typeof val === 'string') return val - return JSON.stringify(val) -} - module.exports = factory diff --git a/src/util/underscore.js b/src/util/underscore.js index 164aa882b..8441f26fa 100644 --- a/src/util/underscore.js +++ b/src/util/underscore.js @@ -6,7 +6,27 @@ const toStr = Object.prototype.toString * @return {Boolean} Returns true if value is a string, else false. */ function isString (value) { - return value instanceof String || typeof value === 'string' + return toStr.call(value) === '[object String]' +} + +function stringify (value) { + if (isString(value)) { + return value + } + if (value && typeof value.to_liquid === 'function') { + return value.to_liquid() + } + + let cache = [] + return JSON.stringify(value, (key, value) => { + if (isObject(value)) { + if (cache.indexOf(value) !== -1) { + return + } + cache.push(value) + } + return value + }) } function isNil (value) { @@ -126,3 +146,4 @@ exports.last = last exports.forOwn = forOwn exports.assign = assign exports.uniq = uniq +exports.stringify = stringify diff --git a/test/render.js b/test/render.js index a4ed65b6e..9a6141f95 100644 --- a/test/render.js +++ b/test/render.js @@ -17,11 +17,17 @@ describe('render', function () { var scope, render beforeEach(function () { - scope = Scope.factory({ + var ctx = { foo: { bar: ['a', 2] + }, + bar: { + to_liquid: x => 'custom' } - }) + } + ctx.self = ctx + + scope = Scope.factory(ctx) filter.clear() tag.clear() render = Render() @@ -39,6 +45,34 @@ describe('render', function () { }) }) + describe('.renderValue()', function () { + it('should respect to .to_liquid() method', function () { + var tpl = Template.parseValue('bar') + return expect(render.renderValue(tpl, scope)).to.eventually.equal('custom') + }) + + it('should stringify objects', function () { + let scope = Scope.factory({ + foo: { obj: { arr: ['a', 2] } } + }) + let tpl = Template.parseValue('foo') + return expect(render.renderValue(tpl, scope)).to.eventually.equal('{"obj":{"arr":["a",2]}}') + }) + it('should skip circular property', function () { + let ctx = { foo: { num: 2 }, bar: 'bar' } + ctx.foo.circular = ctx + + let scope = Scope.factory(ctx) + let tpl = Template.parseValue('foo') + return expect(render.renderValue(tpl, scope)).to.eventually.equal('{"num":2,"circular":{"bar":"bar"}}') + }) + it('should skip function property', function () { + let scope = Scope.factory({obj: {foo: 'foo', bar: x => x}}) + let tpl = Template.parseValue('obj') + return expect(render.renderValue(tpl, scope)).to.eventually.equal('{"foo":"foo"}') + }) + }) + it('should eval filter with correct arguments', function () { var date = sinon.stub().returns('y') var time = sinon.spy() @@ -62,5 +96,10 @@ describe('render', function () { var tpl = Template.parseValue('foo.bar[0] | date: "b" | time:2') expect(render.evalValue(tpl, scope)).to.equal('ab6') }) + it('should reserve type', function () { + filter.register('arr', () => [1]) + var tpl = Template.parseValue('"x" | arr') + expect(render.evalValue(tpl, scope)).to.deep.equal([1]) + }) }) })