diff --git a/.eslintrc.json b/.eslintrc.json index d15f3868c6..1679fa4224 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -18,6 +18,6 @@ "prefer-const": 2, "no-unused-vars": "off", "import/export": "off", - "@typescript-eslint/no-unused-vars": ["error", { "vars": "all", "args": "after-used", "ignoreRestSiblings": false }] + "@typescript-eslint/no-unused-vars": ["error", { "vars": "all", "args": "off", "ignoreRestSiblings": false }] } } diff --git a/README.md b/README.md index 93b8eb0034..3be1a9c347 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,11 @@ This is a liquid implementation for both Node.js and browsers. Website: ` parameter in `{%include filepath %}`, `{%layout filepath%}` as a variable, otherwise as a literal value. Defaults to `true`. -* `strict_filters` is used to enable strict filter existence. If set to `false`, undefined filters will be rendered as empty string. Otherwise, undefined filters will cause an exception. Defaults to `false`. +* `strictFilters` is used to enable strict filter existence. If set to `false`, undefined filters will be rendered as empty string. Otherwise, undefined filters will cause an exception. Defaults to `false`. -* `strict_variables` is used to enable strict variable derivation. +* `strictVariables` is used to enable strict variable derivation. If set to `false`, undefined variables will be rendered as empty string. Otherwise, undefined variables will cause an exception. Defaults to `false`. -* `trim_tag_right` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of tags (`{% %}`) until `\n` (inclusive). Defaults to `false`. +* `trimTagRight` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of tags (`{% %}`) until `\n` (inclusive). Defaults to `false`. -* `trim_tag_left` is similiar to `trim_tag_right`, whereas the `\n` is exclusive. Defaults to `false`. See [Whitespace Control][whitespace control] for details. +* `trimTagLeft` is similiar to `trimTagRight`, whereas the `\n` is exclusive. Defaults to `false`. See [Whitespace Control][whitespace control] for details. -* `trim_output_right` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of values (`{{ }}`) until `\n` (inclusive). Defaults to `false`. +* `trimOutputRight` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of values (`{{ }}`) until `\n` (inclusive). Defaults to `false`. -* `trim_output_left` is similiar to `trim_output_right`, whereas the `\n` is exclusive. Defaults to `false`. See [Whitespace Control][whitespace control] for details. +* `trimOutputLeft` is similiar to `trimOutputRight`, whereas the `\n` is exclusive. Defaults to `false`. See [Whitespace Control][whitespace control] for details. -* `tag_delimiter_left` and `tag_delimiter_right` are used to override the delimiter for liquid tags. +* `tagDelimiterLeft` and `tagDelimiterRight` are used to override the delimiter for liquid tags. -* `output_delimiter_left` and `output_delimiter_right` are used to override the delimiter for liquid outputs. +* `outputDelimiterLeft` and `outputDelimiterRight` are used to override the delimiter for liquid outputs. -* `greedy` is used to specify whether `trim_left`/`trim_right` is greedy. When set to `true`, all consecutive blank characters including `\n` will be trimed regardless of line breaks. Defaults to `true`. +* `greedy` is used to specify whether `trim*Left`/`trim*Right` is greedy. When set to `true`, all consecutive blank characters including `\n` will be trimed regardless of line breaks. Defaults to `true`. ## Register Filters diff --git a/src/drop/drop.ts b/src/drop/drop.ts index 792416e3f8..f6590f59c6 100644 --- a/src/drop/drop.ts +++ b/src/drop/drop.ts @@ -1,6 +1,11 @@ +import { deprecate } from '../util/deprecate' + export abstract class Drop { - abstract valueOf(): any; + valueOf(): any { + return undefined + } - liquid_method_missing (name: string) { // eslint-disable-line + liquidMethodMissing (key: string): string | undefined { + return undefined } } diff --git a/src/liquid-options.ts b/src/liquid-options.ts index 95d6674061..d6f9f893a3 100644 --- a/src/liquid-options.ts +++ b/src/liquid-options.ts @@ -1,5 +1,4 @@ -/* eslint-disable camelcase */ - +import { deprecate } from './util/deprecate' import * as _ from './util/underscore' export interface LiquidOptions { @@ -11,25 +10,25 @@ export interface LiquidOptions { cache?: boolean /** `dynamicPartials`: if set, treat `` parameter in `{%include filepath %}`, `{%layout filepath%}` as a variable, otherwise as a literal value. Defaults to `true`. */ dynamicPartials?: boolean - /** `strict_filters` is used to enable strict filter existence. If set to `false`, undefined filters will be rendered as empty string. Otherwise, undefined filters will cause an exception. Defaults to `false`. */ - strict_filters?: boolean - /** `strict_variables` is used to enable strict variable derivation. If set to `false`, undefined variables will be rendered as empty string. Otherwise, undefined variables will cause an exception. Defaults to `false`. */ - strict_variables?: boolean - /** `trim_tag_right` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of tags (`{% %}`) until `\n` (inclusive). Defaults to `false`. */ - trim_tag_right?: boolean - /** `trim_tag_left` is similar to `trim_tag_right`, whereas the `\n` is exclusive. Defaults to `false`. See Whitespace Control for details. */ - trim_tag_left?: boolean - /** ``trim_output_right` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of values (`{{ }}`) until `\n` (inclusive). Defaults to `false`. */ - trim_output_right?: boolean - /** `trim_output_left` is similar to `trim_output_right`, whereas the `\n` is exclusive. Defaults to `false`. See Whitespace Control for details. */ - trim_output_left?: boolean - /** `tag_delimiter_left` and `tag_delimiter_right` are used to override the delimiter for liquid tags **/ - tag_delimiter_left?: string, - tag_delimiter_right?: string, - /** `output_delimiter_left` and `output_delimiter_right` are used to override the delimiter for liquid outputs **/ - output_delimiter_left?: string, - output_delimiter_right?: string, - /** `greedy` is used to specify whether `trim_left`/`trim_right` is greedy. When set to `true`, all consecutive blank characters including `\n` will be trimed regardless of line breaks. Defaults to `true`. */ + /** `strictFilters` is used to enable strict filter existence. If set to `false`, undefined filters will be rendered as empty string. Otherwise, undefined filters will cause an exception. Defaults to `false`. */ + strictFilters?: boolean + /** `strictVariables` is used to enable strict variable derivation. If set to `false`, undefined variables will be rendered as empty string. Otherwise, undefined variables will cause an exception. Defaults to `false`. */ + strictVariables?: boolean + /** `trimTagRight` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of tags (`{% %}`) until `\n` (inclusive). Defaults to `false`. */ + trimTagRight?: boolean + /** `trimTagLeft` is similar to `trimTagRight`, whereas the `\n` is exclusive. Defaults to `false`. See Whitespace Control for details. */ + trimTagLeft?: boolean + /** ``trimOutputRight` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of values (`{{ }}`) until `\n` (inclusive). Defaults to `false`. */ + trimOutputRight?: boolean + /** `trimOutputLeft` is similar to `trimOutputRight`, whereas the `\n` is exclusive. Defaults to `false`. See Whitespace Control for details. */ + trimOutputLeft?: boolean + /** `tagDelimiterLeft` and `tagDelimiterRight` are used to override the delimiter for liquid tags **/ + tagDelimiterLeft?: string, + tagDelimiterRight?: string, + /** `outputDelimiterLeft` and `outputDelimiterRight` are used to override the delimiter for liquid outputs **/ + outputDelimiterLeft?: string, + outputDelimiterRight?: string, + /** `greedy` is used to specify whether `trim*Left`/`trim*Right` is greedy. When set to `true`, all consecutive blank characters including `\n` will be trimed regardless of line breaks. Defaults to `true`. */ greedy?: boolean } @@ -42,16 +41,16 @@ export interface NormalizedFullOptions extends NormalizedOptions { extname: string cache: boolean dynamicPartials: boolean - strict_filters: boolean - strict_variables: boolean - trim_tag_right: boolean - trim_tag_left: boolean - trim_output_right: boolean - trim_output_left: boolean - tag_delimiter_left: string, - tag_delimiter_right: string, - output_delimiter_left: string, - output_delimiter_right: string, + strictFilters: boolean + strictVariables: boolean + trimTagRight: boolean + trimTagLeft: boolean + trimOutputRight: boolean + trimOutputLeft: boolean + tagDelimiterLeft: string, + tagDelimiterRight: string, + outputDelimiterLeft: string, + outputDelimiterRight: string, greedy: boolean } @@ -60,17 +59,17 @@ const defaultOptions: NormalizedFullOptions = { cache: false, extname: '', dynamicPartials: true, - trim_tag_right: false, - trim_tag_left: false, - trim_output_right: false, - trim_output_left: false, + trimTagRight: false, + trimTagLeft: false, + trimOutputRight: false, + trimOutputLeft: false, greedy: true, - tag_delimiter_left: '{%', - tag_delimiter_right: '%}', - output_delimiter_left: '{{', - output_delimiter_right: '}}', - strict_filters: false, - strict_variables: false + tagDelimiterLeft: '{%', + tagDelimiterRight: '%}', + outputDelimiterLeft: '{{', + outputDelimiterRight: '}}', + strictFilters: false, + strictVariables: false } export function normalize (options?: LiquidOptions): NormalizedOptions { @@ -78,6 +77,13 @@ export function normalize (options?: LiquidOptions): NormalizedOptions { if (options.hasOwnProperty('root')) { options.root = normalizeStringArray(options.root) } + for (const key of Object.keys(options)) { + if (key.indexOf('_') > -1) { + const newKey = key.replace(/_([a-z])/g, (_, ch) => ch.toUpperCase()) + deprecate(`${key} is deprecated, use ${newKey} instead.`, 109) + options[newKey] = options[key] + } + } return options as NormalizedOptions } diff --git a/src/liquid.ts b/src/liquid.ts index 7610ea1314..4b9989f9ba 100644 --- a/src/liquid.ts +++ b/src/liquid.ts @@ -70,7 +70,7 @@ export default class Liquid { return this.render(templates, ctx, opts) } evalValue (str: string, scope: Scope) { - return new Value(str, this.options.strict_filters).value(scope) + return new Value(str, this.options.strictFilters).value(scope) } registerFilter (name: string, filter: FilterImpl) { return Filter.register(name, filter) diff --git a/src/parser/parser.ts b/src/parser/parser.ts index defca40bb1..bf1f5fc295 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -29,7 +29,7 @@ export default class Parser { return new Tag(token as TagToken, remainTokens, this.liquid) } if (token.type === 'output') { - return new Output(token as OutputToken, this.liquid.options.strict_filters) + return new Output(token as OutputToken, this.liquid.options.strictFilters) } return new HTML(token) } catch (e) { diff --git a/src/parser/tokenizer.ts b/src/parser/tokenizer.ts index 85781135cc..02c41414d8 100644 --- a/src/parser/tokenizer.ts +++ b/src/parser/tokenizer.ts @@ -15,10 +15,10 @@ export default class Tokenizer { } tokenize (input: string, file?: string) { const tokens: Token[] = [] - const tagL = this.options.tag_delimiter_left - const tagR = this.options.tag_delimiter_right - const outputL = this.options.output_delimiter_left - const outputR = this.options.output_delimiter_right + const tagL = this.options.tagDelimiterLeft + const tagR = this.options.tagDelimiterRight + const outputL = this.options.outputDelimiterLeft + const outputR = this.options.outputDelimiterRight let p = 0 let curLine = 1 let state = ParseState.HTML diff --git a/src/parser/whitespace-ctrl.ts b/src/parser/whitespace-ctrl.ts index 30d00e91ce..ce7d8600b9 100644 --- a/src/parser/whitespace-ctrl.ts +++ b/src/parser/whitespace-ctrl.ts @@ -23,14 +23,14 @@ export default function whiteSpaceCtrl (tokens: Token[], options: NormalizedFull function shouldTrimLeft (token: DelimitedToken, inRaw: boolean, options: NormalizedFullOptions) { if (inRaw) return false - if (token.type === 'tag') return token.trimLeft || options.trim_tag_left - if (token.type === 'output') return token.trimLeft || options.trim_output_left + if (token.type === 'tag') return token.trimLeft || options.trimTagLeft + if (token.type === 'output') return token.trimLeft || options.trimOutputLeft } function shouldTrimRight (token: DelimitedToken, inRaw: boolean, options: NormalizedFullOptions) { if (inRaw) return false - if (token.type === 'tag') return token.trimRight || options.trim_tag_right - if (token.type === 'output') return token.trimRight || options.trim_output_right + if (token.type === 'tag') return token.trimRight || options.trimTagRight + if (token.type === 'output') return token.trimRight || options.trimOutputRight } function trimLeft (token: Token, greedy: boolean) { diff --git a/src/scope/scope.ts b/src/scope/scope.ts index f225beb403..91a37e1eda 100644 --- a/src/scope/scope.ts +++ b/src/scope/scope.ts @@ -1,4 +1,5 @@ import * as _ from '../util/underscore' +import { Drop } from '../drop/drop' import { __assign } from 'tslib' import assert from '../util/assert' import { NormalizedFullOptions, applyDefault } from '../liquid-options' @@ -27,7 +28,13 @@ export default class Scope { get (path: string): any { const paths = this.propertyAccessSeq(path) const scope = this.findContextFor(paths[0]) || _.last(this.contexts) - return paths.reduce((value, key) => this.readProperty(value, key), scope) + return paths.reduce((value, key) => { + const val = this.readProperty(value, key) + if (_.isNil(val) && this.opts.strictVariables) { + throw new TypeError(`undefined variable: ${key}`) + } + return val + }, scope) } set (path: string, v: any): void { const paths = this.propertyAccessSeq(path) @@ -74,26 +81,20 @@ export default class Scope { return null } private readProperty (obj: Context, key: string) { - let val - if (_.isNil(obj)) { - val = obj - } else { - obj = toLiquid(obj) - val = key === 'size' ? readSize(obj) : obj[key] - if (_.isFunction(obj.liquid_method_missing)) { - val = obj.liquid_method_missing!(key) - } + if (_.isNil(obj)) return obj + obj = _.toLiquid(obj) + if (obj instanceof Drop) { + if (_.isFunction(obj[key])) return obj[key]() + if (obj.hasOwnProperty(key)) return obj[key] + return obj.liquidMethodMissing(key) } - if (_.isNil(val) && this.opts.strict_variables) { - throw new TypeError(`undefined variable: ${key}`) - } - return val + return key === 'size' ? readSize(obj) : obj[key] } /* * Parse property access sequence from access string * @example - * accessSeq("foo.bar") // ['foo', 'bar'] + * accessSeq("foo.bar") // ['foo', 'bar'] * accessSeq("foo['bar']") // ['foo', 'bar'] * accessSeq("foo['b]r']") // ['foo', 'b]r'] * accessSeq("foo[bar.coo]") // ['foo', 'bar'], for bar.coo == 'bar' @@ -150,16 +151,6 @@ export default class Scope { } } -function toLiquid (obj: Context) { - if (_.isFunction(obj.to_liquid)) { - return obj.to_liquid() - } - if (_.isFunction(obj.toLiquid)) { - return obj.toLiquid() - } - return obj -} - function readSize (obj: Context) { if (!_.isNil(obj.size)) return obj.size if (_.isArray(obj) || _.isString(obj)) return obj.length diff --git a/src/types.ts b/src/types.ts index 2d4c388d72..2de9b31e67 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,2 +1,3 @@ export { AssignScope, CaptureScope, IncrementScope, DecrementScope } from './scope/scopes' export { ParseError, TokenizationError, RenderBreakError, AssertionError } from './util/error' +export { Drop } from './drop/drop' diff --git a/src/util/deprecate.ts b/src/util/deprecate.ts new file mode 100644 index 0000000000..367d5f8e6e --- /dev/null +++ b/src/util/deprecate.ts @@ -0,0 +1,7 @@ +const reported:{[key: string]: boolean} = {} + +export function deprecate(msg: string, issue: number) { + if (reported[msg]) return + console.warn(msg + ` See: https://github.com/harttle/liquidjs/issues/${issue}`) + reported[msg] = true +} \ No newline at end of file diff --git a/src/util/underscore.ts b/src/util/underscore.ts index a9a3b20387..c0eb5ff529 100644 --- a/src/util/underscore.ts +++ b/src/util/underscore.ts @@ -1,5 +1,5 @@ +import { deprecate } from './deprecate' const toStr = Object.prototype.toString -const arrToStr = Array.prototype.toString /* * Checks if value is classified as a String primitive or object. @@ -28,25 +28,21 @@ export function promisify (fn: any) { export function stringify (value: any): string { if (isNil(value)) return '' - if (isFunction(value.to_liquid)) return stringify(value.to_liquid()) - if (isFunction(value.toLiquid)) return stringify(value.toLiquid()) - if (isFunction(value.to_s)) return value.to_s() - if ([toStr, arrToStr].indexOf(value.toString) > -1) return defaultToString(value) - if (isFunction(value.toString)) return value.toString() - return toStr.call(value) + value = toLiquid(value) + if (isFunction(value.to_s)) { + deprecate('to_s is deprecated, use toString instead.', 109) + return value.to_s() + } + return String(value) } -function defaultToString (value: any): string { - const cache: any[] = [] - return JSON.stringify(value, (key, value) => { - if (isObject(value)) { - if (cache.indexOf(value) !== -1) { - return - } - cache.push(value) - } - return value - }) +export function toLiquid (value: any): any { + if (isFunction(value.to_liquid)) { + deprecate('to_liquid is deprecated, use toLiquid instead.', 109) + return toLiquid(value.to_liquid()) + } + if (isFunction(value.toLiquid)) return toLiquid(value.toLiquid()) + return value } export function create (proto: T1): T2 { diff --git a/test/e2e/drop.ts b/test/e2e/drop.ts index 8ee11a59a1..f80f6f1484 100644 --- a/test/e2e/drop.ts +++ b/test/e2e/drop.ts @@ -4,25 +4,38 @@ import * as chaiAsPromised from 'chai-as-promised' use(chaiAsPromised) +class SettingsDrop extends Liquid.Types.Drop { + foo: string = 'FOO' + bar() { + return 'BAR' + } + liquidMethodMissing(key: string) { + return key.toUpperCase() + } +} + describe('drop', function () { - var engine: Liquid + const settings = new SettingsDrop() + let engine: Liquid beforeEach(function () { engine = new Liquid() }) - it('should support liquid_method_missing', async function () { + it('should support liquidMethodMissing', async function () { let i = 0 - const src = `{{settings.foo}},{{settings.foo}},{{settings.foo}}` - const ctx = { settings: { liquid_method_missing: () => i++ } } - const html = await engine.parseAndRender(src, ctx) - return expect(html).to.equal('0,1,2') + const src = `{{settings.foo}},{{settings.bar}},{{settings.coo}}` + const html = await engine.parseAndRender(src, { settings }) + return expect(html).to.equal('FOO,BAR,COO') }) - it('should test blank strings', async function () { - const src = ` - {% unless settings.fp_heading == blank %} -

{{ settings.fp_heading }}

- {% endunless %}` - var ctx = { settings: { fp_heading: '' } } - const html = await engine.parseAndRender(src, ctx) - return expect(html).to.match(/^\s+$/) + + describe('BlandDrop', function () { + it('should test blank strings', async function () { + const src = ` + {% unless settings.fp_heading == blank %} +

{{ settings.fp_heading }}

+ {% endunless %}` + var ctx = { settings: { fp_heading: '' } } + const html = await engine.parseAndRender(src, ctx) + return expect(html).to.match(/^\s+$/) + }) }) }) diff --git a/test/e2e/parse-and-render.ts b/test/e2e/parse-and-render.ts index b3216002b0..16b64dd38f 100644 --- a/test/e2e/parse-and-render.ts +++ b/test/e2e/parse-and-render.ts @@ -9,18 +9,13 @@ describe('.parseAndRender()', function () { beforeEach(function () { engine = new Liquid() strictEngine = new Liquid({ - strict_filters: true + strictFilters: true }) }) - it('should stringify object', async function () { - var ctx = { obj: { foo: 'bar' } } - const html = await engine.parseAndRender('{{obj}}', ctx) - return expect(html).to.equal('{"foo":"bar"}') - }) it('should stringify array ', async function () { var ctx = { arr: [-2, 'a'] } const html = await engine.parseAndRender('{{arr}}', ctx) - return expect(html).to.equal('[-2,"a"]') + return expect(html).to.equal('-2,a') }) it('should render undefined as empty', async function () { const html = await engine.parseAndRender('foo{{zzz}}bar', {}) @@ -30,7 +25,7 @@ describe('.parseAndRender()', function () { const html = await engine.parseAndRender('{{"foo" | filter1}}', {}) return expect(html).to.equal('foo') }) - it('should throw upon undefined filter when strict_filters set', function () { + it('should throw upon undefined filter when strictFilters set', function () { return expect(strictEngine.parseAndRender('{{"foo" | filter1}}', {})).to .be.rejectedWith(/undefined filter: filter1/) }) @@ -42,13 +37,13 @@ describe('.parseAndRender()', function () { engine.parse('{{obj}}') }).to.not.throw() }) - it('should render template multiple times', async function () { - const ctx = { obj: { foo: 'bar' } } + it('template should be able to be rendered multiple times', async function () { + const ctx = { obj: [1, 2] } const template = engine.parse('{{obj}}') const result = await engine.render(template, ctx) - expect(result).to.equal('{"foo":"bar"}') + expect(result).to.equal('1,2') const result2 = await engine.render(template, ctx) - expect(result2).to.equal('{"foo":"bar"}') + expect(result2).to.equal('1,2') }) it('should render filters', async function () { var ctx = { names: ['alice', 'bob'] } diff --git a/test/integration/builtin/filters/array.ts b/test/integration/builtin/filters/array.ts index 902338da02..a062e073ef 100644 --- a/test/integration/builtin/filters/array.ts +++ b/test/integration/builtin/filters/array.ts @@ -19,7 +19,7 @@ describe('filters/array', function () { return test(src, 'tiger') }) it('should support map', function () { - return test('{{posts | map: "category"}}', '["foo","bar"]') + return test('{{posts | map: "category"}}', 'foo,bar') }) it('should support reverse', function () { return test('{{ "Ground control to Major Tom." | split: "" | reverse | join: "" }}', diff --git a/test/integration/builtin/filters/date.ts b/test/integration/builtin/filters/date.ts index 9f111bd7b1..feb3811b00 100644 --- a/test/integration/builtin/filters/date.ts +++ b/test/integration/builtin/filters/date.ts @@ -15,6 +15,6 @@ describe('filters/date', function () { return test('{{ "foo" | date: "%Y"}}', 'foo') }) it('should render object as string if not valid', function () { - return test('{{ obj | date: "%Y"}}', '{"foo":"bar"}') + return test('{{ obj | date: "%Y"}}', '[object Object]') }) }) diff --git a/test/integration/builtin/tags/assign.ts b/test/integration/builtin/tags/assign.ts index 8773f95d83..0eb003ca5a 100644 --- a/test/integration/builtin/tags/assign.ts +++ b/test/integration/builtin/tags/assign.ts @@ -31,7 +31,7 @@ describe('tags/assign', function () { it('should assign as array', async function () { const src = '{% assign foo=(1..3) %}{{foo}}' const html = await liquid.parseAndRender(src) - return expect(html).to.equal('[1,2,3]') + return expect(html).to.equal('1,2,3') }) it('should assign as filter result', async function () { const src = '{% assign foo="a b" | capitalize | split: " " | first %}{{foo}}' diff --git a/test/integration/drop/drop.ts b/test/integration/drop/drop.ts new file mode 100644 index 0000000000..40adbd21e2 --- /dev/null +++ b/test/integration/drop/drop.ts @@ -0,0 +1,35 @@ +import { expect } from 'chai' +import Liquid from '../../../src/liquid' + +describe('drop/drop', function () { + let liquid: Liquid + before(() => (liquid = new Liquid())) + + class CustomDrop extends Liquid.Types.Drop { + name: string = 'NAME' + getName() { + return 'GETNAME' + } + } + class CustomDropWithMethodMissing extends CustomDrop { + liquidMethodMissing(key: string) { + return key.toUpperCase() + } + } + it('should call corresponding method', async function () { + const html = await liquid.parseAndRender(`{{obj.getName}}`, {obj: new CustomDrop()}) + expect(html).to.equal('GETNAME') + }) + it('should read corresponding property', async function () { + const html = await liquid.parseAndRender(`{{obj.name}}`, {obj: new CustomDrop()}) + expect(html).to.equal('NAME') + }) + it('should output empty string if not exist', async function () { + const html = await liquid.parseAndRender(`{{obj.foo}}`, {obj: new CustomDrop()}) + expect(html).to.equal('') + }) + it('should respect liquidMethodMissing', async function () { + const html = await liquid.parseAndRender(`{{obj.foo}}`, {obj: new CustomDropWithMethodMissing()}) + expect(html).to.equal('FOO') + }) +}) diff --git a/test/integration/liquid/delimiter.ts b/test/integration/liquid/delimiter.ts index 5da2ee5e61..f30ca05fef 100644 --- a/test/integration/liquid/delimiter.ts +++ b/test/integration/liquid/delimiter.ts @@ -4,26 +4,26 @@ import Liquid from '../../../src/liquid' describe('LiquidOptions#*_delimiter_*', function () { it('should respect tag_delimiter_*', async function () { const engine = new Liquid({ - tag_delimiter_left: '<%=', - tag_delimiter_right: '%>' + tagDelimiterLeft: '<%=', + tagDelimiterRight: '%>' }) const html = await engine.parseAndRender('<%=if true%>foo<%=endif%> ') return expect(html).to.equal('foo ') }) it('should respect output_delimiter_*', async function () { const engine = new Liquid({ - output_delimiter_left: '<<', - output_delimiter_right: '>>' + outputDelimiterLeft: '<<', + outputDelimiterRight: '>>' }) const html = await engine.parseAndRender('<< "liquid" | capitalize >>') return expect(html).to.equal('Liquid') }) it('should support trimming with tag_delimiter_* set', async function () { const engine = new Liquid({ - tag_delimiter_left: '<%=', - tag_delimiter_right: '%>', - trim_tag_left: true, - trim_tag_right: true + tagDelimiterLeft: '<%=', + tagDelimiterRight: '%>', + trimTagLeft: true, + trimTagRight: true }) const html = await engine.parseAndRender(' <%=if true%> \tfoo\t <%=endif%> ') return expect(html).to.equal('foo') diff --git a/test/integration/liquid/strict.ts b/test/integration/liquid/strict.ts index 585c87af7a..c813675f3a 100644 --- a/test/integration/liquid/strict.ts +++ b/test/integration/liquid/strict.ts @@ -10,22 +10,22 @@ describe('LiquidOptions#strict_*', function () { extname: '.html' }) }) - it('should not throw when strict_variables false (default)', async function () { + it('should not throw when strictVariables false (default)', async function () { const html = await engine.parseAndRender('before{{notdefined}}after', ctx) return expect(html).to.equal('beforeafter') }) - it('should throw when strict_variables true', function () { + it('should throw when strictVariables true', function () { const tpl = engine.parse('before{{notdefined}}after') const opts = { - strict_variables: true + strictVariables: true } return expect(engine.render(tpl, ctx, opts)).to .be.rejectedWith(/undefined variable: notdefined/) }) - it('should pass strict_variables to render by parseAndRender', function () { + it('should pass strictVariables to render by parseAndRender', function () { const html = 'before{{notdefined}}after' const opts = { - strict_variables: true + strictVariables: true } return expect(engine.parseAndRender(html, ctx, opts)).to .be.rejectedWith(/undefined variable: notdefined/) diff --git a/test/integration/liquid/trimming.ts b/test/integration/liquid/trimming.ts index 9137c06783..63c8b16a22 100644 --- a/test/integration/liquid/trimming.ts +++ b/test/integration/liquid/trimming.ts @@ -6,34 +6,34 @@ describe('LiquidOptions#trimming', function () { describe('tag trimming', function () { it('should respect trim_tag_left', async function () { - const engine = new Liquid({ trim_tag_left: true }) + const engine = new Liquid({ trim_tag_left: true } as any) const html = await engine.parseAndRender(' \n \t{%if true%}foo{%endif%} ') return expect(html).to.equal('foo ') }) it('should respect trim_tag_right', async function () { - const engine = new Liquid({ trim_tag_right: true }) + const engine = new Liquid({ trim_tag_right: true } as any) const html = await engine.parseAndRender('\t{%if true%}foo{%endif%} \n') return expect(html).to.equal('\tfoo') }) it('should not trim value', async function () { - const engine = new Liquid({ trim_tag_left: true, trim_tag_right: true }) + const engine = new Liquid({ trim_tag_left: true, trim_tag_right: true } as any) const html = await engine.parseAndRender('{%if true%}a {{name}} b{%endif%}', ctx) return expect(html).to.equal('a harttle b') }) }) describe('value trimming', function () { it('should respect trim_output_left', async function () { - const engine = new Liquid({ trim_output_left: true }) + const engine = new Liquid({ trim_output_left: true } as any) const html = await engine.parseAndRender(' \n \t{{name}} ', ctx) return expect(html).to.equal('harttle ') }) it('should respect trim_output_right', async function () { - const engine = new Liquid({ trim_output_right: true }) + const engine = new Liquid({ trim_output_right: true } as any) const html = await engine.parseAndRender(' \n \t{{name}} ', ctx) return expect(html).to.equal(' \n \tharttle') }) it('should respect not trim tag', async function () { - const engine = new Liquid({ trim_output_left: true, trim_output_right: true }) + const engine = new Liquid({ trim_output_left: true, trim_output_right: true } as any) const html = await engine.parseAndRender('\t{% if true %} aha {%endif%}\t') return expect(html).to.equal('\t aha \t') }) @@ -46,7 +46,7 @@ describe('LiquidOptions#trimming', function () { return expect(html).to.equal('aharttle') }) it('should respect to greedy:false by default', async function () { - const engine = new Liquid({ greedy: false }) + const engine = new Liquid({ greedy: false } as any) const html = await engine.parseAndRender(src, ctx) return expect(html).to.equal('\n a \nharttle ') }) diff --git a/test/integration/util/error.ts b/test/integration/util/error.ts index caf3bc9f75..ac5f567391 100644 --- a/test/integration/util/error.ts +++ b/test/integration/util/error.ts @@ -5,8 +5,8 @@ import { mock, restore } from '../../stub/mockfs' let engine = new Liquid() const strictEngine = new Liquid({ - strict_variables: true, - strict_filters: true + strictVariables: true, + strictFilters: true }) describe('error', function () { diff --git a/test/unit/drop/drop.ts b/test/unit/drop/drop.ts new file mode 100644 index 0000000000..c31285082d --- /dev/null +++ b/test/unit/drop/drop.ts @@ -0,0 +1,10 @@ +import { expect } from 'chai' +import { Drop } from '../../../src/drop/drop' + +describe('drop/drop', function () { + class CustomDrop extends Drop { } + + it('.valueOf() should return undefined by default', async function () { + expect(new CustomDrop().valueOf()).to.be.undefined + }) +}) diff --git a/test/unit/scope/scope.ts b/test/unit/scope/scope.ts index 6a18d2ccc3..dccd4781d1 100644 --- a/test/unit/scope/scope.ts +++ b/test/unit/scope/scope.ts @@ -181,11 +181,11 @@ describe('scope', function () { expect(scope.get('foo')).to.equal('bar') }) }) - describe('strict_variables', function () { + describe('strictVariables', function () { let scope: Scope beforeEach(function () { scope = new Scope(ctx, { - strict_variables: true + strictVariables: true } as any) }) it('should throw when variable not defined', function () { diff --git a/test/unit/template/output.ts b/test/unit/template/output.ts index e8b773e835..c35e3e5e5e 100644 --- a/test/unit/template/output.ts +++ b/test/unit/template/output.ts @@ -25,20 +25,13 @@ describe('Output', function () { }) const output = new Output({ value: 'foo' } as OutputToken, false) const html = await output.render(scope) - return expect(html).to.equal('{"obj":{"arr":["a",2]}}') - }) - it('should skip circular property', async function () { - const ctx = { foo: { num: 2 }, bar: 'bar' } as any - ctx.foo.circular = ctx - const output = new Output({ value: 'foo' } as OutputToken, false) - const html = await output.render(new Scope(ctx)) - return expect(html).equal('{"num":2,"circular":{"bar":"bar"}}') + return expect(html).to.equal('[object Object]') }) it('should skip function property', async function () { const scope = new Scope({ obj: { foo: 'foo', bar: (x: any) => x } }) const output = new Output({ value: 'obj' } as OutputToken, false) const html = await output.render(scope) - return expect(html).to.equal('{"foo":"foo"}') + return expect(html).to.equal('[object Object]') }) it('should respect to .toString()', async () => { const scope = new Scope({ obj: { toString: () => 'FOO' } }) @@ -52,9 +45,9 @@ describe('Output', function () { const str = await output.render(scope) return expect(str).to.equal('FOO') }) - it('should respect to .liquid_method_missing()', async () => { - const scope = new Scope({ obj: { liquid_method_missing: (x: string) => x.toUpperCase() } }) - const output = new Output({ value: 'obj.foo' } as OutputToken, false) + it('should respect to .toString()', async () => { + const scope = new Scope({ obj: { toString: () => 'FOO' } }) + const output = new Output({ value: 'obj' } as OutputToken, false) const str = await output.render(scope) return expect(str).to.equal('FOO') }) diff --git a/test/unit/util/underscore.ts b/test/unit/util/underscore.ts index e613ff603e..e696e19b03 100644 --- a/test/unit/util/underscore.ts +++ b/test/unit/util/underscore.ts @@ -34,10 +34,6 @@ describe('util/underscore', function () { it('should return "" for undefined', function () { expect(_.stringify(undefined)).to.equal('') }) - it('should use Object.prototype.toString if no toString method exists', function () { - const obj = { toString: undefined } - expect(_.stringify(obj)).to.equal('[object Object]') - }) it('should return regex string for RegExp', function () { const reg = /foo/g expect(_.stringify(reg)).to.equal('/foo/g')