chore(TypeScript): fix linting and generate .d.ts

This commit is contained in:
harttle
2019-02-17 18:20:04 +08:00
parent 7ee87008c7
commit fc9ebdb90f
79 changed files with 809 additions and 820 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
node_modules node_modules
dist dist
build demo
coverage coverage
+3 -1
View File
@@ -15,6 +15,8 @@
], ],
"rules": { "rules": {
"no-var": 2, "no-var": 2,
"prefer-const": 2 "prefer-const": 2,
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { "vars": "all", "args": "after-used", "ignoreRestSiblings": false }]
} }
} }
+1 -1
View File
@@ -6,7 +6,7 @@
"types": "dist/liquid.d.ts", "types": "dist/liquid.d.ts",
"browser": "dist/liquid.js", "browser": "dist/liquid.js",
"scripts": { "scripts": {
"lint": "eslint src/ test/ *.js", "lint": "eslint . --ext .ts --ext .js",
"unit": "mocha -r ts-node/register -r tsconfig-paths/register test/unit/**.ts", "unit": "mocha -r ts-node/register -r tsconfig-paths/register test/unit/**.ts",
"e2e": "npm run build && mocha -r ts-node/register -r tsconfig-paths/register test/e2e/**/*.ts", "e2e": "npm run build && mocha -r ts-node/register -r tsconfig-paths/register test/e2e/**/*.ts",
"test": "npm run unit && npm run e2e", "test": "npm run unit && npm run e2e",
+1 -1
View File
@@ -25,5 +25,5 @@ export default {
'escape': escape, 'escape': escape,
'escape_once': str => escape(unescape(str)), 'escape_once': str => escape(unescape(str)),
'newline_to_br': v => v.replace(/\n/g, '<br />'), 'newline_to_br': v => v.replace(/\n/g, '<br />'),
'strip_html': v => String(v).replace(/<script.*?<\/script>|<!--.*?-->|<style.*?<\/style>|<.*?>/g, ''), 'strip_html': v => String(v).replace(/<script.*?<\/script>|<!--.*?-->|<style.*?<\/style>|<.*?>/g, '')
} }
+1 -1
View File
@@ -10,7 +10,7 @@ export default {
return Math.round(v * amp) / amp return Math.round(v * amp) / amp
}, },
'plus': bindFixed((v, arg) => Number(v) + Number(arg)), 'plus': bindFixed((v, arg) => Number(v) + Number(arg)),
'times': (v, arg) => v * arg, 'times': (v, arg) => v * arg
} }
function getFixed (v) { function getFixed (v) {
+1 -1
View File
@@ -28,5 +28,5 @@ export default {
let ret = arr.slice(0, l).join(' ') let ret = arr.slice(0, l).join(' ')
if (arr.length > l) ret += o if (arr.length > l) ret += o
return ret return ret
}, }
} }
+1 -1
View File
@@ -1,7 +1,7 @@
import { RenderBreakError } from 'src/util/error' import { RenderBreakError } from 'src/util/error'
export default { export default {
render: async function (scope) { render: async function () {
throw new RenderBreakError('break') throw new RenderBreakError('break')
} }
} }
+7 -4
View File
@@ -1,11 +1,14 @@
import assert from 'src/util/assert' import assert from 'src/util/assert'
import { identifier } from 'src/parser/lexical' import { identifier } from 'src/parser/lexical'
import { CaptureScope } from 'src/scope/scopes' import { CaptureScope } from 'src/scope/scopes'
import TagToken from 'src/parser/tag-token'
import Token from 'src/parser/token'
import Scope from 'src/scope/scope'
const re = new RegExp(`(${identifier.source})`) const re = new RegExp(`(${identifier.source})`)
export default { export default {
parse: function (tagToken, remainTokens) { parse: function (tagToken: TagToken, remainTokens: Token[]) {
const match = tagToken.args.match(re) const match = tagToken.args.match(re)
assert(match, `${tagToken.args} not valid identifier`) assert(match, `${tagToken.args} not valid identifier`)
@@ -13,14 +16,14 @@ export default {
this.templates = [] this.templates = []
const stream = this.liquid.parser.parseStream(remainTokens) const stream = this.liquid.parser.parseStream(remainTokens)
stream.on('tag:endcapture', token => stream.stop()) stream.on('tag:endcapture', () => stream.stop())
.on('template', tpl => this.templates.push(tpl)) .on('template', tpl => this.templates.push(tpl))
.on('end', x => { .on('end', () => {
throw new Error(`tag ${tagToken.raw} not closed`) throw new Error(`tag ${tagToken.raw} not closed`)
}) })
stream.start() stream.start()
}, },
render: async function (scope, hash) { render: async function (scope: Scope) {
const html = await this.liquid.renderer.renderTemplates(this.templates, scope) const html = await this.liquid.renderer.renderTemplates(this.templates, scope)
const ctx = new CaptureScope() const ctx = new CaptureScope()
ctx[this.variable] = html ctx[this.variable] = html
+3 -3
View File
@@ -15,16 +15,16 @@ export default {
}) })
}) })
.on('tag:else', () => (p = this.elseTemplates)) .on('tag:else', () => (p = this.elseTemplates))
.on('tag:endcase', token => stream.stop()) .on('tag:endcase', () => stream.stop())
.on('template', tpl => p.push(tpl)) .on('template', tpl => p.push(tpl))
.on('end', x => { .on('end', () => {
throw new Error(`tag ${tagToken.raw} not closed`) throw new Error(`tag ${tagToken.raw} not closed`)
}) })
stream.start() stream.start()
}, },
render: function (scope, hash) { render: function (scope) {
for (let i = 0; i < this.cases.length; i++) { for (let i = 0; i < this.cases.length; i++) {
const branch = this.cases[i] const branch = this.cases[i]
const val = evalExp(branch.val, scope) const val = evalExp(branch.val, scope)
+1 -1
View File
@@ -5,7 +5,7 @@ export default {
.on('token', token => { .on('token', token => {
if (token.name === 'endcomment') stream.stop() if (token.name === 'endcomment') stream.stop()
}) })
.on('end', x => { .on('end', () => {
throw new Error(`tag ${tagToken.raw} not closed`) throw new Error(`tag ${tagToken.raw} not closed`)
}) })
stream.start() stream.start()
+1 -1
View File
@@ -1,7 +1,7 @@
import { RenderBreakError } from 'src/util/error' import { RenderBreakError } from 'src/util/error'
export default { export default {
render: async function (scope) { render: async function () {
throw new RenderBreakError('continue') throw new RenderBreakError('continue')
} }
} }
+2 -2
View File
@@ -6,7 +6,7 @@ const groupRE = new RegExp(`^(?:(${rValue.source})\\s*:\\s*)?(.*)$`)
const candidatesRE = new RegExp(rValue.source, 'g') const candidatesRE = new RegExp(rValue.source, 'g')
export default { export default {
parse: function (tagToken, remainTokens) { parse: function (tagToken) {
let match = groupRE.exec(tagToken.args) let match = groupRE.exec(tagToken.args)
assert(match, `illegal tag: ${tagToken.raw}`) assert(match, `illegal tag: ${tagToken.raw}`)
@@ -21,7 +21,7 @@ export default {
assert(this.candidates.length, `empty candidates: ${tagToken.raw}`) assert(this.candidates.length, `empty candidates: ${tagToken.raw}`)
}, },
render: function (scope, hash) { render: function (scope) {
const group = evalValue(this.group, scope) const group = evalValue(this.group, scope)
const fingerprint = `cycle:${group}:` + this.candidates.join(',') const fingerprint = `cycle:${group}:` + this.candidates.join(',')
+4 -2
View File
@@ -1,14 +1,16 @@
import assert from 'src/util/assert' import assert from 'src/util/assert'
import { identifier } from 'src/parser/lexical' import { identifier } from 'src/parser/lexical'
import { CaptureScope, AssignScope, DecrementScope } from 'src/scope/scopes' import { CaptureScope, AssignScope, DecrementScope } from 'src/scope/scopes'
import TagToken from 'src/parser/tag-token'
import Scope from 'src/scope/scope'
export default { export default {
parse: function (token) { parse: function (token: TagToken) {
const match = token.args.match(identifier) const match = token.args.match(identifier)
assert(match, `illegal identifier ${token.args}`) assert(match, `illegal identifier ${token.args}`)
this.variable = match[0] this.variable = match[0]
}, },
render: function (scope, hash) { render: function (scope: Scope) {
let context = scope.findContextFor( let context = scope.findContextFor(
this.variable, this.variable,
ctx => { ctx => {
+3 -3
View File
@@ -18,16 +18,16 @@ export default {
}) })
}) })
.on('tag:else', () => (p = this.elseTemplates)) .on('tag:else', () => (p = this.elseTemplates))
.on('tag:endif', token => stream.stop()) .on('tag:endif', () => stream.stop())
.on('template', tpl => p.push(tpl)) .on('template', tpl => p.push(tpl))
.on('end', x => { .on('end', () => {
throw new Error(`tag ${tagToken.raw} not closed`) throw new Error(`tag ${tagToken.raw} not closed`)
}) })
stream.start() stream.start()
}, },
render: function (scope, hash) { render: function (scope) {
for (const branch of this.branches) { for (const branch of this.branches) {
const cond = evalExp(branch.cond, scope) const cond = evalExp(branch.cond, scope)
if (isTruthy(cond)) { if (isTruthy(cond)) {
+1 -1
View File
@@ -8,7 +8,7 @@ export default {
assert(match, `illegal identifier ${token.args}`) assert(match, `illegal identifier ${token.args}`)
this.variable = match[0] this.variable = match[0]
}, },
render: function (scope, hash) { render: function (scope) {
let context = scope.findContextFor( let context = scope.findContextFor(
this.variable, this.variable,
ctx => { ctx => {
+4 -1
View File
@@ -15,7 +15,10 @@ import tablerow from './tablerow'
import unless from './unless' import unless from './unless'
import Break from './break' import Break from './break'
import Continue from './continue' import Continue from './continue'
import ITagImplOptions from 'src/template/tag/itag-impl-options'
export default { const tags: { [key: string]: ITagImplOptions } = {
assign, 'for': For, capture, 'case': Case, comment, include, decrement, increment, cycle, 'if': If, layout, block, raw, tablerow, unless, 'break': Break, 'continue': Continue assign, 'for': For, capture, 'case': Case, comment, include, decrement, increment, cycle, 'if': If, layout, block, raw, tablerow, unless, 'break': Break, 'continue': Continue
} }
export default tags
+1 -1
View File
@@ -13,7 +13,7 @@ export default {
}) })
stream.start() stream.start()
}, },
render: function (scope, hash) { render: function () {
return this.tokens.map(token => token.raw).join('') return this.tokens.map(token => token.raw).join('')
} }
} }
+2 -2
View File
@@ -19,7 +19,7 @@ export default {
let p let p
const stream = this.liquid.parser.parseStream(remainTokens) const stream = this.liquid.parser.parseStream(remainTokens)
.on('start', () => (p = this.templates)) .on('start', () => (p = this.templates))
.on('tag:endtablerow', token => stream.stop()) .on('tag:endtablerow', () => stream.stop())
.on('template', tpl => p.push(tpl)) .on('template', tpl => p.push(tpl))
.on('end', () => { .on('end', () => {
throw new Error(`tag ${tagToken.raw} not closed`) throw new Error(`tag ${tagToken.raw} not closed`)
@@ -35,7 +35,7 @@ export default {
collection = collection.slice(offset, offset + limit) collection = collection.slice(offset, offset + limit)
const cols = hash.cols || collection.length const cols = hash.cols || collection.length
const contexts = collection.map((item, i) => { const contexts = collection.map(item => {
const ctx = {} const ctx = {}
ctx[this.variable] = item ctx[this.variable] = item
return ctx return ctx
+4 -4
View File
@@ -6,21 +6,21 @@ export default {
this.elseTemplates = [] this.elseTemplates = []
let p let p
const stream = this.liquid.parser.parseStream(remainTokens) const stream = this.liquid.parser.parseStream(remainTokens)
.on('start', x => { .on('start', () => {
p = this.templates p = this.templates
this.cond = tagToken.args this.cond = tagToken.args
}) })
.on('tag:else', () => (p = this.elseTemplates)) .on('tag:else', () => (p = this.elseTemplates))
.on('tag:endunless', token => stream.stop()) .on('tag:endunless', () => stream.stop())
.on('template', tpl => p.push(tpl)) .on('template', tpl => p.push(tpl))
.on('end', x => { .on('end', () => {
throw new Error(`tag ${tagToken.raw} not closed`) throw new Error(`tag ${tagToken.raw} not closed`)
}) })
stream.start() stream.start()
}, },
render: function (scope, hash) { render: function (scope) {
const cond = evalExp(this.cond, scope) const cond = evalExp(this.cond, scope)
return isFalsy(cond) return isFalsy(cond)
? this.liquid.renderer.renderTemplates(this.templates, scope) ? this.liquid.renderer.renderTemplates(this.templates, scope)
+7 -7
View File
@@ -1,5 +1,5 @@
export interface LiquidOptions { export interface LiquidOptions {
/** `root` is a directory or an array of directories to resolve layouts and includes, as well as the filename passed in when calling `.renderFile()`. If an array, the files are looked up in the order they occur in the array. Defaults to `["."]`*/ /** `root` is a directory or an array of directories to resolve layouts and includes, as well as the filename passed in when calling `.renderFile()`. If an array, the files are looked up in the order they occur in the array. Defaults to `["."]` */
root?: string | string[] root?: string | string[]
/** `extname` is used to lookup the template file when filepath doesn't include an extension name. Eg: setting to `".html"` will allow including file by basename. Defaults to `""`. */ /** `extname` is used to lookup the template file when filepath doesn't include an extension name. Eg: setting to `".html"` will allow including file by basename. Defaults to `""`. */
extname?: string extname?: string
@@ -8,17 +8,17 @@ export interface LiquidOptions {
/** `dynamicPartials`: if set, treat `<filepath>` parameter in `{%include filepath %}`, `{%layout filepath%}` as a variable, otherwise as a literal value. Defaults to `true`. */ /** `dynamicPartials`: if set, treat `<filepath>` parameter in `{%include filepath %}`, `{%layout filepath%}` as a variable, otherwise as a literal value. Defaults to `true`. */
dynamicPartials?: boolean 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` 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_filters?: boolean // eslint-disable-line
/** `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` 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 strict_variables?: boolean // eslint-disable-line
/** `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` 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_right?: boolean // eslint-disable-line
/** `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` is similar to `trim_tag_right`, whereas the `\n` is exclusive. Defaults to `false`. See Whitespace Control for details. */
trim_tag_left?: boolean trim_tag_left?: boolean // eslint-disable-line
/** ``trim_value_right` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of values (`{{ }}`) until `\n` (inclusive). Defaults to `false`. */ /** ``trim_value_right` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of values (`{{ }}`) until `\n` (inclusive). Defaults to `false`. */
trim_value_right?: boolean trim_value_right?: boolean // eslint-disable-line
/** `trim_value_left` is similar to `trim_value_right`, whereas the `\n` is exclusive. Defaults to `false`. See Whitespace Control for details. */ /** `trim_value_left` is similar to `trim_value_right`, whereas the `\n` is exclusive. Defaults to `false`. See Whitespace Control for details. */
trim_value_left?: boolean trim_value_left?: boolean // eslint-disable-line
/** `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`. */
greedy?: boolean greedy?: boolean
} }
+9 -7
View File
@@ -3,7 +3,7 @@ import * as Types from './types'
import * as template from 'template' import * as template from 'template'
import * as _ from './util/underscore' import * as _ from './util/underscore'
import ITemplate from './template/itemplate' import ITemplate from './template/itemplate'
import * as tokenizer from './parser/tokenizer' import Tokenizer from './parser/tokenizer'
import Render from './render/render' import Render from './render/render'
import Tag from './template/tag/tag' import Tag from './template/tag/tag'
import Filter from './template/filter' import Filter from './template/filter'
@@ -20,6 +20,7 @@ export default class Liquid {
private cache: object private cache: object
private parser: Parser private parser: Parser
private renderer: Render private renderer: Render
private tokenizer: Tokenizer
constructor (options: LiquidOptions = {}) { constructor (options: LiquidOptions = {}) {
options = _.assign({}, defaultOptions, options) options = _.assign({}, defaultOptions, options)
@@ -31,31 +32,32 @@ export default class Liquid {
this.options = options this.options = options
this.parser = new Parser(this) this.parser = new Parser(this)
this.renderer = new Render() this.renderer = new Render()
this.tokenizer = new Tokenizer(this.options)
_.forOwn(builtinTags, (conf, name) => this.registerTag(name, conf)) _.forOwn(builtinTags, (conf, name) => this.registerTag(name, conf))
_.forOwn(builtinFilters, (handler, name) => this.registerFilter(name, handler)) _.forOwn(builtinFilters, (handler, name) => this.registerFilter(name, handler))
} }
parse(html: string, filepath?: string) { parse (html: string, filepath?: string) {
const tokens = tokenizer.parse(html, filepath, this.options) const tokens = this.tokenizer.tokenize(html, filepath)
return this.parser.parse(tokens) return this.parser.parse(tokens)
} }
render(tpl: Array<ITemplate>, ctx?: object, opts?: LiquidOptions) { render (tpl: Array<ITemplate>, ctx?: object, opts?: LiquidOptions) {
opts = _.assign({}, this.options, opts) opts = _.assign({}, this.options, opts)
const scope = new Scope(ctx, opts) const scope = new Scope(ctx, opts)
return this.renderer.renderTemplates(tpl, scope) return this.renderer.renderTemplates(tpl, scope)
} }
async parseAndRender(html: string, ctx?: object, opts?: LiquidOptions) { async parseAndRender (html: string, ctx?: object, opts?: LiquidOptions) {
const tpl = await this.parse(html) const tpl = await this.parse(html)
return this.render(tpl, ctx, opts) return this.render(tpl, ctx, opts)
} }
async getTemplate(file, root) { async getTemplate (file, root) {
const filepath = await template.resolve(file, root, this.options) const filepath = await template.resolve(file, root, this.options)
return this.respectCache(filepath, async () => { return this.respectCache(filepath, async () => {
const str = await template.read(filepath) const str = await template.read(filepath)
return this.parse(str, filepath) return this.parse(str, filepath)
}) })
} }
async renderFile(file, ctx?: object, opts?: LiquidOptions) { async renderFile (file, ctx?: object, opts?: LiquidOptions) {
opts = _.assign({}, opts) opts = _.assign({}, opts)
const templates = await this.getTemplate(file, opts.root) const templates = await this.getTemplate(file, opts.root)
return this.render(templates, ctx, opts) return this.render(templates, ctx, opts)
+6 -6
View File
@@ -1,12 +1,12 @@
import Token from './token' import Token from './token'
export default class DelimitedToken extends Token { export default class DelimitedToken extends Token {
trim_left: boolean trimLeft: boolean
trim_right: boolean trimRight: boolean
constructor(raw, pos, input, file, line) { constructor (raw, pos, input, file, line) {
super(raw, pos, input, file, line) super(raw, pos, input, file, line)
this.trim_left = raw[2] === '-' this.trimLeft = raw[2] === '-'
this.trim_right = raw[raw.length - 3] === '-' this.trimRight = raw[raw.length - 3] === '-'
this.value = raw.slice(this.trim_left ? 3 : 2, this.trim_right ? -3 : -2).trim() this.value = raw.slice(this.trimLeft ? 3 : 2, this.trimRight ? -3 : -2).trim()
} }
} }
+1 -1
View File
@@ -1,7 +1,7 @@
import Token from './token' import Token from './token'
export default class HTMLToken extends Token { export default class HTMLToken extends Token {
constructor(str, begin, input, file, line) { constructor (str, begin, input, file, line) {
super(str, begin, input, file, line) super(str, begin, input, file, line)
this.type = 'html' this.type = 'html'
this.value = str this.value = str
+1 -1
View File
@@ -1,7 +1,7 @@
import DelimitedToken from './delimited-token' import DelimitedToken from './delimited-token'
export default class OutputToken extends DelimitedToken { export default class OutputToken extends DelimitedToken {
constructor(raw, pos, input, file, line) { constructor (raw, pos, input, file, line) {
super(raw, pos, input, file, line) super(raw, pos, input, file, line)
this.type = 'output' this.type = 'output'
} }
+4 -6
View File
@@ -2,18 +2,16 @@ import { ParseError } from '../util/error'
import Liquid from 'src/liquid' import Liquid from 'src/liquid'
import ParseStream from './parse-stream' import ParseStream from './parse-stream'
import Token from './token' import Token from './token'
import Tag from 'src/template/tag/tag'
import HTMLToken from './html-token'
import TagToken from './tag-token' import TagToken from './tag-token'
import OutputToken from './output-token' import OutputToken from './output-token'
import Tag from 'src/template/tag/tag'
import Output from 'src/template/output' import Output from 'src/template/output'
import HTML from 'src/template/html' import HTML from 'src/template/html'
import Value from 'src/template/value'
export default class Parser { export default class Parser {
liquid: Liquid liquid: Liquid
constructor(liquid: Liquid) { constructor (liquid: Liquid) {
this.liquid = liquid this.liquid = liquid
} }
parse (tokens: Array<Token>) { parse (tokens: Array<Token>) {
@@ -27,10 +25,10 @@ export default class Parser {
parseToken (token: Token, remainTokens: Array<Token>) { parseToken (token: Token, remainTokens: Array<Token>) {
try { try {
if (token.type === 'tag') { if (token.type === 'tag') {
return new Tag(token, remainTokens, this.liquid) return new Tag(token as TagToken, remainTokens, this.liquid)
} }
if (token.type === 'output') { if (token.type === 'output') {
return new Output(token, this.liquid.options.strict_filters) return new Output(token as OutputToken, this.liquid.options.strict_filters)
} }
return new HTML(token) return new HTML(token)
} catch (e) { } catch (e) {
+1 -1
View File
@@ -5,7 +5,7 @@ import * as lexical from './lexical'
export default class TagToken extends DelimitedToken { export default class TagToken extends DelimitedToken {
name: string name: string
args: string args: string
constructor(raw, pos, input, file, line) { constructor (raw, pos, input, file, line) {
super(raw, pos, input, file, line) super(raw, pos, input, file, line)
this.type = 'tag' this.type = 'tag'
const match = this.value.match(lexical.tagLine) const match = this.value.match(lexical.tagLine)
+1 -1
View File
@@ -5,7 +5,7 @@ export default class Token {
input: string input: string
file: string file: string
value: string value: string
constructor(raw, pos, input, file, line) { constructor (raw, pos, input, file, line) {
this.line = line this.line = line
this.raw = raw this.raw = raw
this.input = input this.input = input
+44 -39
View File
@@ -2,52 +2,57 @@ import whiteSpaceCtrl from './whitespace-ctrl'
import HTMLToken from './html-token' import HTMLToken from './html-token'
import TagToken from './tag-token' import TagToken from './tag-token'
import OutputToken from './output-token' import OutputToken from './output-token'
import { LiquidOptions, defaultOptions } from 'src/liquid-options'
enum ParseState { HTML, OUTPUT, TAG } enum ParseState { HTML, OUTPUT, TAG }
export function parse (input: string, file?: string, options?) { export default class Tokenizer {
const tokens = [] options: LiquidOptions
let p = 0 constructor (options: LiquidOptions = defaultOptions) {
let line = 1 this.options = options
let state = ParseState.HTML }
let buffer = '' tokenize (input: string, file?: string) {
let bufferBegin = 0 const tokens = []
let p = 0
let line = 1
let state = ParseState.HTML
let buffer = ''
let bufferBegin = 0
while(p < input.length) { while (p < input.length) {
if (input[p] === '\n') line++ if (input[p] === '\n') line++
const bin = input.substr(p, 2) const bin = input.substr(p, 2)
if (state === ParseState.HTML) { if (state === ParseState.HTML) {
if (bin === '{{' || bin === '{%') { if (bin === '{{' || bin === '{%') {
if (buffer) tokens.push(new HTMLToken(buffer, bufferBegin, input, file, line)) if (buffer) tokens.push(new HTMLToken(buffer, bufferBegin, input, file, line))
buffer = bin buffer = bin
bufferBegin = p bufferBegin = p
p += 2
state = bin === '{{' ? ParseState.OUTPUT : ParseState.TAG
continue
}
} else if (state === ParseState.OUTPUT && bin === '}}') {
buffer += '}}'
tokens.push(new OutputToken(buffer, bufferBegin, input, file, line))
p += 2 p += 2
state = bin === '{{' ? ParseState.OUTPUT : ParseState.TAG buffer = ''
bufferBegin = p
state = ParseState.HTML
continue
} else if (bin === '%}') {
buffer += '%}'
tokens.push(new TagToken(buffer, bufferBegin, input, file, line))
p += 2
buffer = ''
bufferBegin = p
state = ParseState.HTML
continue continue
} }
buffer += input[p++]
} }
else if (state === ParseState.OUTPUT && bin === '}}') { if (buffer) tokens.push(new HTMLToken(buffer, bufferBegin, input, file, line))
buffer += '}}'
tokens.push(new OutputToken(buffer, bufferBegin, input, file, line))
p += 2
buffer = ''
bufferBegin = p
state = ParseState.HTML
continue
}
else if (bin === '%}') {
buffer += '%}'
tokens.push(new TagToken(buffer, bufferBegin, input, file, line))
p += 2
buffer = ''
bufferBegin = p
state = ParseState.HTML
continue
}
buffer += input[p++]
}
if (buffer) tokens.push(new HTMLToken(buffer, bufferBegin, input, file, line))
whiteSpaceCtrl(tokens, options) whiteSpaceCtrl(tokens, this.options)
return tokens return tokens
}
} }
+18 -17
View File
@@ -1,46 +1,47 @@
import { assign } from 'src/util/underscore' import { assign } from 'src/util/underscore'
import TagToken from './tag-token' import DelimitedToken from 'src/parser/delimited-token'
import OutputToken from './output-token' import Token from 'src/parser/token'
import HTMLToken from './html-token' import TagToken from 'src/parser/tag-token'
import { LiquidOptions } from 'src/liquid-options'
export default function whiteSpaceCtrl (tokens, options) { export default function whiteSpaceCtrl (tokens: Token[], options: LiquidOptions) {
options = assign({ greedy: true }, options) options = assign({ greedy: true }, options)
let inRaw = false let inRaw = false
tokens.forEach((token, i) => { tokens.forEach((token: Token, i: number) => {
if (shouldTrimLeft(token, inRaw, options)) { if (shouldTrimLeft(token as DelimitedToken, inRaw, options)) {
trimLeft(tokens[i - 1], options.greedy) trimLeft(tokens[i - 1], options.greedy)
} }
if (token.type === 'tag' && token.name === 'raw') inRaw = true if (token.type === 'tag' && (token as TagToken).name === 'raw') inRaw = true
if (token.type === 'tag' && token.name === 'endraw') inRaw = false if (token.type === 'tag' && (token as TagToken).name === 'endraw') inRaw = false
if (shouldTrimRight(token, inRaw, options)) { if (shouldTrimRight(token as DelimitedToken, inRaw, options)) {
trimRight(tokens[i + 1], options.greedy) trimRight(tokens[i + 1], options.greedy)
} }
}) })
} }
function shouldTrimLeft (token, inRaw, options) { function shouldTrimLeft (token: DelimitedToken, inRaw: boolean, options) {
if (inRaw) return false if (inRaw) return false
if (token.type === 'tag') return token.trim_left || options.trim_tag_left if (token.type === 'tag') return token.trimLeft || options.trim_tag_left
if (token.type === 'output') return token.trim_left || options.trim_value_left if (token.type === 'output') return token.trimLeft || options.trim_value_left
} }
function shouldTrimRight (token, inRaw, options) { function shouldTrimRight (token: DelimitedToken, inRaw: boolean, options) {
if (inRaw) return false if (inRaw) return false
if (token.type === 'tag') return token.trim_right || options.trim_tag_right if (token.type === 'tag') return token.trimRight || options.trim_tag_right
if (token.type === 'output') return token.trim_right || options.trim_value_right if (token.type === 'output') return token.trimRight || options.trim_value_right
} }
function trimLeft (token, greedy) { function trimLeft (token: Token, greedy: boolean) {
if (!token || token.type !== 'html') return if (!token || token.type !== 'html') return
const rLeft = greedy ? /\s+$/g : /[\t\r ]*$/g const rLeft = greedy ? /\s+$/g : /[\t\r ]*$/g
token.value = token.value.replace(rLeft, '') token.value = token.value.replace(rLeft, '')
} }
function trimRight (token, greedy) { function trimRight (token: Token, greedy: boolean) {
if (!token || token.type !== 'html') return if (!token || token.type !== 'html') return
const rRight = greedy ? /^\s+/g : /^[\t\r ]*\n?/g const rRight = greedy ? /^\s+/g : /^[\t\r ]*\n?/g
-2
View File
@@ -1,7 +1,5 @@
import { stringify, create } from 'src/util/underscore'
import { RenderBreakError, RenderError } from 'src/util/error' import { RenderBreakError, RenderError } from 'src/util/error'
import assert from 'src/util/assert' import assert from 'src/util/assert'
import Scope from 'src/scope/scope'
export default class Render { export default class Render {
async renderTemplates (templates, scope) { async renderTemplates (templates, scope) {
+2 -2
View File
@@ -15,7 +15,7 @@ export default class Scope {
strict_variables: false, strict_variables: false,
strict_filters: false, strict_filters: false,
root: [] root: []
} , opts) }, opts)
this.contexts = [ctx || {}] this.contexts = [ctx || {}]
} }
getAll () { getAll () {
@@ -59,7 +59,7 @@ export default class Scope {
} }
return this.contexts.splice(i, 1)[0] return this.contexts.splice(i, 1)[0]
} }
findContextFor (key: string, filter = (arg => true)) { findContextFor (key: string, filter: ((conttext: object) => boolean) = () => true) {
for (let i = this.contexts.length - 1; i >= 0; i--) { for (let i = this.contexts.length - 1; i >= 0; i--) {
const candidate = this.contexts[i] const candidate = this.contexts[i]
if (!filter(candidate)) continue if (!filter(candidate)) continue
+5 -5
View File
@@ -13,21 +13,21 @@ export default class Filter {
args: string[] args: string[]
private static impls: {[key: string]: impl} = {} private static impls: {[key: string]: impl} = {}
constructor (str: string, strict_filters: boolean = false) { constructor (str: string, strictFilters: boolean = false) {
let match = lexical.filterLine.exec(str) const match = lexical.filterLine.exec(str)
assert(match, 'illegal filter: ' + str) assert(match, 'illegal filter: ' + str)
const name = match[1] const name = match[1]
const argList = match[2] || '' const argList = match[2] || ''
const impl = Filter.impls[name] const impl = Filter.impls[name]
if (!impl && strict_filters) throw new TypeError(`undefined filter: ${name}`) if (!impl && strictFilters) throw new TypeError(`undefined filter: ${name}`)
this.name = name this.name = name
this.impl = impl || (x => x) this.impl = impl || (x => x)
this.args = this.parseArgs(argList) this.args = this.parseArgs(argList)
} }
parseArgs (argList: string): string[] { parseArgs (argList: string): string[] {
let match, args = [] let match; const args = []
while ((match = valueRE.exec(argList.trim()))) { while ((match = valueRE.exec(argList.trim()))) {
const v = match[0] const v = match[0]
const re = new RegExp(`${v}\\s*:`, 'g') const re = new RegExp(`${v}\\s*:`, 'g')
@@ -42,7 +42,7 @@ export default class Filter {
args.unshift(value) args.unshift(value)
return this.impl.apply(null, args) return this.impl.apply(null, args)
} }
static register(name, filter) { static register (name, filter) {
Filter.impls[name] = filter Filter.impls[name] = filter
} }
static clear () { static clear () {
+2 -3
View File
@@ -1,15 +1,14 @@
import Template from 'src/template/template' import Template from 'src/template/template'
import Scope from 'src/scope/scope'
import ITemplate from 'src/template/itemplate' import ITemplate from 'src/template/itemplate'
import Token from 'src/parser/token' import Token from 'src/parser/token'
export default class extends Template implements ITemplate { export default class extends Template implements ITemplate {
str: string str: string
constructor(token: Token) { constructor (token: Token) {
super(token) super(token)
this.str = token.value this.str = token.value
} }
async render(scope: Scope): Promise<string> { async render (): Promise<string> {
return this.str return this.str
} }
} }
+4 -3
View File
@@ -3,14 +3,15 @@ import { stringify } from 'src/util/underscore'
import Template from 'src/template/template' import Template from 'src/template/template'
import ITemplate from 'src/template/itemplate' import ITemplate from 'src/template/itemplate'
import Scope from 'src/scope/scope' import Scope from 'src/scope/scope'
import OutputToken from 'src/parser/output-token'
export default class Output extends Template implements ITemplate { export default class Output extends Template implements ITemplate {
value: Value value: Value
constructor(token, strict_filters?) { constructor (token: OutputToken, strictFilters?: boolean) {
super(token) super(token)
this.value = new Value(token.value, strict_filters) this.value = new Value(token.value, strictFilters)
} }
async render(scope: Scope) { async render (scope: Scope): Promise<string> {
const html = await this.value.value(scope) const html = await this.value.value(scope)
return stringify(html) return stringify(html)
} }
+1 -1
View File
@@ -9,7 +9,7 @@ import { evalValue } from 'src/render/syntax'
*/ */
export default class Hash { export default class Hash {
[key: string]: any [key: string]: any
constructor(markup, scope) { constructor (markup, scope) {
let match let match
hashCapture.lastIndex = 0 hashCapture.lastIndex = 0
while ((match = hashCapture.exec(markup))) { while ((match = hashCapture.exec(markup))) {
+1 -1
View File
@@ -6,5 +6,5 @@ import ITagImpl from './itag-impl'
export default interface ITagImplOptions { export default interface ITagImplOptions {
parse?: (this: ITagImpl, token: TagToken, remainingTokens: Array<Token>) => void parse?: (this: ITagImpl, token: TagToken, remainingTokens: Array<Token>) => void
render?: (this: ITagImpl, scope: Scope, hash: Hash) => string | Promise<string> render?: (this: ITagImpl, scope: Scope, hash: Hash) => any | Promise<any>
} }
+7 -6
View File
@@ -1,6 +1,6 @@
import { create } from 'src/util/underscore' import { create, stringify } from 'src/util/underscore'
import { stringify } from 'src/util/underscore'
import assert from 'src/util/assert' import assert from 'src/util/assert'
import Scope from 'src/scope/scope'
import ITagImpl from './itag-impl' import ITagImpl from './itag-impl'
import ITagImplOptions from './itag-impl-options' import ITagImplOptions from './itag-impl-options'
import Liquid from 'src/liquid' import Liquid from 'src/liquid'
@@ -8,26 +8,27 @@ import Hash from './hash'
import Template from 'src/template/template' import Template from 'src/template/template'
import ITemplate from 'src/template/itemplate' import ITemplate from 'src/template/itemplate'
import TagToken from 'src/parser/tag-token' import TagToken from 'src/parser/tag-token'
import Token from 'src/parser/token'
export default class Tag extends Template implements ITemplate { export default class Tag extends Template implements ITemplate {
name: string name: string
token: TagToken token: TagToken
private impl: ITagImpl private impl: ITagImpl
static impls: object = {} static impls: { [key: string]: ITagImplOptions } = {}
constructor (token, tokens, liquid: Liquid) { constructor (token: TagToken, tokens: Token[], liquid: Liquid) {
super(token) super(token)
this.name = token.name this.name = token.name
const impl = Tag.impls[token.name] const impl = Tag.impls[token.name]
assert(impl, `tag ${token.name} not found`) assert(impl, `tag ${token.name} not found`)
this.impl = create(impl) this.impl = create<ITagImplOptions, ITagImpl>(impl)
this.impl.liquid = liquid this.impl.liquid = liquid
if (this.impl.parse) { if (this.impl.parse) {
this.impl.parse(token, tokens) this.impl.parse(token, tokens)
} }
} }
async render (scope) { async render (scope: Scope) {
const hash = new Hash(this.token.args, scope) const hash = new Hash(this.token.args, scope)
const impl = this.impl const impl = this.impl
if (typeof impl.render !== 'function') { if (typeof impl.render !== 'function') {
+2 -2
View File
@@ -2,7 +2,7 @@ import Token from 'src/parser/token'
export default class Template { export default class Template {
token: Token; token: Token;
constructor(token) { constructor (token) {
this.token = token; this.token = token
} }
} }
+3 -3
View File
@@ -7,7 +7,7 @@ import Scope from 'src/scope/scope'
export default class { export default class {
initial: any initial: any
filters: Array<any> filters: Array<any>
constructor(str: string, strict_filters?: boolean) { constructor (str: string, strictFilters?: boolean) {
let match = lexical.matchValue(str) let match = lexical.matchValue(str)
assert(match, `illegal value string: ${str}`) assert(match, `illegal value string: ${str}`)
@@ -20,9 +20,9 @@ export default class {
} }
this.initial = initial this.initial = initial
this.filters = filters.map(str => new Filter(str, strict_filters)) this.filters = filters.map(str => new Filter(str, strictFilters))
} }
value(scope: Scope) { value (scope: Scope) {
return this.filters.reduce( return this.filters.reduce(
(prev, filter) => filter.render(prev, scope), (prev, filter) => filter.render(prev, scope),
evalExp(this.initial, scope)) evalExp(this.initial, scope))
+11 -11
View File
@@ -16,14 +16,14 @@ abstract class LiquidError {
private input: string private input: string
private token: Token private token: Token
private originalError: Error private originalError: Error
constructor(err, token) { constructor (err, token) {
this.input = token.input this.input = token.input
this.line = token.line this.line = token.line
this.file = token.file this.file = token.file
this.originalError = err this.originalError = err
this.token = token this.token = token
} }
captureStackTrace(obj) { captureStackTrace (obj) {
this.name = obj.constructor.name this.name = obj.constructor.name
captureStack.call(obj) captureStack.call(obj)
@@ -37,32 +37,32 @@ abstract class LiquidError {
} }
export class TokenizationError extends LiquidError { export class TokenizationError extends LiquidError {
constructor(message, token) { constructor (message, token) {
super({message}, token) super({ message }, token)
super.captureStackTrace(this) super.captureStackTrace(this)
} }
} }
TokenizationError.prototype = _.create(Error.prototype) TokenizationError.prototype = _.create(Error.prototype) as any
TokenizationError.prototype.constructor = TokenizationError TokenizationError.prototype.constructor = TokenizationError
export class ParseError extends LiquidError { export class ParseError extends LiquidError {
constructor(err, token) { constructor (err, token) {
super(err, token) super(err, token)
_.assign(this, err) _.assign(this, err)
super.captureStackTrace(this) super.captureStackTrace(this)
} }
} }
ParseError.prototype = _.create(Error.prototype) ParseError.prototype = _.create(Error.prototype) as any
ParseError.prototype.constructor = ParseError ParseError.prototype.constructor = ParseError
export class RenderError extends LiquidError { export class RenderError extends LiquidError {
constructor(err, tpl) { constructor (err, tpl) {
super(err, tpl.token) super(err, tpl.token)
_.assign(this, err) _.assign(this, err)
super.captureStackTrace(this) super.captureStackTrace(this)
} }
} }
RenderError.prototype = _.create(Error.prototype) RenderError.prototype = _.create(Error.prototype) as any
RenderError.prototype.constructor = RenderError RenderError.prototype.constructor = RenderError
export class RenderBreakError { export class RenderBreakError {
@@ -73,7 +73,7 @@ export class RenderBreakError {
this.message = message + '' this.message = message + ''
} }
} }
RenderBreakError.prototype = _.create(Error.prototype) RenderBreakError.prototype = _.create(Error.prototype) as any
RenderBreakError.prototype.constructor = RenderBreakError RenderBreakError.prototype.constructor = RenderBreakError
export class AssertionError { export class AssertionError {
@@ -83,7 +83,7 @@ export class AssertionError {
this.message = message + '' this.message = message + ''
} }
} }
AssertionError.prototype = _.create(Error.prototype) AssertionError.prototype = _.create(Error.prototype) as any
AssertionError.prototype.constructor = AssertionError AssertionError.prototype.constructor = AssertionError
function mkContext (input, targetLine) { function mkContext (input, targetLine) {
+1 -1
View File
@@ -7,7 +7,7 @@
export function anySeries (iterable, iteratee) { export function anySeries (iterable, iteratee) {
let ret: Promise<any> = Promise.reject(new Error('init')) let ret: Promise<any> = Promise.reject(new Error('init'))
iterable.forEach(function (item, idx) { iterable.forEach(function (item, idx) {
ret = ret.catch(e => iteratee(item, idx, iterable)) ret = ret.catch(() => iteratee(item, idx, iterable))
}) })
return ret return ret
} }
+1 -1
View File
@@ -159,7 +159,7 @@ const formatCodes = {
} }
}; };
(formatCodes as any).h = formatCodes.b; (formatCodes as any).h = formatCodes.b;
(formatCodes as any).N = formatCodes.L; (formatCodes as any).N = formatCodes.L
export default function (d, format) { export default function (d, format) {
let output = '' let output = ''
+18 -17
View File
@@ -6,11 +6,11 @@ const arrToStr = Array.prototype.toString
* @param {any} value The value to check. * @param {any} value The value to check.
* @return {Boolean} Returns true if value is a string, else false. * @return {Boolean} Returns true if value is a string, else false.
*/ */
export function isString (value) { export function isString (value: any) {
return toStr.call(value) === '[object String]' return toStr.call(value) === '[object String]'
} }
export function isFunction (value) { export function isFunction (value: any) {
return typeof value === 'function' return typeof value === 'function'
} }
@@ -24,7 +24,7 @@ export function promisify (fn) {
} }
} }
export function stringify (value) { export function stringify (value: any): string {
if (isNil(value)) return '' if (isNil(value)) return ''
if (isFunction(value.to_liquid)) return stringify(value.to_liquid()) if (isFunction(value.to_liquid)) return stringify(value.to_liquid())
if (isFunction(value.toLiquid)) return stringify(value.toLiquid()) if (isFunction(value.toLiquid)) return stringify(value.toLiquid())
@@ -34,7 +34,7 @@ export function stringify (value) {
return toStr.call(value) return toStr.call(value)
} }
function defaultToString (value) { function defaultToString (value: any): string {
const cache = [] const cache = []
return JSON.stringify(value, (key, value) => { return JSON.stringify(value, (key, value) => {
if (isObject(value)) { if (isObject(value)) {
@@ -47,20 +47,20 @@ function defaultToString (value) {
}) })
} }
export function create (proto) { export function create<T1 extends object, T2 extends T1 = T1> (proto: T1): T2 {
return Object.create(proto) return Object.create(proto)
} }
export function isNil (value) { export function isNil (value: any): boolean {
return value === null || value === undefined return value === null || value === undefined
} }
export function isArray (value) { export function isArray (value: any): boolean {
// be compatible with IE 8 // be compatible with IE 8
return toStr.call(value) === '[object Array]' return toStr.call(value) === '[object Array]'
} }
export function isError (value) { export function isError (value: any): boolean {
const signature = toStr.call(value) const signature = toStr.call(value)
// [object XXXError] // [object XXXError]
return signature.substr(-6, 5) === 'Error' || return signature.substr(-6, 5) === 'Error' ||
@@ -75,7 +75,7 @@ export function isError (value) {
* @param {Function} iteratee The function invoked per iteration. * @param {Function} iteratee The function invoked per iteration.
* @return {Object} Returns object. * @return {Object} Returns object.
*/ */
export function forOwn (object, iteratee) { export function forOwn (object, iteratee: ((val: any, key: string, obj: object) => boolean | void)) {
object = object || {} object = object || {}
for (const k in object) { for (const k in object) {
if (object.hasOwnProperty(k)) { if (object.hasOwnProperty(k)) {
@@ -96,21 +96,22 @@ export function forOwn (object, iteratee) {
* @param {...Object} sources The source objects. * @param {...Object} sources The source objects.
* @return {Object} Returns object. * @return {Object} Returns object.
*/ */
export function assign (obj, ...srcs) { export function assign (obj: object, ...srcs: object[]): object {
obj = isObject(obj) ? obj : {} obj = isObject(obj) ? obj : {}
srcs.forEach(src => binaryAssign(obj, src)) srcs.forEach(src => binaryAssign(obj, src))
return obj return obj
} }
function binaryAssign(target, src) { function binaryAssign (target: object, src: object): object {
for(let key in src) if (src.hasOwnProperty(key)) target[key] = src[key] for (const key in src) if (src.hasOwnProperty(key)) target[key] = src[key]
return target
} }
export function last (arr) { export function last (arr: any[]): any {
return arr[arr.length - 1] return arr[arr.length - 1]
} }
export function uniq (arr) { export function uniq (arr: any[]): any[] {
const u = {} const u = {}
const a = [] const a = []
for (let i = 0, l = arr.length; i < l; ++i) { for (let i = 0, l = arr.length; i < l; ++i) {
@@ -129,7 +130,7 @@ export function uniq (arr) {
* @param {any} value The value to check. * @param {any} value The value to check.
* @return {Boolean} Returns true if value is an object, else false. * @return {Boolean} Returns true if value is an object, else false.
*/ */
export function isObject (value) { export function isObject (value: any): boolean {
const type = typeof value const type = typeof value
return value !== null && (type === 'object' || type === 'function') return value !== null && (type === 'object' || type === 'function')
} }
@@ -156,9 +157,9 @@ export function range (start: number, stop?: number, step?: number) {
return arr return arr
} }
export function padStart(str: any, length: number, ch: string = ' ') { export function padStart (str: any, length: number, ch: string = ' ') {
str = String(str) str = String(str)
let n = length - str.length let n = length - str.length
while(n-- > 0) str = ch + str while (n-- > 0) str = ch + str
return str return str
} }
+12
View File
@@ -0,0 +1,12 @@
{
"env": {
"mocha": true
},
"plugins": [
"mocha"
],
"rules": {
"no-unused-expressions": "off",
"no-new": "off"
}
}
+3 -6
View File
@@ -1,12 +1,9 @@
var chai = require('chai') import { expect } from 'chai'
var Liquid = require('../..') import Liquid from '../..'
var expect = chai.expect
chai.use(require('chai-as-promised'))
describe('.evalValue()', function () { describe('.evalValue()', function () {
var engine var engine
beforeEach(() => engine = new Liquid()) beforeEach(() => { engine = new Liquid() })
it('should throw when scope undefined', function () { it('should throw when scope undefined', function () {
expect(() => engine.evalValue('{{"foo"}}')).to.throw(/scope undefined/) expect(() => engine.evalValue('{{"foo"}}')).to.throw(/scope undefined/)
+1 -5
View File
@@ -1,12 +1,8 @@
import * as chai from 'chai' import { expect } from 'chai'
import * as request from 'supertest' import * as request from 'supertest'
import * as express from 'express' import * as express from 'express'
import * as mock from 'mock-fs' import * as mock from 'mock-fs'
import Liquid from '../../dist/liquid.common.js' import Liquid from '../../dist/liquid.common.js'
import * as chaiAsPromised from 'chai-as-promised'
const expect = chai.expect
chai.use(chaiAsPromised)
describe('express()', function () { describe('express()', function () {
var app, engine var app, engine
+29 -23
View File
@@ -1,8 +1,8 @@
var chai = require('chai') import Liquid from '../..'
var Liquid = require('../..') import { expect, use } from 'chai'
var expect = chai.expect import * as chaiAsPromised from 'chai-as-promised'
chai.use(require('chai-as-promised')) use(chaiAsPromised)
describe('.parseAndRender()', function () { describe('.parseAndRender()', function () {
var engine, strictEngine var engine, strictEngine
@@ -12,19 +12,23 @@ describe('.parseAndRender()', function () {
strict_filters: true strict_filters: true
}) })
}) })
it('should stringify object', function () { it('should stringify object', async function () {
var ctx = { obj: { foo: 'bar' } } var ctx = { obj: { foo: 'bar' } }
return expect(engine.parseAndRender('{{obj}}', ctx)).to.eventually.equal('{"foo":"bar"}') const html = await engine.parseAndRender('{{obj}}', ctx)
return expect(html).to.equal('{"foo":"bar"}')
}) })
it('should stringify array ', function () { it('should stringify array ', async function () {
var ctx = { arr: [-2, 'a'] } var ctx = { arr: [-2, 'a'] }
return expect(engine.parseAndRender('{{arr}}', ctx)).to.eventually.equal('[-2,"a"]') const html = await engine.parseAndRender('{{arr}}', ctx)
return expect(html).to.equal('[-2,"a"]')
}) })
it('should render undefined as empty', function () { it('should render undefined as empty', async function () {
return expect(engine.parseAndRender('foo{{zzz}}bar', {})).to.eventually.equal('foobar') const html = await engine.parseAndRender('foo{{zzz}}bar', {})
return expect(html).to.equal('foobar')
}) })
it('should render as null when filter undefined', function () { it('should render as null when filter undefined', async function () {
return expect(engine.parseAndRender('{{"foo" | filter1}}', {})).to.eventually.equal('foo') 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 strict_filters set', function () {
return expect(strictEngine.parseAndRender('{{"foo" | filter1}}', {})).to return expect(strictEngine.parseAndRender('{{"foo" | filter1}}', {})).to
@@ -38,22 +42,24 @@ describe('.parseAndRender()', function () {
engine.parse('<html><head>{{obj}}</head></html>') engine.parse('<html><head>{{obj}}</head></html>')
}).to.not.throw() }).to.not.throw()
}) })
it('should render template multiple times', function () { it('should render template multiple times', async function () {
var ctx = { obj: { foo: 'bar' } } const ctx = { obj: { foo: 'bar' } }
var template = engine.parse('{{obj}}') const template = engine.parse('{{obj}}')
return engine.render(template, ctx) const result = await engine.render(template, ctx)
.then(result => expect(result).to.equal('{"foo":"bar"}')) expect(result).to.equal('{"foo":"bar"}')
.then(() => engine.render(template, ctx)) const result2 = await engine.render(template, ctx)
.then((result) => expect(result).to.equal('{"foo":"bar"}')) expect(result2).to.equal('{"foo":"bar"}')
}) })
it('should render filters', function () { it('should render filters', async function () {
var ctx = { names: ['alice', 'bob'] } var ctx = { names: ['alice', 'bob'] }
var template = engine.parse('<p>{{names | join: ","}}</p>') var template = engine.parse('<p>{{names | join: ","}}</p>')
return expect(engine.render(template, ctx)).to.eventually.equal('<p>alice,bob</p>') const html = await engine.render(template, ctx)
return expect(html).to.equal('<p>alice,bob</p>')
}) })
it('should render accessive filters', function () { it('should render accessive filters', async function () {
var src = '{% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}' + var src = '{% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}' +
'{{ my_array | first }}' '{{ my_array | first }}'
return expect(engine.parseAndRender(src)).to.eventually.equal('apples') const html = await engine.parseAndRender(src)
return expect(html).to.equal('apples')
}) })
}) })
+24 -26
View File
@@ -1,9 +1,6 @@
var chai = require('chai') import { expect } from 'chai'
var mock = require('mock-fs') import * as mock from 'mock-fs'
var Liquid = require('../..') import Liquid from '../..'
var expect = chai.expect
chai.use(require('chai-as-promised'))
describe('#renderFile()', function () { describe('#renderFile()', function () {
var engine var engine
@@ -24,28 +21,28 @@ describe('#renderFile()', function () {
afterEach(function () { afterEach(function () {
mock.restore() mock.restore()
}) })
it('should render file', function () { it('should render file', async function () {
return expect(engine.renderFile('/root/files/foo.html', {})) const html = await engine.renderFile('/root/files/foo.html', {})
.to.eventually.equal('foo') return expect(html).to.equal('foo')
}) })
it('should find files without extname', function () { it('should find files without extname', async function () {
var engine = new Liquid({ root: '/root' }) var engine = new Liquid({ root: '/root' })
return expect(engine.renderFile('/root/files/bar', {})) const html = await engine.renderFile('/root/files/bar', {})
.to.eventually.equal('bar') return expect(html).to.equal('bar')
}) })
it('should accept relative path', function () { it('should accept relative path', async function () {
return expect(engine.renderFile('files/foo.html')) const html = await engine.renderFile('files/foo.html')
.to.eventually.equal('foo') return expect(html).to.equal('foo')
}) })
it('should resolve array as root', function () { it('should resolve array as root', async function () {
engine = new Liquid({ engine = new Liquid({
root: ['/boo', '/root/'], root: ['/boo', '/root/'],
extname: '.html' extname: '.html'
}) })
return expect(engine.renderFile('files/foo.html')) const html = await engine.renderFile('files/foo.html')
.to.eventually.equal('foo') return expect(html).to.equal('foo')
}) })
it('should default root to cwd', function () { it('should default root to cwd', async function () {
var files = {} var files = {}
files[process.cwd() + '/foo.html'] = 'FOO' files[process.cwd() + '/foo.html'] = 'FOO'
mock(files) mock(files)
@@ -53,15 +50,16 @@ describe('#renderFile()', function () {
engine = new Liquid({ engine = new Liquid({
extname: '.html' extname: '.html'
}) })
return expect(engine.renderFile('foo.html')) const html = await engine.renderFile('foo.html')
.to.eventually.equal('FOO') return expect(html).to.equal('FOO')
}) })
it('should render file with context', function () { it('should render file with context', async function () {
return expect(engine.renderFile('/root/files/name.html', { name: 'harttle' })) const html = await engine.renderFile('/root/files/name.html', { name: 'harttle' })
.to.eventually.equal('My name is harttle.') return expect(html).to.equal('My name is harttle.')
}) })
it('should use default extname', function () { it('should use default extname', async function () {
return expect(engine.renderFile('files/name', { name: 'harttle' })).to.eventually.equal('My name is harttle.') const html = await engine.renderFile('files/name', { name: 'harttle' })
return expect(html).to.equal('My name is harttle.')
}) })
it('should throw with lookup list when file not exist', function () { it('should throw with lookup list when file not exist', function () {
engine = new Liquid({ engine = new Liquid({
+5 -5
View File
@@ -1,10 +1,7 @@
import * as chai from 'chai' import { expect } from 'chai'
import Liquid from '../..' import Liquid from '../..'
import * as chaiAsPromised from 'chai-as-promised'
const liquid = new Liquid() const liquid = new Liquid()
const expect = chai.expect
chai.use(chaiAsPromised)
const cases = [ const cases = [
{ {
@@ -445,6 +442,9 @@ const cases = [
describe('Whitespace Control', function () { describe('Whitespace Control', function () {
cases.forEach(item => it( cases.forEach(item => it(
item.text, item.text,
() => expect(liquid.parseAndRender(item.text)).to.eventually.equal(item.expected) async () => {
const html = await liquid.parseAndRender(item.text)
expect(html).to.equal(item.expected)
}
)) ))
}) })
+38 -42
View File
@@ -1,11 +1,10 @@
import Liquid from '../../dist/liquid.js' import Liquid from '../../dist/liquid.js'
import { createFakeServer, useFakeXMLHttpRequest } from 'sinon' import { createFakeServer, useFakeXMLHttpRequest } from 'sinon'
import * as chai from 'chai' import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import { JSDOM } from 'jsdom' import { JSDOM } from 'jsdom'
import * as chaiAsPromised from 'chai-as-promised'
const expect = chai.expect use(chaiAsPromised)
chai.use(chaiAsPromised)
describe('xhr', () => { describe('xhr', () => {
if (+process.version.match(/^v(\d+)/)[1] < 8) { if (+process.version.match(/^v(\d+)/)[1] < 8) {
@@ -24,7 +23,7 @@ describe('xhr', () => {
includeNodeLocations: true includeNodeLocations: true
}); });
(global as any).XMLHttpRequest = useFakeXMLHttpRequest(); (global as any).XMLHttpRequest = useFakeXMLHttpRequest();
(global as any).document = dom.window.document; (global as any).document = dom.window.document
engine = new Liquid({ engine = new Liquid({
root: 'https://example.com/views/', root: 'https://example.com/views/',
extname: '.html' extname: '.html'
@@ -36,97 +35,94 @@ describe('xhr', () => {
delete (global as any).document delete (global as any).document
}) })
describe('#renderFile()', () => { describe('#renderFile()', () => {
it('should support without extname', () => { it('should support without extname', async () => {
return expect(engine.renderFile('hello', { name: 'alice1' })) const html = await engine.renderFile('hello', { name: 'alice1' })
.to.eventually.equal('hello alice1') return expect(html).to.equal('hello alice1')
}) })
it('should support with extname', () => { it('should support with extname', async () => {
return expect(engine.renderFile('hello.html', { name: 'alice2' })) const html = await engine.renderFile('hello.html', { name: 'alice2' })
.to.eventually.equal('hello alice2') return expect(html).to.equal('hello alice2')
}) })
it('should support with absolute path', () => { it('should support with absolute path', async () => {
server.respondWith('GET', 'https://example.com/foo.html', server.respondWith('GET', 'https://example.com/foo.html',
[200, { 'Content-Type': 'text/plain' }, 'foo']) [200, { 'Content-Type': 'text/plain' }, 'foo'])
return expect(engine.renderFile('/foo.html')) const html = await engine.renderFile('/foo.html')
.to.eventually.equal('foo') return expect(html).to.equal('foo')
}) })
it('should support with url', () => { it('should support with url', async () => {
return expect(engine.renderFile('https://example.com/views/hello.html', { name: 'alice4' })) const html = await engine.renderFile('https://example.com/views/hello.html', { name: 'alice4' })
.to.eventually.equal('hello alice4') return expect(html).to.equal('hello alice4')
}) })
it('should support include', () => { it('should support include', async () => {
server.respondWith('GET', 'https://example.com/views/hello.html', server.respondWith('GET', 'https://example.com/views/hello.html',
[200, { 'Content-Type': 'text/plain' }, "hello {% include 'name.html' %}"]) [200, { 'Content-Type': 'text/plain' }, "hello {% include 'name.html' %}"])
server.respondWith('GET', 'https://example.com/views/name.html', server.respondWith('GET', 'https://example.com/views/name.html',
[200, { 'Content-Type': 'text/plain' }, '{{name}}']) [200, { 'Content-Type': 'text/plain' }, '{{name}}'])
return expect(engine.renderFile('hello.html', { name: 'alice5' })) const html = await engine.renderFile('hello.html', { name: 'alice5' })
.to.eventually.equal('hello alice5') return expect(html).to.equal('hello alice5')
}) })
it('should throw 404', () => { it('should throw 404', () => {
return expect(engine.renderFile('/not/exist.html')) return expect(engine.renderFile('/not/exist.html'))
.to.be.rejectedWith('Not Found') .to.be.rejectedWith('Not Found')
}) })
it('should throw error', function (done) { it('should throw error', function () {
engine.renderFile('hello.html') const result = expect(engine.renderFile('hello.html'))
.then(() => done('should not be resolved')) .to.be.rejectedWith('An error occurred whilst receiving the response.');
.catch(function (e) {
expect(e.message).to.equal('An error occurred whilst receiving the response.')
done()
});
(global as any).XMLHttpRequest.onCreate = function (request) { (global as any).XMLHttpRequest.onCreate = function (request) {
setTimeout(() => request.error()) setTimeout(() => request.error())
} }
return result
}) })
}) })
describe('#renderFile() with root specified', () => { describe('#renderFile() with root specified', () => {
it('should support undefined root', () => { it('should support undefined root', async () => {
engine = new Liquid({ engine = new Liquid({
extname: '.html' extname: '.html'
}) })
server.respondWith('GET', 'https://example.com/foo/hello.html', server.respondWith('GET', 'https://example.com/foo/hello.html',
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}']) [200, { 'Content-Type': 'text/plain' }, 'hello {{name}}'])
return expect(engine.renderFile('hello.html', { name: 'alice5' })) const html = await engine.renderFile('hello.html', { name: 'alice5' })
.to.eventually.equal('hello alice5') return expect(html).to.equal('hello alice5')
}) })
it('should support empty root', () => { it('should support empty root', async () => {
engine = new Liquid({ engine = new Liquid({
root: '', root: '',
extname: '.html' extname: '.html'
}) })
server.respondWith('https://example.com/foo/hello.html', server.respondWith('https://example.com/foo/hello.html',
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}']) [200, { 'Content-Type': 'text/plain' }, 'hello {{name}}'])
return expect(engine.renderFile('hello.html', { name: 'alice5' })) const html = await engine.renderFile('hello.html', { name: 'alice5' })
.to.eventually.equal('hello alice5') return expect(html).to.equal('hello alice5')
}) })
it('should support with relative path', () => { it('should support with relative path', async () => {
engine = new Liquid({ engine = new Liquid({
root: './views/', root: './views/',
extname: '.html' extname: '.html'
}) })
server.respondWith('GET', 'https://example.com/foo/views/hello.html', server.respondWith('GET', 'https://example.com/foo/views/hello.html',
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}']) [200, { 'Content-Type': 'text/plain' }, 'hello {{name}}'])
return expect(engine.renderFile('hello.html', { name: 'alice5' })) const html = await engine.renderFile('hello.html', { name: 'alice5' })
.to.eventually.equal('hello alice5') return expect(html).to.equal('hello alice5')
}) })
it('should support with absolute path', () => { it('should support with absolute path', async () => {
engine = new Liquid({ engine = new Liquid({
root: '/views/', root: '/views/',
extname: '.html' extname: '.html'
}) })
server.respondWith('GET', 'https://example.com/views/hello.html', server.respondWith('GET', 'https://example.com/views/hello.html',
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}']) [200, { 'Content-Type': 'text/plain' }, 'hello {{name}}'])
return expect(engine.renderFile('hello.html', { name: 'alice5' })) const html = await engine.renderFile('hello.html', { name: 'alice5' })
.to.eventually.equal('hello alice5') return expect(html).to.equal('hello alice5')
}) })
it('should support with url', () => { it('should support with url', async () => {
engine = new Liquid({ engine = new Liquid({
root: 'https://foo.com/bar/', root: 'https://foo.com/bar/',
extname: '.html' extname: '.html'
}) })
server.respondWith('GET', 'https://foo.com/bar/hello.html', server.respondWith('GET', 'https://foo.com/bar/hello.html',
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}']) [200, { 'Content-Type': 'text/plain' }, 'hello {{name}}'])
return expect(engine.renderFile('hello.html', { name: 'alice5' })) const html = await engine.renderFile('hello.html', { name: 'alice5' })
.to.eventually.equal('hello alice5') return expect(html).to.equal('hello alice5')
}) })
}) })
describe('cache options', () => { describe('cache options', () => {
+5 -8
View File
@@ -1,10 +1,6 @@
import * as chai from 'chai' import { expect } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import Liquid from '../../src/liquid' import Liquid from '../../src/liquid'
chai.use(chaiAsPromised)
const expect = chai.expect
const ctx = { const ctx = {
date: new Date(), date: new Date(),
foo: 'bar', foo: 'bar',
@@ -21,12 +17,13 @@ const ctx = {
} }
let liquid let liquid
function test (src, dst) { async function test (src, dst) {
return expect(liquid.parseAndRender(src, ctx)).to.eventually.equal(dst) const html = await liquid.parseAndRender(src, ctx)
return expect(html).to.equal(dst)
} }
describe('filters', function () { describe('filters', function () {
before(() => liquid = new Liquid()) before(() => { liquid = new Liquid() })
describe('abs', function () { describe('abs', function () {
it('should return 3 for -3', () => test('{{ -3 | abs }}', '3')) it('should return 3 for -3', () => test('{{ -3 | abs }}', '3'))
it('should return 2 for arr[0]', () => test('{{ arr[0] | abs }}', '2')) it('should return 2 for arr[0]', () => test('{{ arr[0] | abs }}', '2'))
+2 -2
View File
@@ -7,13 +7,13 @@ const expect = chai.expect
describe('Liquid', function () { describe('Liquid', function () {
describe('#constructor()', function () { describe('#constructor()', function () {
it('should throw on illegal root', function () { it('should throw on illegal root', function () {
expect(() => new (Liquid as any)({root: {}})).to.throw(/illegal root/) expect(() => new (Liquid as any)({ root: {} })).to.throw(/illegal root/)
}) })
}) })
describe('#plugin()', function () { describe('#plugin()', function () {
it('should call plugin on the instance', async function () { it('should call plugin on the instance', async function () {
const engine = new Liquid() const engine = new Liquid()
engine.plugin(function (Liquid) { engine.plugin(function () {
this.registerFilter('foo', x => `foo${x}foo`) this.registerFilter('foo', x => `foo${x}foo`)
}) })
const html = await engine.parseAndRender('{{"bar"|foo}}') const html = await engine.parseAndRender('{{"bar"|foo}}')
+1 -5
View File
@@ -1,10 +1,6 @@
import * as chai from 'chai' import { expect } from 'chai'
import * as mock from 'mock-fs' import * as mock from 'mock-fs'
import Liquid from '../../../src/liquid' import Liquid from '../../../src/liquid'
import * as chaiAsPromised from 'chai-as-promised'
const expect = chai.expect
chai.use(chaiAsPromised)
describe('LiquidOptions#cache', function () { describe('LiquidOptions#cache', function () {
let engine let engine
+5 -8
View File
@@ -1,8 +1,5 @@
import Liquid from '../../../src/liquid' import Liquid from 'src/liquid'
import * as chai from 'chai' import { expect } from 'chai'
const expect = chai.expect
chai.use(require('chai-as-promised'))
describe('LiquidOptions#strict_*', function () { describe('LiquidOptions#strict_*', function () {
let engine let engine
@@ -13,9 +10,9 @@ describe('LiquidOptions#strict_*', function () {
extname: '.html' extname: '.html'
}) })
}) })
it('should not throw when strict_variables false (default)', function () { it('should not throw when strict_variables false (default)', async function () {
return expect(engine.parseAndRender('before{{notdefined}}after', ctx)).to const html = await engine.parseAndRender('before{{notdefined}}after', ctx)
.eventually.equal('beforeafter') return expect(html).to.equal('beforeafter')
}) })
it('should throw when strict_variables true', function () { it('should throw when strict_variables true', function () {
const tpl = engine.parse('before{{notdefined}}after') const tpl = engine.parse('before{{notdefined}}after')
+31 -33
View File
@@ -1,62 +1,58 @@
import * as chai from 'chai' import { expect } from 'chai'
import Liquid from '../../../src/liquid' import Liquid from '../../../src/liquid'
import * as chaiAsPromised from 'chai-as-promised'
const expect = chai.expect
chai.use(chaiAsPromised)
describe('LiquidOptions#trimming', function () { describe('LiquidOptions#trimming', function () {
const ctx = { name: 'harttle' } const ctx = { name: 'harttle' }
describe('tag trimming', function () { describe('tag trimming', function () {
it('should respect trim_tag_left', 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 })
return expect(engine.parseAndRender(' \n \t{%if true%}foo{%endif%} ')) const html = await engine.parseAndRender(' \n \t{%if true%}foo{%endif%} ')
.to.eventually.equal('foo ') return expect(html).to.equal('foo ')
}) })
it('should respect trim_tag_right', function () { it('should respect trim_tag_right', async function () {
const engine = new Liquid({ trim_tag_right: true }) const engine = new Liquid({ trim_tag_right: true })
return expect(engine.parseAndRender('\t{%if true%}foo{%endif%} \n')) const html = engine.parseAndRender('\t{%if true%}foo{%endif%} \n')
.to.eventually.equal('\tfoo') return expect(html).to.equal('\tfoo')
}) })
it('should not trim value', function () { 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 })
return expect(engine.parseAndRender('{%if true%}a {{name}} b{%endif%}', ctx)) const html = await engine.parseAndRender('{%if true%}a {{name}} b{%endif%}', ctx)
.to.eventually.equal('a harttle b') return expect(html).to.equal('a harttle b')
}) })
}) })
describe('value trimming', function () { describe('value trimming', function () {
it('should respect trim_value_left', function () { it('should respect trim_value_left', async function () {
const engine = new Liquid({ trim_value_left: true }) const engine = new Liquid({ trim_value_left: true })
return expect(engine.parseAndRender(' \n \t{{name}} ', ctx)) const html = await engine.parseAndRender(' \n \t{{name}} ', ctx)
.to.eventually.equal('harttle ') return expect(html).to.equal('harttle ')
}) })
it('should respect trim_value_right', function () { it('should respect trim_value_right', async function () {
const engine = new Liquid({ trim_value_right: true }) const engine = new Liquid({ trim_value_right: true })
return expect(engine.parseAndRender(' \n \t{{name}} ', ctx)) const html = await engine.parseAndRender(' \n \t{{name}} ', ctx)
.to.eventually.equal(' \n \tharttle') return expect(html).to.equal(' \n \tharttle')
}) })
it('should respect not trim tag', function () { it('should respect not trim tag', async function () {
const engine = new Liquid({ trim_value_left: true, trim_value_right: true }) const engine = new Liquid({ trim_value_left: true, trim_value_right: true })
return expect(engine.parseAndRender('\t{% if true %} aha {%endif%}\t')) const html = await engine.parseAndRender('\t{% if true %} aha {%endif%}\t')
.to.eventually.equal('\t aha \t') return expect(html).to.equal('\t aha \t')
}) })
}) })
describe('greedy', function () { describe('greedy', function () {
const src = '\n {%-if true-%}\n a \n{{-name-}}{%-endif-%}\n ' const src = '\n {%-if true-%}\n a \n{{-name-}}{%-endif-%}\n '
it('should enable greedy by default', function () { it('should enable greedy by default', async function () {
const engine = new Liquid() const engine = new Liquid()
return expect(engine.parseAndRender(src, ctx)) const html = await engine.parseAndRender(src, ctx)
.to.eventually.equal('aharttle') return expect(html).to.equal('aharttle')
}) })
it('should respect to greedy:false by default', function () { it('should respect to greedy:false by default', async function () {
const engine = new Liquid({ greedy: false }) const engine = new Liquid({ greedy: false })
return expect(engine.parseAndRender(src, ctx)) const html = await engine.parseAndRender(src, ctx)
.to.eventually.equal('\n a \nharttle ') return expect(html).to.equal('\n a \nharttle ')
}) })
}) })
describe('markup', function () { describe('markup', function () {
it('should support trim using markup', function () { it('should support trim using markup', async function () {
const engine = new Liquid() const engine = new Liquid()
const src = [ const src = [
'{%- assign username = "John G. Chalmers-Smith" -%}', '{%- assign username = "John G. Chalmers-Smith" -%}',
@@ -67,9 +63,10 @@ describe('LiquidOptions#trimming', function () {
'{%- endif -%}' '{%- endif -%}'
].join('\n') ].join('\n')
const dst = 'Wow, John G. Chalmers-Smith, you have a long name!' const dst = 'Wow, John G. Chalmers-Smith, you have a long name!'
return expect(engine.parseAndRender(src)).to.eventually.equal(dst) const html = await engine.parseAndRender(src)
return expect(html).to.equal(dst)
}) })
it('should not trim when not specified', function () { it('should not trim when not specified', async function () {
const engine = new Liquid() const engine = new Liquid()
const src = [ const src = [
'{% assign username = "John G. Chalmers-Smith" %}', '{% assign username = "John G. Chalmers-Smith" %}',
@@ -80,7 +77,8 @@ describe('LiquidOptions#trimming', function () {
'{% endif %}' '{% endif %}'
].join('\n') ].join('\n')
const dst = '\n\n Wow, John G. Chalmers-Smith, you have a long name!\n' const dst = '\n\n Wow, John G. Chalmers-Smith, you have a long name!\n'
return expect(engine.parseAndRender(src)).to.eventually.equal(dst) const html = await engine.parseAndRender(src)
return expect(html).to.equal(dst)
}) })
}) })
}) })
+24 -19
View File
@@ -1,14 +1,9 @@
import * as chai from 'chai' import * as chai from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import * as sinonChai from 'sinon-chai'
import * as sinon from 'sinon'
import Scope from '../../src/scope/scope' import Scope from '../../src/scope/scope'
import Output from '../../src/template/output' import Output from '../../src/template/output'
import OutputToken from 'src/parser/output-token'
import Filter from 'src/template/filter' import Filter from 'src/template/filter'
chai.use(sinonChai)
chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
describe('Output', function () { describe('Output', function () {
@@ -16,41 +11,51 @@ describe('Output', function () {
Filter.clear() Filter.clear()
}) })
it('should respect to .to_liquid() method', function () { it('should respect to .to_liquid() method', async function () {
const scope = new Scope({ const scope = new Scope({
bar: { to_liquid: x => 'custom' } bar: { to_liquid: () => 'custom' }
}) })
return expect(new Output({value: 'bar'}).render(scope)).to.eventually.equal('custom') const output = new Output({ value: 'bar' } as OutputToken)
const html = await output.render(scope)
return expect(html).to.equal('custom')
}) })
it('should stringify objects', function () { it('should stringify objects', async function () {
const scope = new Scope({ const scope = new Scope({
foo: { obj: { arr: ['a', 2] } } foo: { obj: { arr: ['a', 2] } }
}) })
return expect(new Output({value: 'foo'}).render(scope)).to.eventually.equal('{"obj":{"arr":["a",2]}}') const output = new Output({ value: 'foo' } as OutputToken)
const html = await output.render(scope)
return expect(html).to.equal('{"obj":{"arr":["a",2]}}')
}) })
it('should skip circular property', function () { it('should skip circular property', async function () {
const ctx = { foo: { num: 2 }, bar: 'bar' } as any const ctx = { foo: { num: 2 }, bar: 'bar' } as any
ctx.foo.circular = ctx ctx.foo.circular = ctx
const scope = new Scope(ctx) const output = new Output({ value: 'foo' } as OutputToken)
return expect(new Output({value: 'foo'}).render(scope)).to.eventually.equal('{"num":2,"circular":{"bar":"bar"}}') const html = await output.render(new Scope(ctx))
return expect(html).equal('{"num":2,"circular":{"bar":"bar"}}')
}) })
it('should skip function property', function () { it('should skip function property', async function () {
const scope = new Scope({ obj: { foo: 'foo', bar: x => x } }) const scope = new Scope({ obj: { foo: 'foo', bar: x => x } })
return expect(new Output({value: 'obj'}).render(scope)).to.eventually.equal('{"foo":"foo"}') const output = new Output({ value: 'obj' } as OutputToken)
const html = await output.render(scope)
return expect(html).to.equal('{"foo":"foo"}')
}) })
it('should respect to .toString()', async () => { it('should respect to .toString()', async () => {
const scope = new Scope({ obj: { toString: () => 'FOO' } }) const scope = new Scope({ obj: { toString: () => 'FOO' } })
const str = await new Output({value: 'obj'}).render(scope) const output = new Output({ value: 'obj' } as OutputToken)
const str = await output.render(scope)
return expect(str).to.equal('FOO') return expect(str).to.equal('FOO')
}) })
it('should respect to .to_s()', async () => { it('should respect to .to_s()', async () => {
const scope = new Scope({ obj: { to_s: () => 'FOO' } }) const scope = new Scope({ obj: { to_s: () => 'FOO' } })
const str = await new Output({value: 'obj'}).render(scope) const output = new Output({ value: 'obj' } as OutputToken)
const str = await output.render(scope)
return expect(str).to.equal('FOO') return expect(str).to.equal('FOO')
}) })
it('should respect to .liquid_method_missing()', async () => { it('should respect to .liquid_method_missing()', async () => {
const scope = new Scope({ obj: { liquid_method_missing: x => x.toUpperCase() } }) const scope = new Scope({ obj: { liquid_method_missing: x => x.toUpperCase() } })
const str = await new Output({value: 'obj.foo'}).render(scope) const output = new Output({ value: 'obj.foo' } as OutputToken)
const str = await output.render(scope)
return expect(str).to.equal('FOO') return expect(str).to.equal('FOO')
}) })
}) })
+7 -16
View File
@@ -1,24 +1,14 @@
import * as chai from 'chai' import { expect } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import * as sinonChai from 'sinon-chai'
import * as sinon from 'sinon'
import Scope from '../../src/scope/scope' import Scope from '../../src/scope/scope'
import Token from 'src/parser/token' import Token from '../../src/parser/token'
import Tag from 'src/template/tag/tag' import Tag from 'src/template/tag/tag'
import Filter from 'src/template/filter' import Filter from 'src/template/filter'
import Render from '../../src/render/render' import Render from '../../src/render/render'
import Parser from '../../src/parser/parser'
import HTML from 'src/template/html' import HTML from 'src/template/html'
chai.use(sinonChai)
chai.use(chaiAsPromised)
const expect = chai.expect
const parser = new Parser(null)
let render
describe('render', function () { describe('render', function () {
beforeEach(function () { let render
before(function () {
Filter.clear() Filter.clear()
Tag.clear() Tag.clear()
render = new Render() render = new Render()
@@ -29,10 +19,11 @@ describe('render', function () {
expect(render.renderTemplates([])).to.be.rejectedWith(/scope undefined/) expect(render.renderTemplates([])).to.be.rejectedWith(/scope undefined/)
}) })
it('should render html', function () { it('should render html', async function () {
const scope = new Scope() const scope = new Scope()
const token = { type: 'html', value: '<p>' } as Token const token = { type: 'html', value: '<p>' } as Token
return expect(render.renderTemplates([new HTML(token)], scope)).to.eventually.equal('<p>') const html = await render.renderTemplates([new HTML(token)], scope)
return expect(html).to.equal('<p>')
}) })
}) })
}) })
+5 -5
View File
@@ -1,14 +1,14 @@
import * as chai from 'chai' import * as chai from 'chai'
import Tag from 'src/template/tag/tag' import Tag from 'src/template/tag/tag'
import TagToken from 'src/parser/tag-token'
import Scope from 'src/scope/scope' import Scope from 'src/scope/scope'
import * as sinon from 'sinon' import * as sinon from 'sinon'
import * as sinonChai from 'sinon-chai' import * as sinonChai from 'sinon-chai'
import Liquid from 'src/liquid' import Liquid from 'src/liquid'
import TagToken from 'src/parser/tag-token'
chai.use(sinonChai) chai.use(sinonChai)
const expect = chai.expect const expect = chai.expect
const liquid = new Liquid(); const liquid = new Liquid()
describe('tag', function () { describe('tag', function () {
let scope let scope
@@ -29,14 +29,14 @@ describe('tag', function () {
type: 'tag', type: 'tag',
value: 'foo', value: 'foo',
name: 'foo' name: 'foo'
}, [], liquid) } as TagToken, [], liquid)
}).to.throw(/tag foo not found/) }).to.throw(/tag foo not found/)
}) })
it('should register simple tag', function () { it('should register simple tag', function () {
expect(function () { expect(function () {
Tag.register('foo', { Tag.register('foo', {
render: x => 'bar' render: () => 'bar'
}) })
}).not.throw() }).not.throw()
}) })
@@ -50,7 +50,7 @@ describe('tag', function () {
type: 'tag', type: 'tag',
value: 'foo', value: 'foo',
name: 'foo' name: 'foo'
} } as TagToken
await new Tag(token, [], liquid).render(scope) await new Tag(token, [], liquid).render(scope)
expect(spy).to.have.been.called expect(spy).to.have.been.called
}) })
+40 -37
View File
@@ -1,9 +1,8 @@
import Liquid from 'src/liquid' import Liquid from 'src/liquid'
import * as chai from 'chai' import { expect, use } from 'chai'
import * as sinonChai from 'chai-as-promised' import * as chaiAsPromised from 'chai-as-promised'
chai.use(sinonChai) use(chaiAsPromised)
const expect = chai.expect
describe('tags/assign', function () { describe('tags/assign', function () {
const liquid = new Liquid() const liquid = new Liquid()
@@ -12,65 +11,69 @@ describe('tags/assign', function () {
const ctx = {} const ctx = {}
return expect(liquid.parseAndRender(src, ctx)).to.be.rejectedWith(/illegal/) return expect(liquid.parseAndRender(src, ctx)).to.be.rejectedWith(/illegal/)
}) })
it('should support assign to a string', function () { it('should support assign to a string', async function () {
const src = '{% assign foo="bar" %}{{foo}}' const src = '{% assign foo="bar" %}{{foo}}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('bar') return expect(html).to.equal('bar')
}) })
it('should support assign to a number', function () { it('should support assign to a number', async function () {
const src = '{% assign foo=10086 %}{{foo}}' const src = '{% assign foo=10086 %}{{foo}}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('10086') return expect(html).to.equal('10086')
}) })
it('should shading rather than overwriting', function () { it('should shading rather than overwriting', async function () {
const ctx = { foo: 'foo' } const ctx = { foo: 'foo' }
const src = '{% assign foo="FOO" %}{{foo}}' const src = '{% assign foo="FOO" %}{{foo}}'
return liquid.parseAndRender(src, ctx) const html = await liquid.parseAndRender(src, ctx)
.then(x => { expect(html).to.equal('FOO')
expect(x).to.equal('FOO') expect(ctx.foo).to.equal('foo')
expect(ctx.foo).to.equal('foo')
})
}) })
it('should assign as array', function () { it('should assign as array', async function () {
const src = '{% assign foo=(1..3) %}{{foo}}' const src = '{% assign foo=(1..3) %}{{foo}}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('[1,2,3]') return expect(html).to.equal('[1,2,3]')
}) })
it('should assign as filter result', function () { it('should assign as filter result', async function () {
const src = '{% assign foo="a b" | capitalize | split: " " | first %}{{foo}}' const src = '{% assign foo="a b" | capitalize | split: " " | first %}{{foo}}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('A') return expect(html).to.equal('A')
}) })
it('should assign as filter across multiple lines as result', function () { it('should assign as filter across multiple lines as result', async function () {
const src = `{% assign foo="a b" const src = `{% assign foo="a b"
| capitalize | capitalize
| split: " " | split: " "
| first %}{{foo}}` | first %}{{foo}}`
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('A') return expect(html).to.equal('A')
}) })
it('should assign var-1', function () { it('should assign var-1', async function () {
const src = '{% assign var-1 = 5 %}{{ var-1 }}' const src = '{% assign var-1 = 5 %}{{ var-1 }}'
return expect(liquid.parseAndRender(src)).to.eventually.equal('5') const html = await liquid.parseAndRender(src)
return expect(html).to.equal('5')
}) })
it('should assign var-', function () { it('should assign var-', async function () {
const src = '{% assign var- = 5 %}{{ var- }}' const src = '{% assign var- = 5 %}{{ var- }}'
return expect(liquid.parseAndRender(src)).to.eventually.equal('5') const html = await liquid.parseAndRender(src)
return expect(html).to.equal('5')
}) })
it('should assign -var', function () { it('should assign -var', async function () {
const src = '{% assign -let = 5 %}{{ -let }}' const src = '{% assign -let = 5 %}{{ -let }}'
return expect(liquid.parseAndRender(src)).to.eventually.equal('5') const html = await liquid.parseAndRender(src)
return expect(html).to.equal('5')
}) })
it('should assign -5-5', function () { it('should assign -5-5', async function () {
const src = '{% assign -5-5 = 5 %}{{ -5-5 }}' const src = '{% assign -5-5 = 5 %}{{ -5-5 }}'
return expect(liquid.parseAndRender(src)).to.eventually.equal('5') const html = await liquid.parseAndRender(src)
return expect(html).to.equal('5')
}) })
it('should assign 4-3', function () { it('should assign 4-3', async function () {
const src = '{% assign 4-3 = 5 %}{{ 4-3 }}' const src = '{% assign 4-3 = 5 %}{{ 4-3 }}'
return expect(liquid.parseAndRender(src)).to.eventually.equal('5') const html = await liquid.parseAndRender(src)
return expect(html).to.equal('5')
}) })
it('should not assign -6', function () { it('should not assign -6', async function () {
const src = '{% assign -6 = 5 %}{{ -6 }}' const src = '{% assign -6 = 5 %}{{ -6 }}'
return expect(liquid.parseAndRender(src)).to.eventually.equal('-6') const html = await liquid.parseAndRender(src)
return expect(html).to.equal('-6')
}) })
}) })
+10 -12
View File
@@ -1,26 +1,24 @@
import Liquid from 'src/liquid' import Liquid from 'src/liquid'
import * as chai from 'chai' import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
const expect = chai.expect use(chaiAsPromised)
chai.use(require('chai-as-promised'))
describe('tags/capture', function () { describe('tags/capture', function () {
const liquid = new Liquid() const liquid = new Liquid()
it('should support capture', function () { it('should support capture', async function () {
const src = '{% capture f %}{{"a" | capitalize}}{%endcapture%}{{f}}' const src = '{% capture f %}{{"a" | capitalize}}{%endcapture%}{{f}}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('A') return expect(html).to.equal('A')
}) })
it('should shading rather than overwriting', function () { it('should shading rather than overwriting', async function () {
const src = '{% capture var %}10{% endcapture %}{{var}}' const src = '{% capture var %}10{% endcapture %}{{var}}'
const ctx = { 'var': 20 } const ctx = { 'var': 20 }
return liquid.parseAndRender(src, ctx) const html = await liquid.parseAndRender(src, ctx)
.then(x => { expect(html).to.equal('10')
expect(x).to.equal('10') expect(ctx.var).to.equal(20)
expect(ctx.var).to.equal(20)
})
}) })
it('should throw on invalid identifier', function () { it('should throw on invalid identifier', function () {
+18 -18
View File
@@ -1,8 +1,8 @@
import Liquid from 'src/liquid' import Liquid from 'src/liquid'
import * as chai from 'chai' import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
const expect = chai.expect use(chaiAsPromised)
chai.use(require('chai-as-promised'))
describe('tags/case', function () { describe('tags/case', function () {
const liquid = new Liquid() const liquid = new Liquid()
@@ -12,42 +12,42 @@ describe('tags/case', function () {
return expect(liquid.parseAndRender(src)) return expect(liquid.parseAndRender(src))
.to.be.rejectedWith(/{% case "foo"%} not closed/) .to.be.rejectedWith(/{% case "foo"%} not closed/)
}) })
it('should hit the specified case', function () { it('should hit the specified case', async function () {
const src = '{% case "foo"%}' + const src = '{% case "foo"%}' +
'{% when "foo" %}foo{% when "bar"%}bar' + '{% when "foo" %}foo{% when "bar"%}bar' +
'{%endcase%}' '{%endcase%}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('foo') return expect(html).to.equal('foo')
}) })
it('should resolve empty string if not hit', function () { it('should resolve empty string if not hit', async function () {
const src = '{% case empty %}' + const src = '{% case empty %}' +
'{% when "foo" %}foo{% when ""%}bar' + '{% when "foo" %}foo{% when ""%}bar' +
'{%endcase%}' '{%endcase%}'
const ctx = { const ctx = {
empty: '' empty: ''
} }
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('bar') return expect(html).to.equal('bar')
}) })
it('should accept empty string as branch name', function () { it('should accept empty string as branch name', async function () {
const src = '{% case false %}' + const src = '{% case false %}' +
'{% when "foo" %}foo{% when ""%}bar' + '{% when "foo" %}foo{% when ""%}bar' +
'{%endcase%}' '{%endcase%}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('') return expect(html).to.equal('')
}) })
it('should support boolean case', function () { it('should support boolean case', async function () {
const src = '{% case false %}' + const src = '{% case false %}' +
'{% when "foo" %}foo{% when false%}bar' + '{% when "foo" %}foo{% when false%}bar' +
'{%endcase%}' '{%endcase%}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('bar') return expect(html).to.equal('bar')
}) })
it('should support else branch', function () { it('should support else branch', async function () {
const src = '{% case "a" %}' + const src = '{% case "a" %}' +
'{% when "b" %}b{% when "c"%}c{%else %}d' + '{% when "b" %}b{% when "c"%}c{%else %}d' +
'{%endcase%}' '{%endcase%}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('d') return expect(html).to.equal('d')
}) })
}) })
+15 -15
View File
@@ -1,8 +1,8 @@
import Liquid from 'src/liquid' import Liquid from 'src/liquid'
import * as chai from 'chai' import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
const expect = chai.expect use(chaiAsPromised)
chai.use(require('chai-as-promised'))
describe('tags/comment', function () { describe('tags/comment', function () {
const liquid = new Liquid() const liquid = new Liquid()
@@ -11,24 +11,24 @@ describe('tags/comment', function () {
return expect(liquid.parseAndRender(src)) return expect(liquid.parseAndRender(src))
.to.be.rejectedWith(/{% comment %} not closed/) .to.be.rejectedWith(/{% comment %} not closed/)
}) })
it('should ignore plain string', function () { it('should ignore plain string', async function () {
const src = 'My name is {% comment %}super{% endcomment %} Shopify.' const src = 'My name is {% comment %}super{% endcomment %} Shopify.'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('My name is Shopify.') return expect(html).to.equal('My name is Shopify.')
}) })
it('should ignore output tokens', function () { it('should ignore output tokens', async function () {
const src = '{% comment %}\n{{ foo}} \n{% endcomment %}' const src = '{% comment %}\n{{ foo}} \n{% endcomment %}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('') return expect(html).to.equal('')
}) })
it('should ignore tag tokens', function () { it('should ignore tag tokens', async function () {
const src = '{% comment %}{%if true%}true{%else%}false{%endif%}{% endcomment %}' const src = '{% comment %}{%if true%}true{%else%}false{%endif%}{% endcomment %}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('') return expect(html).to.equal('')
}) })
it('should ignore un-balenced tag tokens', function () { it('should ignore un-balenced tag tokens', async function () {
const src = '{% comment %}{%if true%}true{%else%}false{% endcomment %}' const src = '{% comment %}{%if true%}true{%else%}false{% endcomment %}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('') return expect(html).to.equal('')
}) })
}) })
+13 -15
View File
@@ -1,16 +1,16 @@
import Liquid from 'src/liquid' import Liquid from 'src/liquid'
import * as chai from 'chai' import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
const expect = chai.expect use(chaiAsPromised)
chai.use(require('chai-as-promised'))
describe('tags/cycle', function () { describe('tags/cycle', function () {
const liquid = new Liquid() const liquid = new Liquid()
it('should support cycle', function () { it('should support cycle', async function () {
const src = "{% cycle '1', '2', '3' %}" const src = "{% cycle '1', '2', '3' %}"
return expect(liquid.parseAndRender(src + src + src + src)) const html = await liquid.parseAndRender(src + src + src + src)
.to.eventually.equal('1231') return expect(html).to.equal('1231')
}) })
it('should throw when cycle candidates empty', function () { it('should throw when cycle candidates empty', function () {
@@ -18,23 +18,21 @@ describe('tags/cycle', function () {
.to.be.rejectedWith(/empty candidates/) .to.be.rejectedWith(/empty candidates/)
}) })
it('should support cycle in for block', function () { it('should support cycle in for block', async function () {
const src = '{% for i in (1..5) %}{% cycle one, "e"%}{% endfor %}' const src = '{% for i in (1..5) %}{% cycle one, "e"%}{% endfor %}'
const ctx = { const ctx = {
one: 1 one: 1
} }
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('1e1e1') return expect(html).to.equal('1e1e1')
}) })
it('should support cycle group', function () { it('should support cycle group', async function () {
const src = "{% cycle one: '1', '2', '3'%}" + const src = "{% cycle one: '1', '2', '3'%}" +
"{% cycle 1: '1', '2', '3'%}" + "{% cycle 1: '1', '2', '3'%}" +
"{% cycle 2: '1', '2', '3'%}" "{% cycle 2: '1', '2', '3'%}"
const ctx = { const ctx = { one: 1 }
one: 1 const html = await liquid.parseAndRender(src, ctx)
} return expect(html).to.equal('121')
return expect(liquid.parseAndRender(src, ctx))
.to.eventually.equal('121')
}) })
}) })
+25 -27
View File
@@ -1,8 +1,8 @@
import Liquid from 'src/liquid' import Liquid from 'src/liquid'
import * as chai from 'chai' import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
const expect = chai.expect use(chaiAsPromised)
chai.use(require('chai-as-promised'))
describe('tags/decrement', function () { describe('tags/decrement', function () {
const liquid = new Liquid() const liquid = new Liquid()
@@ -12,49 +12,47 @@ describe('tags/decrement', function () {
return expect(liquid.parseAndRender(src, ctx)).to.be.rejectedWith(/illegal/) return expect(liquid.parseAndRender(src, ctx)).to.be.rejectedWith(/illegal/)
}) })
it('should decrement undefined variable', function () { it('should decrement undefined variable', async function () {
const src = '{% decrement var %}{% decrement var %}{% decrement var %}' const src = '{% decrement var %}{% decrement var %}{% decrement var %}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('-1-2-3') return expect(html).to.equal('-1-2-3')
}) })
it('should decrement defined variable', function () { it('should decrement defined variable', async function () {
const src = '{% decrement var %}{% decrement var %}{% decrement var %}' const src = '{% decrement var %}{% decrement var %}{% decrement var %}'
const ctx = { 'var': 10 } const ctx = { 'var': 10 }
return liquid.parseAndRender(src, ctx) const html = await liquid.parseAndRender(src, ctx)
.then(x => { expect(html).to.equal('987')
expect(x).to.equal('987') expect(ctx.var).to.equal(7)
expect(ctx.var).to.equal(7)
})
}) })
it('should be independent from assign', function () { it('should be independent from assign', async function () {
const src = '{% assign var=10 %}{% decrement var %}{% decrement var %}{% decrement var %}' const src = '{% assign var=10 %}{% decrement var %}{% decrement var %}{% decrement var %}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('-1-2-3') return expect(html).to.equal('-1-2-3')
}) })
it('should be independent from capture', function () { it('should be independent from capture', async function () {
const src = '{% capture var %}10{% endcapture %}{% decrement var %}{% decrement var %}{% decrement var %}' const src = '{% capture var %}10{% endcapture %}{% decrement var %}{% decrement var %}{% decrement var %}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('-1-2-3') return expect(html).to.equal('-1-2-3')
}) })
it('should not shading assign', function () { it('should not shading assign', async function () {
const src = '{% assign var=10 %}{% decrement var %}{% decrement var %}{% decrement var %} {{var}}' const src = '{% assign var=10 %}{% decrement var %}{% decrement var %}{% decrement var %} {{var}}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('-1-2-3 10') return expect(html).to.equal('-1-2-3 10')
}) })
it('should not shading capture', function () { it('should not shading capture', async function () {
const src = '{% capture var %}10{% endcapture %}{% decrement var %}{% decrement var %}{% decrement var %} {{var}}' const src = '{% capture var %}10{% endcapture %}{% decrement var %}{% decrement var %}{% decrement var %} {{var}}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('-1-2-3 10') return expect(html).to.equal('-1-2-3 10')
}) })
it('should share the same variable with increment', function () { it('should share the same variable with increment', async function () {
const src = '{%increment var%}{%increment var%}{%decrement var%}{%decrement var%}{%increment var%}' const src = '{%increment var%}{%increment var%}{%decrement var%}{%decrement var%}{%increment var%}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('01100') return expect(html).to.equal('01100')
}) })
}) })
+75 -75
View File
@@ -1,8 +1,8 @@
import Liquid from 'src/liquid' import Liquid from 'src/liquid'
import * as chai from 'chai' import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
const expect = chai.expect use(chaiAsPromised)
chai.use(require('chai-as-promised'))
describe('tags/for', function () { describe('tags/for', function () {
let liquid, ctx let liquid, ctx
@@ -22,28 +22,28 @@ describe('tags/for', function () {
emptyArray: [] emptyArray: []
} }
}) })
it('should support array', function () { it('should support array', async function () {
const src = '{%for c in alpha%}{{c}}{%endfor%}' const src = '{%for c in alpha%}{{c}}{%endfor%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('abc') return expect(html).to.equal('abc')
}) })
it('should support object', function () { it('should support object', async function () {
const src = '{%for item in obj%}{{item[0]}},{{item[1]}}-{%else%}b{%endfor%}' const src = '{%for item in obj%}{{item[0]}},{{item[1]}}-{%else%}b{%endfor%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('foo,bar-coo,haa-') return expect(html).to.equal('foo,bar-coo,haa-')
}) })
describe('scope', function () { describe('scope', function () {
it('should read super scope', function () { it('should read super scope', async function () {
const src = '{%for a in (1..2)%}{{num}}{%endfor%}' const src = '{%for a in (1..2)%}{{num}}{%endfor%}'
return expect(liquid.parseAndRender(src, { num: 1 })) const html = await liquid.parseAndRender(src, { num: 1 })
.to.eventually.equal('11') return expect(html).to.equal('11')
}) })
it('should write super scope', function () { it('should write super scope', async function () {
const src = '{%for a in (1..2)%}{{num}}{%assign num = 2%}{%endfor%}' const src = '{%for a in (1..2)%}{{num}}{%assign num = 2%}{%endfor%}'
return expect(liquid.parseAndRender(src, { num: 1 })) const html = await liquid.parseAndRender(src, { num: 1 })
.to.eventually.equal('12') return expect(html).to.equal('12')
}) })
}) })
@@ -62,45 +62,45 @@ describe('tags/for', function () {
}) })
describe('else', function () { describe('else', function () {
it('should goto else for empty array', function () { it('should goto else for empty array', async function () {
const src = '{%for c in emptyArray%}a{%else%}b{%endfor%}' const src = '{%for c in emptyArray%}a{%else%}b{%endfor%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('b') return expect(html).to.equal('b')
}) })
it('should treat non-empty string as one single element', function () { it('should treat non-empty string as one single element', async function () {
const src = '{%for c in "abc"%}x{{c}}{%else%}y{%endfor%}' const src = '{%for c in "abc"%}x{{c}}{%else%}y{%endfor%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('xabc') return expect(html).to.equal('xabc')
}) })
it('should goto else for empty string', function () { it('should goto else for empty string', async function () {
const src = '{%for c in ""%}a{%else%}b{%endfor%}' const src = '{%for c in ""%}a{%else%}b{%endfor%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('b') return expect(html).to.equal('b')
}) })
it('should goto else for empty string object', function () { it('should goto else for empty string object', async function () {
// it should be false although `new String` is none-conform // it should be false although `new String` is none-conform
const src = '{%for c in strObj%}a{%else%}b{%endfor%}' const src = '{%for c in strObj%}a{%else%}b{%endfor%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('b') return expect(html).to.equal('b')
}) })
it('should goto else for empty object', function () { it('should goto else for empty object', async function () {
const src = '{%for c in emptyObj%}a{%else%}b{%endfor%}' const src = '{%for c in emptyObj%}a{%else%}b{%endfor%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('b') return expect(html).to.equal('b')
}) })
it('should goto else for null-prototyped object', function () { it('should goto else for null-prototyped object', async function () {
const src = '{%for c in nullProtoObj%}a{%else%}b{%endfor%}' const src = '{%for c in nullProtoObj%}a{%else%}b{%endfor%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('b') return expect(html).to.equal('b')
}) })
}) })
it('should support for with forloop', function () { it('should support for with forloop', async function () {
const src = '{%for c in alpha%}' + const src = '{%for c in alpha%}' +
'{{forloop.first}}.{{forloop.index}}.{{forloop.index0}}.' + '{{forloop.first}}.{{forloop.index}}.{{forloop.index0}}.' +
'{{forloop.last}}.{{forloop.length}}.' + '{{forloop.last}}.{{forloop.length}}.' +
@@ -110,41 +110,41 @@ describe('tags/for', function () {
const dst = 'true.1.0.false.3.3.2a\n' + const dst = 'true.1.0.false.3.3.2a\n' +
'false.2.1.false.3.2.1b\n' + 'false.2.1.false.3.2.1b\n' +
'false.3.2.true.3.1.0c\n' 'false.3.2.true.3.1.0c\n'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal(dst) return expect(html).to.equal(dst)
}) })
it('should support for with continue', function () { it('should support for with continue', async function () {
const src = '{% for i in (1..5) %}' + const src = '{% for i in (1..5) %}' +
'{{i}}{% continue %}after' + '{{i}}{% continue %}after' +
'{% endfor %}' '{% endfor %}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('12345') return expect(html).to.equal('12345')
}) })
it('should support for with break', function () { it('should support for with break', async function () {
const src = '{% for i in (one..5) %}' + const src = '{% for i in (one..5) %}' +
'{% if i == 4 %}{% break %}{% endif %}' + '{% if i == 4 %}{% break %}{% endif %}' +
'{{ i }}' + '{{ i }}' +
'{% endfor %}' '{% endfor %}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('123') return expect(html).to.equal('123')
}) })
describe('limit', function () { describe('limit', function () {
it('should support for with limit', function () { it('should support for with limit', async function () {
const src = '{% for i in (1..5) limit:2 %}{{ i }}{% endfor %}' const src = '{% for i in (1..5) limit:2 %}{{ i }}{% endfor %}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('12') return expect(html).to.equal('12')
}) })
it('should set forloop.last properly', function () { it('should set forloop.last properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.last}} {%endfor%}' const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.last}} {%endfor%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('false true ') return expect(html).to.equal('false true ')
}) })
it('should set forloop.first properly', function () { it('should set forloop.first properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.first}} {%endfor%}' const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.first}} {%endfor%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('true false ') return expect(html).to.equal('true false ')
}) })
it('should set forloop.length properly', function () { it('should set forloop.length properly', function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.length}} {%endfor%}' const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.length}} {%endfor%}'
@@ -154,50 +154,50 @@ describe('tags/for', function () {
}) })
describe('offset', function () { describe('offset', function () {
it('should support offset with limit', function () { it('should support offset with limit', async function () {
const src = '{% for i in (1..10) limit:2 offset:5%}{{ i }}{% endfor %}' const src = '{% for i in (1..10) limit:2 offset:5%}{{ i }}{% endfor %}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('67') return expect(html).to.equal('67')
}) })
it('should set index properly', function () { it('should set index properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.index}} {%endfor%}' const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.index}} {%endfor%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('1 2 ') return expect(html).to.equal('1 2 ')
}) })
it('should set index0 properly', function () { it('should set index0 properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.index0}} {%endfor%}' const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.index0}} {%endfor%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('0 1 ') return expect(html).to.equal('0 1 ')
}) })
it('should set rindex properly', function () { it('should set rindex properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.rindex}} {%endfor%}' const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.rindex}} {%endfor%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('2 1 ') return expect(html).to.equal('2 1 ')
}) })
it('should set rindex0 properly', function () { it('should set rindex0 properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.rindex0}} {%endfor%}' const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.rindex0}} {%endfor%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('1 0 ') return expect(html).to.equal('1 0 ')
}) })
}) })
describe('reverse', function () { describe('reverse', function () {
it('should support for reversed in the last position', function () { it('should support for reversed in the last position', async function () {
const src = '{% for i in (1..5) limit:2 reversed %}{{ i }}{% endfor %}' const src = '{% for i in (1..5) limit:2 reversed %}{{ i }}{% endfor %}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('21') return expect(html).to.equal('21')
}) })
it('should support for reversed in the first position', function () { it('should support for reversed in the first position', async function () {
const src = '{% for i in (1..5) reversed limit:2 %}{{ i }}{% endfor %}' const src = '{% for i in (1..5) reversed limit:2 %}{{ i }}{% endfor %}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('21') return expect(html).to.equal('21')
}) })
it('should support for reversed in the middle position', function () { it('should support for reversed in the middle position', async function () {
const src = '{% for i in (1..5) offset:2 reversed limit:4 %}{{ i }}{% endfor %}' const src = '{% for i in (1..5) offset:2 reversed limit:4 %}{{ i }}{% endfor %}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('543') return expect(html).to.equal('543')
}) })
}) })
}) })
+49 -52
View File
@@ -1,8 +1,5 @@
import Liquid from 'src/liquid' import Liquid from 'src/liquid'
import * as chai from 'chai' import { expect } from 'chai'
const expect = chai.expect
chai.use(require('chai-as-promised'))
describe('tags/if', function () { describe('tags/if', function () {
const liquid = new Liquid() const liquid = new Liquid()
@@ -18,99 +15,99 @@ describe('tags/if', function () {
return expect(liquid.parseAndRender(src, ctx)) return expect(liquid.parseAndRender(src, ctx))
.to.be.rejectedWith(/tag {% if false%} not closed/) .to.be.rejectedWith(/tag {% if false%} not closed/)
}) })
it('should support nested', function () { it('should support nested', async function () {
const src = '{%if false%}{%if true%}{%else%}a{%endif%}{%endif%}' const src = '{%if false%}{%if true%}{%else%}a{%endif%}{%endif%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('') return expect(html).to.equal('')
}) })
describe('single value as condition', function () { describe('single value as condition', function () {
it('should support boolean', function () { it('should support boolean', async function () {
const src = '{% if false %}1{%elsif true%}2{%else%}3{%endif%}' const src = '{% if false %}1{%elsif true%}2{%else%}3{%endif%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('2') return expect(html).to.equal('2')
}) })
it('should treat Array truthy', function () { it('should treat Array truthy', async function () {
const src = '{%if emptyArray%}a{%endif%}' const src = '{%if emptyArray%}a{%endif%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('a') return expect(html).to.equal('a')
}) })
it('should return true if empty string', function () { it('should return true if empty string', async function () {
const src = '{%if emptyString%}a{%endif%}' const src = '{%if emptyString%}a{%endif%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('a') return expect(html).to.equal('a')
}) })
}) })
describe('expression as condition', function () { describe('expression as condition', function () {
it('should support ==', function () { it('should support ==', async function () {
const src = '{% if 2==3 %}yes{%else%}no{%endif%}' const src = '{% if 2==3 %}yes{%else%}no{%endif%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('no') return expect(html).to.equal('no')
}) })
it('should support >=', function () { it('should support >=', async function () {
const src = '{% if 1>=2 and one<two %}a{%endif%}' const src = '{% if 1>=2 and one<two %}a{%endif%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('') return expect(html).to.equal('')
}) })
it('should support !=', function () { it('should support !=', async function () {
const src = '{% if one!=two %}yes{%else%}no{%endif%}' const src = '{% if one!=two %}yes{%else%}no{%endif%}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('yes') return expect(html).to.equal('yes')
}) })
it('should support value and expression', function () { it('should support value and expression', async function () {
const src = `X{%if version and version != '' %}x{{version}}y{%endif%}Y` const src = `X{%if version and version != '' %}x{{version}}y{%endif%}Y`
const ctx = { 'version': '' } const ctx = { 'version': '' }
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('XY') return expect(html).to.equal('XY')
}) })
}) })
describe('comparasion to null', function () { describe('comparasion to null', function () {
it('should evaluate false for null < 10', function () { it('should evaluate false for null < 10', async function () {
const src = '{% if null < 10 %}yes{% else %}no{% endif %}' const src = '{% if null < 10 %}yes{% else %}no{% endif %}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('no') return expect(html).to.equal('no')
}) })
it('should evaluate false for null > 10', function () { it('should evaluate false for null > 10', async function () {
const src = '{% if null > 10 %}yes{% else %}no{% endif %}' const src = '{% if null > 10 %}yes{% else %}no{% endif %}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('no') return expect(html).to.equal('no')
}) })
it('should evaluate false for null <= 10', function () { it('should evaluate false for null <= 10', async function () {
const src = '{% if null <= 10 %}yes{% else %}no{% endif %}' const src = '{% if null <= 10 %}yes{% else %}no{% endif %}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('no') return expect(html).to.equal('no')
}) })
it('should evaluate false for null >= 10', function () { it('should evaluate false for null >= 10', async function () {
const src = '{% if null >= 10 %}yes{% else %}no{% endif %}' const src = '{% if null >= 10 %}yes{% else %}no{% endif %}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('no') return expect(html).to.equal('no')
}) })
it('should evaluate false for 10 < null', function () { it('should evaluate false for 10 < null', async function () {
const src = '{% if 10 < null %}yes{% else %}no{% endif %}' const src = '{% if 10 < null %}yes{% else %}no{% endif %}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('no') return expect(html).to.equal('no')
}) })
it('should evaluate false for 10 > null', function () { it('should evaluate false for 10 > null', async function () {
const src = '{% if 10 > null %}yes{% else %}no{% endif %}' const src = '{% if 10 > null %}yes{% else %}no{% endif %}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('no') return expect(html).to.equal('no')
}) })
it('should evaluate false for 10 <= null', function () { it('should evaluate false for 10 <= null', async function () {
const src = '{% if 10 <= null %}yes{% else %}no{% endif %}' const src = '{% if 10 <= null %}yes{% else %}no{% endif %}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('no') return expect(html).to.equal('no')
}) })
it('should evaluate false for 10 >= null', function () { it('should evaluate false for 10 >= null', async function () {
const src = '{% if 10 >= null %}yes{% else %}no{% endif %}' const src = '{% if 10 >= null %}yes{% else %}no{% endif %}'
return expect(liquid.parseAndRender(src, ctx)) const html = await liquid.parseAndRender(src, ctx)
.to.eventually.equal('no') return expect(html).to.equal('no')
}) })
}) })
}) })
+34 -37
View File
@@ -1,9 +1,6 @@
import Liquid from 'src/liquid' import Liquid from 'src/liquid'
import { expect } from 'chai'
import * as mock from 'mock-fs' import * as mock from 'mock-fs'
import * as chai from 'chai'
const expect = chai.expect
chai.use(require('chai-as-promised'))
describe('tags/include', function () { describe('tags/include', function () {
let liquid let liquid
@@ -16,21 +13,21 @@ describe('tags/include', function () {
afterEach(function () { afterEach(function () {
mock.restore() mock.restore()
}) })
it('should support include', function () { it('should support include', async function () {
mock({ mock({
'/current.html': 'bar{% include "bar/foo.html" %}bar', '/current.html': 'bar{% include "bar/foo.html" %}bar',
'/bar/foo.html': 'foo' '/bar/foo.html': 'foo'
}) })
return expect(liquid.renderFile('/current.html')).to const html = await liquid.renderFile('/current.html')
.eventually.equal('barfoobar') return expect(html).to.equal('barfoobar')
}) })
it('should support template string', function () { it('should support template string', async function () {
mock({ mock({
'/current.html': 'bar{% include "bar/{{name}}" %}bar', '/current.html': 'bar{% include "bar/{{name}}" %}bar',
'/bar/foo.html': 'foo' '/bar/foo.html': 'foo'
}) })
return expect(liquid.renderFile('/current.html', { name: 'foo.html' })).to const html = await liquid.renderFile('/current.html', { name: 'foo.html' })
.eventually.equal('barfoobar') return expect(html).to.equal('barfoobar')
}) })
it('should throw when not specified', function () { it('should throw when not specified', function () {
@@ -53,43 +50,43 @@ describe('tags/include', function () {
}) })
}) })
it('should support include with relative path', function () { it('should support include with relative path', async function () {
mock({ mock({
'/bar/foo.html': 'foo', '/bar/foo.html': 'foo',
'/foo/relative.html': 'bar{% include "../bar/foo.html" %}bar' '/foo/relative.html': 'bar{% include "../bar/foo.html" %}bar'
}) })
return expect(liquid.renderFile('foo/relative.html')).to const html = await liquid.renderFile('foo/relative.html')
.eventually.equal('barfoobar') return expect(html).to.equal('barfoobar')
}) })
it('should support include: hash list', function () { it('should support include: hash list', async function () {
mock({ mock({
'/hash.html': '{% assign name="harttle" %}{% include "user.html", role: "admin", alias: name %}', '/hash.html': '{% assign name="harttle" %}{% include "user.html", role: "admin", alias: name %}',
'/user.html': '{{name}} : {{role}} : {{alias}}' '/user.html': '{{name}} : {{role}} : {{alias}}'
}) })
return expect(liquid.renderFile('hash.html')).to const html = await liquid.renderFile('hash.html')
.eventually.equal('harttle : admin : harttle') return expect(html).to.equal('harttle : admin : harttle')
}) })
it('should support include: parent scope', function () { it('should support include: parent scope', async function () {
mock({ mock({
'/scope.html': '{% assign shape="triangle" %}{% assign color="yellow" %}{% include "color.html" %}', '/scope.html': '{% assign shape="triangle" %}{% assign color="yellow" %}{% include "color.html" %}',
'/color.html': 'color:{{color}}, shape:{{shape}}' '/color.html': 'color:{{color}}, shape:{{shape}}'
}) })
return expect(liquid.renderFile('scope.html')).to const html = await liquid.renderFile('scope.html')
.eventually.equal('color:yellow, shape:triangle') return expect(html).to.equal('color:yellow, shape:triangle')
}) })
it('should support include: with', function () { it('should support include: with', async function () {
mock({ mock({
'/with.html': '{% include "color" with "red", shape: "rect" %}', '/with.html': '{% include "color" with "red", shape: "rect" %}',
'/color.html': 'color:{{color}}, shape:{{shape}}' '/color.html': 'color:{{color}}, shape:{{shape}}'
}) })
return expect(liquid.renderFile('with.html')).to const html = await liquid.renderFile('with.html')
.eventually.equal('color:red, shape:rect') return expect(html).to.equal('color:red, shape:rect')
}) })
it('should support nested includes', function () { it('should support nested includes', async function () {
mock({ mock({
'/personInfo.html': 'This is a person {% include "card.html" %}', '/personInfo.html': 'This is a person {% include "card.html" %}',
'/card.html': '<p>{{person.firstName}} {{person.lastName}}<br/>{% include "address" %}</p>', '/card.html': '<p>{{person.firstName}} {{person.lastName}}<br/>{% include "address" %}</p>',
@@ -104,49 +101,49 @@ describe('tags/include', function () {
} }
} }
} }
return expect(liquid.renderFile('personInfo.html', ctx)).to const html = await liquid.renderFile('personInfo.html', ctx)
.eventually.equal('This is a person <p>Joe Shmoe<br/>City: Dallas</p>') return expect(html).to.equal('This is a person <p>Joe Shmoe<br/>City: Dallas</p>')
}) })
describe('static partial', function () { describe('static partial', function () {
it('should support filename with extention', function () { it('should support filename with extention', async function () {
mock({ mock({
'/parent.html': 'X{% include child.html color:"red" %}Y', '/parent.html': 'X{% include child.html color:"red" %}Y',
'/child.html': 'child with {{color}}' '/child.html': 'child with {{color}}'
}) })
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' }) const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
return expect(staticLiquid.renderFile('parent.html')).to const html = await staticLiquid.renderFile('parent.html')
.eventually.equal('Xchild with redY') return expect(html).to.equal('Xchild with redY')
}) })
it('should support parent paths', function () { it('should support parent paths', async function () {
mock({ mock({
'/parent.html': 'X{% include bar/./../foo/child.html %}Y', '/parent.html': 'X{% include bar/./../foo/child.html %}Y',
'/foo/child.html': 'child' '/foo/child.html': 'child'
}) })
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' }) const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
return expect(staticLiquid.renderFile('parent.html')).to const html = await staticLiquid.renderFile('parent.html')
.eventually.equal('XchildY') return expect(html).to.equal('XchildY')
}) })
it('should support subpaths', function () { it('should support subpaths', async function () {
mock({ mock({
'/parent.html': 'X{% include foo/child.html %}Y', '/parent.html': 'X{% include foo/child.html %}Y',
'/foo/child.html': 'child' '/foo/child.html': 'child'
}) })
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' }) const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
return expect(staticLiquid.renderFile('parent.html')).to const html = await staticLiquid.renderFile('parent.html')
.eventually.equal('XchildY') return expect(html).to.equal('XchildY')
}) })
it('should support comma separated arguments', function () { it('should support comma separated arguments', async function () {
mock({ mock({
'/parent.html': 'X{% include child.html, color:"red" %}Y', '/parent.html': 'X{% include child.html, color:"red" %}Y',
'/child.html': 'child with {{color}}' '/child.html': 'child with {{color}}'
}) })
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' }) const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
return expect(staticLiquid.renderFile('parent.html')).to const html = await staticLiquid.renderFile('parent.html')
.eventually.equal('Xchild with redY') return expect(html).to.equal('Xchild with redY')
}) })
}) })
}) })
+22 -24
View File
@@ -1,49 +1,47 @@
import Liquid from 'src/liquid' import Liquid from 'src/liquid'
import * as chai from 'chai' import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
const expect = chai.expect use(chaiAsPromised)
chai.use(require('chai-as-promised'))
describe('tags/increment', function () { describe('tags/increment', function () {
const liquid = new Liquid() const liquid = new Liquid()
it('should increment undefined variable', function () { it('should increment undefined variable', async function () {
const src = '{% increment one %}{% increment one %}{% increment one %}' const src = '{% increment one %}{% increment one %}{% increment one %}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('012') return expect(html).to.equal('012')
}) })
it('should increment defined variable', function () { it('should increment defined variable', async function () {
const src = '{% increment one %}{% increment one %}{% increment one %}' const src = '{% increment one %}{% increment one %}{% increment one %}'
const ctx = { one: 7 } const ctx = { one: 7 }
return liquid.parseAndRender(src, ctx) const html = await liquid.parseAndRender(src, ctx)
.then(x => { expect(html).to.equal('789')
expect(x).to.equal('789') expect(ctx.one).to.equal(10)
expect(ctx.one).to.equal(10)
})
}) })
it('should be independent from assign', function () { it('should be independent from assign', async function () {
const src = '{% assign var=10 %}{% increment var %}{% increment var %}{% increment var %}' const src = '{% assign var=10 %}{% increment var %}{% increment var %}{% increment var %}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('012') return expect(html).to.equal('012')
}) })
it('should be independent from capture', function () { it('should be independent from capture', async function () {
const src = '{% capture var %}10{% endcapture %}{% increment var %}{% increment var %}{% increment var %}' const src = '{% capture var %}10{% endcapture %}{% increment var %}{% increment var %}{% increment var %}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('012') return expect(html).to.equal('012')
}) })
it('should not shading assign', function () { it('should not shading assign', async function () {
const src = '{% assign var=10 %}{% increment var %}{% increment var %}{% increment var %} {{var}}' const src = '{% assign var=10 %}{% increment var %}{% increment var %}{% increment var %} {{var}}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('012 10') return expect(html).to.equal('012 10')
}) })
it('should not shading capture', function () { it('should not shading capture', async function () {
const src = '{% capture var %}10{% endcapture %}{% increment var %}{% increment var %}{% increment var %} {{var}}' const src = '{% capture var %}10{% endcapture %}{% increment var %}{% increment var %}{% increment var %} {{var}}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('012 10') return expect(html).to.equal('012 10')
}) })
}) })
+34 -37
View File
@@ -1,9 +1,6 @@
import Liquid from 'src/liquid' import Liquid from 'src/liquid'
import { expect } from 'chai'
import * as mock from 'mock-fs' import * as mock from 'mock-fs'
import * as chai from 'chai'
const expect = chai.expect
chai.use(require('chai-as-promised'))
describe('tags/layout', function () { describe('tags/layout', function () {
let liquid let liquid
@@ -35,51 +32,51 @@ describe('tags/layout', function () {
}) })
}) })
describe('anonymous block', function () { describe('anonymous block', function () {
it('should handle anonymous block', function () { it('should handle anonymous block', async function () {
mock({ mock({
'/parent.html': 'X{%block%}{%endblock%}Y' '/parent.html': 'X{%block%}{%endblock%}Y'
}) })
const src = '{% layout "parent.html" %}{%block%}A{%endblock%}' const src = '{% layout "parent.html" %}{%block%}A{%endblock%}'
return expect(liquid.parseAndRender(src)).to const html = await liquid.parseAndRender(src)
.eventually.equal('XAY') return expect(html).to.equal('XAY')
}) })
it('should handle top level contents as anonymous block', function () { it('should handle top level contents as anonymous block', async function () {
mock({ mock({
'/parent.html': 'X{%block%}{%endblock%}Y' '/parent.html': 'X{%block%}{%endblock%}Y'
}) })
const src = '{% layout "parent.html" %}A' const src = '{% layout "parent.html" %}A'
return expect(liquid.parseAndRender(src)).to const html = await liquid.parseAndRender(src)
.eventually.equal('XAY') return expect(html).to.equal('XAY')
}) })
}) })
it('should handle named blocks', function () { it('should handle named blocks', async function () {
mock({ mock({
'/parent.html': 'X{% block "a"%}{% endblock %}Y{% block b%}{%endblock%}Z' '/parent.html': 'X{% block "a"%}{% endblock %}Y{% block b%}{%endblock%}Z'
}) })
const src = '{% layout "parent.html" %}' + const src = '{% layout "parent.html" %}' +
'{%block a%}A{%endblock%}' + '{%block a%}A{%endblock%}' +
'{%block b%}B{%endblock%}' '{%block b%}B{%endblock%}'
return expect(liquid.parseAndRender(src)).to const html = await liquid.parseAndRender(src)
.eventually.equal('XAYBZ') return expect(html).to.equal('XAYBZ')
}) })
it('should support default block content', function () { it('should support default block content', async function () {
mock({ mock({
'/parent.html': 'X{% block "a"%}A{% endblock %}Y{% block b%}B{%endblock%}Z' '/parent.html': 'X{% block "a"%}A{% endblock %}Y{% block b%}B{%endblock%}Z'
}) })
const src = '{% layout "parent.html" %}{%block a%}a{%endblock%}' const src = '{% layout "parent.html" %}{%block a%}a{%endblock%}'
return expect(liquid.parseAndRender(src)).to const html = await liquid.parseAndRender(src)
.eventually.equal('XaYBZ') return expect(html).to.equal('XaYBZ')
}) })
it('should handle nested block', function () { it('should handle nested block', async function () {
mock({ mock({
'/grand.html': 'X{%block a%}G{%endblock%}Y', '/grand.html': 'X{%block a%}G{%endblock%}Y',
'/parent.html': '{%layout "grand" %}{%block a%}P{%endblock%}', '/parent.html': '{%layout "grand" %}{%block a%}P{%endblock%}',
'/main.html': '{%layout "parent"%}{%block a%}A{%endblock%}' '/main.html': '{%layout "parent"%}{%block a%}A{%endblock%}'
}) })
return expect(liquid.renderFile('/main.html')).to const html = await liquid.renderFile('/main.html')
.eventually.equal('XAY') return expect(html).to.equal('XAY')
}) })
it('should not bleed scope into included layout', function () { it('should not bleed scope into included layout', async function () {
mock({ mock({
'/parent.html': 'X{%block a%}{%endblock%}Y{%block b%}{%endblock%}Z', '/parent.html': 'X{%block a%}{%endblock%}Y{%block b%}{%endblock%}Z',
'/main.html': '{%layout "parent"%}' + '/main.html': '{%layout "parent"%}' +
@@ -87,55 +84,55 @@ describe('tags/layout', function () {
'{%block b%}I{%include "included"%}J{%endblock%}', '{%block b%}I{%include "included"%}J{%endblock%}',
'/included.html': '{%layout "parent"%}{%block a%}a{%endblock%}' '/included.html': '{%layout "parent"%}{%block a%}a{%endblock%}'
}) })
return expect(liquid.renderFile('main')).to const html = await liquid.renderFile('main')
.eventually.equal('XAYIXaYZJZ') return expect(html).to.equal('XAYIXaYZJZ')
}) })
it('should support hash list', function () { it('should support hash list', async function () {
mock({ mock({
'/parent.html': '{{color}}{%block%}{%endblock%}', '/parent.html': '{{color}}{%block%}{%endblock%}',
'/main.html': '{% layout "parent.html" color:"black"%}{%block%}A{%endblock%}' '/main.html': '{% layout "parent.html" color:"black"%}{%block%}A{%endblock%}'
}) })
return expect(liquid.renderFile('/main.html')).to const html = await liquid.renderFile('/main.html')
.eventually.equal('blackA') return expect(html).to.equal('blackA')
}) })
it('should support multiple hash', function () { it('should support multiple hash', async function () {
mock({ mock({
'/parent.html': '{{color}}{{bg}}{%block%}{%endblock%}', '/parent.html': '{{color}}{{bg}}{%block%}{%endblock%}',
'/main.html': '{% layout "parent.html" color:"black", bg:"red"%}{%block%}A{%endblock%}' '/main.html': '{% layout "parent.html" color:"black", bg:"red"%}{%block%}A{%endblock%}'
}) })
return expect(liquid.renderFile('/main.html')).to const html = await liquid.renderFile('/main.html')
.eventually.equal('blackredA') return expect(html).to.equal('blackredA')
}) })
describe('static partial', function () { describe('static partial', function () {
it('should support filename with extention', function () { it('should support filename with extention', async function () {
mock({ mock({
'/parent.html': '{{color}}{%block%}{%endblock%}', '/parent.html': '{{color}}{%block%}{%endblock%}',
'/main.html': '{% layout parent.html color:"black"%}{%block%}A{%endblock%}' '/main.html': '{% layout parent.html color:"black"%}{%block%}A{%endblock%}'
}) })
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false }) const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
return expect(staticLiquid.renderFile('/main.html')).to const html = await staticLiquid.renderFile('/main.html')
.eventually.equal('blackA') return expect(html).to.equal('blackA')
}) })
it('should support parent paths', function () { it('should support parent paths', async function () {
mock({ mock({
'/foo/parent.html': '{{color}}{%block%}{%endblock%}', '/foo/parent.html': '{{color}}{%block%}{%endblock%}',
'/main.html': '{% layout bar/../foo/parent.html color:"black"%}{%block%}A{%endblock%}' '/main.html': '{% layout bar/../foo/parent.html color:"black"%}{%block%}A{%endblock%}'
}) })
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false }) const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
return expect(staticLiquid.renderFile('/main.html')).to const html = await staticLiquid.renderFile('/main.html')
.eventually.equal('blackA') return expect(html).to.equal('blackA')
}) })
it('should support subpaths', function () { it('should support subpaths', async function () {
mock({ mock({
'/foo/parent.html': '{{color}}{%block%}{%endblock%}', '/foo/parent.html': '{{color}}{%block%}{%endblock%}',
'/main.html': '{% layout foo/parent.html color:"black"%}{%block%}A{%endblock%}' '/main.html': '{% layout foo/parent.html color:"black"%}{%block%}A{%endblock%}'
}) })
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false }) const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
return expect(staticLiquid.renderFile('/main.html')).to const html = await staticLiquid.renderFile('/main.html')
.eventually.equal('blackA') return expect(html).to.equal('blackA')
}) })
}) })
}) })
+6 -7
View File
@@ -1,8 +1,5 @@
import Liquid from 'src/liquid' import Liquid from 'src/liquid'
import * as chai from 'chai' import { expect } from 'chai'
const expect = chai.expect
chai.use(require('chai-as-promised'))
describe('tags/raw', function () { describe('tags/raw', function () {
const liquid = new Liquid() const liquid = new Liquid()
@@ -13,11 +10,13 @@ describe('tags/raw', function () {
it('should support raw 2', async function () { it('should support raw 2', async function () {
const src = '{% raw %}{{ 5 | plus: 6 }}{% endraw %} is equal to 11.' const src = '{% raw %}{{ 5 | plus: 6 }}{% endraw %} is equal to 11.'
const dst = '{{ 5 | plus: 6 }} is equal to 11.' const dst = '{{ 5 | plus: 6 }} is equal to 11.'
return expect(liquid.parseAndRender(src)).to.eventually.equal(dst) const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
}) })
it('should support raw 3', function () { it('should support raw 3', async function () {
const src = '{% raw %}\n{{ foo}} \n{% endraw %}' const src = '{% raw %}\n{{ foo}} \n{% endraw %}'
const dst = '\n{{ foo}} \n' const dst = '\n{{ foo}} \n'
return expect(liquid.parseAndRender(src)).to.eventually.equal(dst) const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
}) })
}) })
+25 -20
View File
@@ -1,19 +1,17 @@
import Liquid from 'src/liquid' import Liquid from 'src/liquid'
import * as chai from 'chai' import { expect } from 'chai'
const expect = chai.expect
chai.use(require('chai-as-promised'))
describe('tags/tablerow', function () { describe('tags/tablerow', function () {
const liquid = new Liquid() const liquid = new Liquid()
it('should support tablerow', function () { it('should support tablerow', async function () {
const src = '{% tablerow i in (1..3)%}{{ i }}{% endtablerow %}' const src = '{% tablerow i in (1..3)%}{{ i }}{% endtablerow %}'
const dst = '<tr class="row1"><td class="col1">1</td><td class="col2">2</td><td class="col3">3</td></tr>' const dst = '<tr class="row1"><td class="col1">1</td><td class="col2">2</td><td class="col3">3</td></tr>'
return expect(liquid.parseAndRender(src)).to.eventually.equal(dst) const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
}) })
it('should support cols', function () { it('should support cols', async function () {
const src = '{% tablerow i in alpha cols:2 %}{{ i }}{% endtablerow %}' const src = '{% tablerow i in alpha cols:2 %}{{ i }}{% endtablerow %}'
const ctx = { const ctx = {
alpha: ['a', 'b', 'c'] alpha: ['a', 'b', 'c']
@@ -21,25 +19,29 @@ describe('tags/tablerow', function () {
const dst = const dst =
'<tr class="row1"><td class="col1">a</td><td class="col2">b</td></tr>' + '<tr class="row1"><td class="col1">a</td><td class="col2">b</td></tr>' +
'<tr class="row2"><td class="col1">c</td></tr>' '<tr class="row2"><td class="col1">c</td></tr>'
return expect(liquid.parseAndRender(src, ctx)).to.eventually.equal(dst) const html = await liquid.parseAndRender(src, ctx)
return expect(html).to.equal(dst)
}) })
it('should support cols set to 0', function () { it('should support cols set to 0', async function () {
const src = '{% tablerow i in (1..3) cols:0 %}{{ i }}{% endtablerow %}' const src = '{% tablerow i in (1..3) cols:0 %}{{ i }}{% endtablerow %}'
const dst = '<tr class="row1"><td class="col1">1</td><td class="col2">2</td><td class="col3">3</td></tr>' const dst = '<tr class="row1"><td class="col1">1</td><td class="col2">2</td><td class="col3">3</td></tr>'
return expect(liquid.parseAndRender(src)).to.eventually.equal(dst) const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
}) })
it('should support empty tablerow', function () { it('should support empty tablerow', async function () {
const src = '{% tablerow i in (1..0) cols:2 %}{{ i }}{% endtablerow %}' const src = '{% tablerow i in (1..0) cols:2 %}{{ i }}{% endtablerow %}'
const dst = '' const dst = ''
return expect(liquid.parseAndRender(src)).to.eventually.equal(dst) const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
}) })
it('should support empty array', function () { it('should support empty array', async function () {
const src = '{% tablerow i in alpha.z cols:2 %}{{ i }}{% endtablerow %}' const src = '{% tablerow i in alpha.z cols:2 %}{{ i }}{% endtablerow %}'
const dst = '' const dst = ''
return expect(liquid.parseAndRender(src)).to.eventually.equal(dst) const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
}) })
it('should throw when tablerow not closed', function () { it('should throw when tablerow not closed', function () {
@@ -48,26 +50,29 @@ describe('tags/tablerow', function () {
.to.be.rejectedWith(/tag .* not closed/) .to.be.rejectedWith(/tag .* not closed/)
}) })
it('should support tablerow with range', function () { it('should support tablerow with range', async function () {
const src = '{% tablerow i in (1..5) cols:2 %}{{ i }}{% endtablerow %}' const src = '{% tablerow i in (1..5) cols:2 %}{{ i }}{% endtablerow %}'
const dst = const dst =
'<tr class="row1"><td class="col1">1</td><td class="col2">2</td></tr>' + '<tr class="row1"><td class="col1">1</td><td class="col2">2</td></tr>' +
'<tr class="row2"><td class="col1">3</td><td class="col2">4</td></tr>' + '<tr class="row2"><td class="col1">3</td><td class="col2">4</td></tr>' +
'<tr class="row3"><td class="col1">5</td></tr>' '<tr class="row3"><td class="col1">5</td></tr>'
return expect(liquid.parseAndRender(src)).to.eventually.equal(dst) const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
}) })
it('should support tablerow with limit', function () { it('should support tablerow with limit', async function () {
const src = '{% tablerow i in (1..5) cols:2 limit:3 %}{{ i }}{% endtablerow %}' const src = '{% tablerow i in (1..5) cols:2 limit:3 %}{{ i }}{% endtablerow %}'
const dst = const dst =
'<tr class="row1"><td class="col1">1</td><td class="col2">2</td></tr>' + '<tr class="row1"><td class="col1">1</td><td class="col2">2</td></tr>' +
'<tr class="row2"><td class="col1">3</td></tr>' '<tr class="row2"><td class="col1">3</td></tr>'
return expect(liquid.parseAndRender(src)).to.eventually.equal(dst) const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
}) })
it('should support tablerow with offset', function () { it('should support tablerow with offset', async function () {
const src = '{% tablerow i in (1..5) cols:2 offset:3 %}{{ i }}{% endtablerow %}' const src = '{% tablerow i in (1..5) cols:2 offset:3 %}{{ i }}{% endtablerow %}'
const dst = '<tr class="row1"><td class="col1">4</td><td class="col2">5</td></tr>' const dst = '<tr class="row1"><td class="col1">4</td><td class="col2">5</td></tr>'
return expect(liquid.parseAndRender(src)).to.eventually.equal(dst) const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
}) })
}) })
+15 -15
View File
@@ -1,37 +1,37 @@
import Liquid from 'src/liquid' import Liquid from 'src/liquid'
import * as chai from 'chai' import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
const expect = chai.expect use(chaiAsPromised)
chai.use(require('chai-as-promised'))
describe('tags/unless', function () { describe('tags/unless', function () {
let liquid let liquid
before(() => { liquid = new Liquid() }) before(() => { liquid = new Liquid() })
it('should render else when predicate yields true', function () { it('should render else when predicate yields true', async function () {
// 0 is truthy // 0 is truthy
const src = '{% unless 0 %}yes{%else%}no{%endunless%}' const src = '{% unless 0 %}yes{%else%}no{%endunless%}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('no') return expect(html).to.equal('no')
}) })
it('should render unless when predicate yields false', function () { it('should render unless when predicate yields false', async function () {
const src = '{% unless false %}yes{%else%}no{%endunless%}' const src = '{% unless false %}yes{%else%}no{%endunless%}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('yes') return expect(html).to.equal('yes')
}) })
it('should reject when tag not closed', function () { it('should reject when tag not closed', function () {
const src = '{% unless 1>2 %}yes' const src = '{% unless 1>2 %}yes'
return expect(liquid.parseAndRender(src)) return expect(liquid.parseAndRender(src))
.to.be.rejectedWith(/tag {% unless 1>2 %} not closed/) .to.be.rejectedWith(/tag {% unless 1>2 %} not closed/)
}) })
it('should render unless when predicate yields false and else undefined', function () { it('should render unless when predicate yields false and else undefined', async function () {
const src = '{% unless 1>2 %}yes{%endunless%}' const src = '{% unless 1>2 %}yes{%endunless%}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('yes') return expect(html).to.equal('yes')
}) })
it('should render "" when predicate yields false and else undefined', function () { it('should render "" when predicate yields false and else undefined', async function () {
const src = '{% unless 1<2 %}yes{%endunless%}' const src = '{% unless 1<2 %}yes{%endunless%}'
return expect(liquid.parseAndRender(src)) const html = await liquid.parseAndRender(src)
.to.eventually.equal('') return expect(html).to.equal('')
}) })
}) })
+1 -3
View File
@@ -1,7 +1,5 @@
import { resolve } from '../../src/parser/template-browser' import { resolve } from '../../src/parser/template-browser'
import * as chai from 'chai' import { expect } from 'chai'
const expect = chai.expect
describe('template-browser', function () { describe('template-browser', function () {
if (+process.version.match(/^v(\d+)/)[1] < 8) { if (+process.version.match(/^v(\d+)/)[1] < 8) {
+4 -8
View File
@@ -1,11 +1,7 @@
import { resolve } from '../../src/parser/template' import { resolve } from '../../src/parser/template'
import * as mock from 'mock-fs' import * as mock from 'mock-fs'
import * as chai from 'chai' import { expect } from 'chai'
import * as path from 'path' import * as path from 'path'
import * as chaiAsPromised from 'chai-as-promised'
const expect = chai.expect
chai.use(chaiAsPromised)
describe('template', function () { describe('template', function () {
before(function () { before(function () {
@@ -14,10 +10,10 @@ describe('template', function () {
}) })
}) })
describe('#resolve()', function () { describe('#resolve()', function () {
it('should resolve based on root', function () { it('should resolve based on root', async function () {
const filepath = resolve('bar.html', '/foo', { root: [] }) const filepath = await resolve('bar.html', '/foo', { root: [] })
const expected = path.resolve('/foo/bar.html') const expected = path.resolve('/foo/bar.html')
return expect(filepath).to.eventually.equal(expected) return expect(filepath).to.equal(expected)
}) })
it('should resolve based on root', function () { it('should resolve based on root', function () {
return expect(resolve('foo.html', '/foo', { root: [] })) return expect(resolve('foo.html', '/foo', { root: [] }))
+9 -8
View File
@@ -1,14 +1,15 @@
import { expect } from 'chai' import { expect } from 'chai'
import { parse } from 'src/parser/tokenizer' import Tokenizer from 'src/parser/tokenizer'
import TagToken from 'src/parser/tag-token' import TagToken from 'src/parser/tag-token'
import OutputToken from 'src/parser/output-token' import OutputToken from 'src/parser/output-token'
import HTMLToken from 'src/parser/html-token' import HTMLToken from 'src/parser/html-token'
describe('tokenizer', function () { describe('tokenizer', function () {
const tokenizer = new Tokenizer()
describe('parse', function () { describe('parse', function () {
it('should handle plain HTML', function () { it('should handle plain HTML', function () {
const html = '<html><body><p>Lorem Ipsum</p></body></html>' const html = '<html><body><p>Lorem Ipsum</p></body></html>'
const tokens = parse(html) const tokens = tokenizer.tokenize(html)
expect(tokens.length).to.equal(1) expect(tokens.length).to.equal(1)
expect(tokens[0].value).to.equal(html) expect(tokens[0].value).to.equal(html)
@@ -16,7 +17,7 @@ describe('tokenizer', function () {
}) })
it('should handle tag syntax', function () { it('should handle tag syntax', function () {
const html = '<p>{% for p in a[1]%}</p>' const html = '<p>{% for p in a[1]%}</p>'
const tokens = parse(html) const tokens = tokenizer.tokenize(html)
expect(tokens.length).to.equal(3) expect(tokens.length).to.equal(3)
expect(tokens[1]).instanceOf(TagToken) expect(tokens[1]).instanceOf(TagToken)
@@ -24,7 +25,7 @@ describe('tokenizer', function () {
}) })
it('should handle value syntax', function () { it('should handle value syntax', function () {
const html = '<p>{{foo | date: "%Y-%m-%d"}}</p>' const html = '<p>{{foo | date: "%Y-%m-%d"}}</p>'
const tokens = parse(html) const tokens = tokenizer.tokenize(html)
expect(tokens.length).to.equal(3) expect(tokens.length).to.equal(3)
expect(tokens[1]).instanceOf(OutputToken) expect(tokens[1]).instanceOf(OutputToken)
@@ -32,7 +33,7 @@ describe('tokenizer', function () {
}) })
it('should handle consecutive value and tags', function () { it('should handle consecutive value and tags', function () {
const html = '{{foo}}{{bar}}{%foo%}{%bar%}' const html = '{{foo}}{{bar}}{%foo%}{%bar%}'
const tokens = parse(html) const tokens = tokenizer.tokenize(html)
expect(tokens.length).to.equal(4) expect(tokens.length).to.equal(4)
expect(tokens[0]).instanceOf(OutputToken) expect(tokens[0]).instanceOf(OutputToken)
@@ -43,7 +44,7 @@ describe('tokenizer', function () {
}) })
it('should keep white spaces and newlines', function () { it('should keep white spaces and newlines', function () {
const html = '{%foo%}\n{%bar %} \n {%alice%}' const html = '{%foo%}\n{%bar %} \n {%alice%}'
const tokens = parse(html) const tokens = tokenizer.tokenize(html)
expect(tokens.length).to.equal(5) expect(tokens.length).to.equal(5)
expect(tokens[1]).instanceOf(HTMLToken) expect(tokens[1]).instanceOf(HTMLToken)
expect(tokens[1].raw).to.equal('\n') expect(tokens[1].raw).to.equal('\n')
@@ -52,7 +53,7 @@ describe('tokenizer', function () {
}) })
it('should handle multiple lines tag', function () { it('should handle multiple lines tag', function () {
const html = '{%foo\na:a\nb:1.23\n%}' const html = '{%foo\na:a\nb:1.23\n%}'
const tokens = parse(html) const tokens = tokenizer.tokenize(html)
expect(tokens.length).to.equal(1) expect(tokens.length).to.equal(1)
expect(tokens[0]).instanceOf(TagToken) expect(tokens[0]).instanceOf(TagToken)
expect(tokens[0].args).to.equal('a:a\nb:1.23') expect(tokens[0].args).to.equal('a:a\nb:1.23')
@@ -60,7 +61,7 @@ describe('tokenizer', function () {
}) })
it('should handle multiple lines value', function () { it('should handle multiple lines value', function () {
const html = '{{foo\n|date:\n"%Y-%m-%d"\n}}' const html = '{{foo\n|date:\n"%Y-%m-%d"\n}}'
const tokens = parse(html) const tokens = tokenizer.tokenize(html)
expect(tokens.length).to.equal(1) expect(tokens.length).to.equal(1)
expect(tokens[0]).instanceOf(OutputToken) expect(tokens[0]).instanceOf(OutputToken)
expect(tokens[0].raw).to.equal('{{foo\n|date:\n"%Y-%m-%d"\n}}') expect(tokens[0].raw).to.equal('{{foo\n|date:\n"%Y-%m-%d"\n}}')
+5 -7
View File
@@ -1,11 +1,8 @@
import Liquid from '../../../src/liquid' import { expect } from 'chai'
import Liquid from 'src/liquid'
import * as mock from 'mock-fs' import * as mock from 'mock-fs'
import * as chai from 'chai'
import * as path from 'path' import * as path from 'path'
const expect = chai.expect
chai.use(require('chai-as-promised'))
let engine = new Liquid() let engine = new Liquid()
const strictEngine = new Liquid({ const strictEngine = new Liquid({
strict_variables: true, strict_variables: true,
@@ -111,8 +108,9 @@ describe('error', function () {
expect(err.name).to.equal('RenderError') expect(err.name).to.equal('RenderError')
expect(err.message).to.contain('throwed by filter') expect(err.message).to.contain('throwed by filter')
}) })
it('should not throw when variable undefined by default', function () { it('should not throw when variable undefined by default', async function () {
return expect(engine.parseAndRender('X{{a}}Y')).to.eventually.equal('XY') const html = await engine.parseAndRender('X{{a}}Y')
return expect(html).to.equal('XY')
}) })
it('should throw RenderError when variable not defined', async function () { it('should throw RenderError when variable not defined', async function () {
const err = await expect(strictEngine.parseAndRender('{{a}}')).be.rejected const err = await expect(strictEngine.parseAndRender('{{a}}')).be.rejected
+18 -13
View File
@@ -1,8 +1,9 @@
const chai = require('chai') import * as chai from 'chai'
const sinon = require('sinon') import * as sinon from 'sinon'
import * as sinonChai from 'sinon-chai'
const expect = chai.expect const expect = chai.expect
chai.use(require('chai-as-promised')) chai.use(sinonChai)
chai.use(require('sinon-chai'))
const P = require('../../../src/util/promise') const P = require('../../../src/util/promise')
@@ -32,10 +33,12 @@ describe('util/promise', function () {
item => Promise.reject(new Error(item))) item => Promise.reject(new Error(item)))
return expect(p).to.be.rejectedWith('third') return expect(p).to.be.rejectedWith('third')
}) })
it('should resolve the value that first callback resolved', () => { it('should resolve the value that first callback resolved', async () => {
const p = P.anySeries(['first', 'second'], const result = await P.anySeries(
item => Promise.resolve(item)) ['first', 'second'],
return expect(p).to.eventually.equal('first') item => Promise.resolve(item)
)
return expect(result).to.equal('first')
}) })
it('should not call rest of callbacks once resolved', () => { it('should not call rest of callbacks once resolved', () => {
const spy = sinon.spy() const spy = sinon.spy()
@@ -50,10 +53,12 @@ describe('util/promise', function () {
}) })
}) })
describe('.mapSeries()', function () { describe('.mapSeries()', function () {
it('should resolve when all resolved', function () { it('should resolve when all resolved', async function () {
const p = P.mapSeries(['first', 'second', 'third'], const result = P.mapSeries(
item => Promise.resolve(item)) ['first', 'second', 'third'],
return expect(p).to.eventually.deep.equal(['first', 'second', 'third']) item => Promise.resolve(item)
)
return expect(result).to.deep.equal(['first', 'second', 'third'])
}) })
it('should reject with the error that first callback rejected', () => { it('should reject with the error that first callback rejected', () => {
const p = P.mapSeries(['first', 'second'], const p = P.mapSeries(['first', 'second'],
@@ -66,7 +71,7 @@ describe('util/promise', function () {
return P return P
.mapSeries( .mapSeries(
['first', 'second'], ['first', 'second'],
(item, idx) => new Promise(function (resolve, reject) { (item, idx) => new Promise(function (resolve) {
if (idx === 0) { if (idx === 0) {
setTimeout(function () { setTimeout(function () {
spy1() spy1()
-3
View File
@@ -1,5 +1,4 @@
import * as chai from 'chai' import * as chai from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import * as sinonChai from 'sinon-chai' import * as sinonChai from 'sinon-chai'
import * as sinon from 'sinon' import * as sinon from 'sinon'
import Scope from '../../src/scope/scope' import Scope from '../../src/scope/scope'
@@ -7,7 +6,6 @@ import Filter from 'src/template/filter'
import Value from 'src/template/value' import Value from 'src/template/value'
chai.use(sinonChai) chai.use(sinonChai)
chai.use(chaiAsPromised)
const expect = chai.expect const expect = chai.expect
const add = (l, r) => l + r const add = (l, r) => l + r
@@ -41,7 +39,6 @@ describe('Value', function () {
expect(tpl.filters.length).to.equal(2) expect(tpl.filters.length).to.equal(2)
}) })
it('should eval value', function () { it('should eval value', function () {
Filter.register('date', (l, r) => l + r) Filter.register('date', (l, r) => l + r)
Filter.register('time', (l, r) => l + 3 * r) Filter.register('time', (l, r) => l + 3 * r)
+7 -8
View File
@@ -1,12 +1,11 @@
import { read } from 'src/parser/template-browser' import { read } from 'src/parser/template-browser'
import * as sinon from 'sinon' import * as sinon from 'sinon'
import * as chai from 'chai' import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
chai.use(require('chai-as-promised')) use(chaiAsPromised)
const expect = chai.expect describe('xhr', () => {
describe('template-browser', () => {
if (+process.version.match(/^v(\d+)/)[1] < 8) { if (+process.version.match(/^v(\d+)/)[1] < 8) {
console.info('jsdom not supported, skipping xhr...') console.info('jsdom not supported, skipping xhr...')
return return
@@ -24,9 +23,9 @@ describe('template-browser', () => {
delete (global as any).XMLHttpRequest delete (global as any).XMLHttpRequest
}) })
describe('#read()', () => { describe('#read()', () => {
it('should get corresponding text', () => { it('should get corresponding text', async function () {
return expect(read('https://example.com/views/hello.html')) const html = await read('https://example.com/views/hello.html')
.to.eventually.equal('hello {{name}}') return expect(html).to.equal('hello {{name}}')
}) })
it('should throw 404', () => { it('should throw 404', () => {
return expect(read('https://example.com/not/exist.html')) return expect(read('https://example.com/not/exist.html'))