mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-16 12:50:38 -07:00
feat: nil/null/empty/blank literals, resolves #102
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
import { isNil, isString } from 'src/util/underscore'
|
||||||
|
import { isDrop } from 'src/drop/idrop'
|
||||||
|
import { EmptyDrop } from 'src/drop/empty-drop'
|
||||||
|
|
||||||
|
export class BlankDrop extends EmptyDrop {
|
||||||
|
equals (value: any) {
|
||||||
|
if (value === false) return true
|
||||||
|
if (isNil(isDrop(value) ? value.value() : value)) return true
|
||||||
|
if (isString(value)) return /^\s*$/.test(value)
|
||||||
|
return super.equals(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export abstract class Drop {
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { Drop } from './drop'
|
||||||
|
import { IComparable } from './icomparable'
|
||||||
|
import { isObject, isString, isArray } from 'src/util/underscore'
|
||||||
|
import { IDrop } from 'src/drop/idrop'
|
||||||
|
|
||||||
|
export class EmptyDrop extends Drop implements IDrop, IComparable {
|
||||||
|
equals (value: any) {
|
||||||
|
if (isString(value) || isArray(value)) return value.length === 0
|
||||||
|
if (isObject(value)) return Object.keys(value).length === 0
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
gt () {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
geq () {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
lt () {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
leq () {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
value () {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { isFunction } from 'src/util/underscore'
|
||||||
|
|
||||||
|
export interface IComparable {
|
||||||
|
equals: (rhs: any) => boolean
|
||||||
|
gt: (rhs: any) => boolean
|
||||||
|
geq: (rhs: any) => boolean
|
||||||
|
lt: (rhs: any) => boolean
|
||||||
|
leq: (rhs: any) => boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isComparable (arg: any): arg is IComparable {
|
||||||
|
return arg && isFunction(arg.equals)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { Drop } from './drop'
|
||||||
|
import { isFunction } from 'src/util/underscore'
|
||||||
|
|
||||||
|
export interface IDrop {
|
||||||
|
value(): any
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDrop (value: any): value is IDrop {
|
||||||
|
return value instanceof Drop && isFunction((value as any).value)
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Drop } from './drop'
|
||||||
|
import { IComparable } from './icomparable'
|
||||||
|
import { isNil } from 'src/util/underscore'
|
||||||
|
import { IDrop, isDrop } from 'src/drop/idrop'
|
||||||
|
import { BlankDrop } from 'src/drop/blank-drop'
|
||||||
|
|
||||||
|
export class NullDrop extends Drop implements IDrop, IComparable {
|
||||||
|
equals (value: any) {
|
||||||
|
return isNil(isDrop(value) ? value.value() : value) || value instanceof BlankDrop
|
||||||
|
}
|
||||||
|
gt () {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
geq () {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
lt () {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
leq () {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
value () {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
+58
-16
@@ -2,14 +2,43 @@ import * as lexical from '../parser/lexical'
|
|||||||
import assert from '../util/assert'
|
import assert from '../util/assert'
|
||||||
import Scope from 'src/scope/scope'
|
import Scope from 'src/scope/scope'
|
||||||
import { range, last } from 'src/util/underscore'
|
import { range, last } from 'src/util/underscore'
|
||||||
|
import { isComparable } from 'src/drop/icomparable'
|
||||||
|
import { NullDrop } from 'src/drop/null-drop'
|
||||||
|
import { EmptyDrop } from 'src/drop/empty-drop'
|
||||||
|
import { BlankDrop } from 'src/drop/blank-drop'
|
||||||
|
import { isDrop } from 'src/drop/idrop'
|
||||||
|
|
||||||
const operators = {
|
const binaryOperators: {[key: string]: (lhs: any, rhs: any) => boolean} = {
|
||||||
'==': (l: any, r: any) => l === r,
|
'==': (l: any, r: any) => {
|
||||||
'!=': (l: any, r: any) => l !== r,
|
if (isComparable(l)) return l.equals(r)
|
||||||
'>': (l: any, r: any) => l !== null && r !== null && l > r,
|
if (isComparable(r)) return r.equals(l)
|
||||||
'<': (l: any, r: any) => l !== null && r !== null && l < r,
|
return l === r
|
||||||
'>=': (l: any, r: any) => l !== null && r !== null && l >= r,
|
},
|
||||||
'<=': (l: any, r: any) => l !== null && r !== null && l <= r,
|
'!=': (l: any, r: any) => {
|
||||||
|
if (isComparable(l)) return !l.equals(r)
|
||||||
|
if (isComparable(r)) return !r.equals(l)
|
||||||
|
return l !== r
|
||||||
|
},
|
||||||
|
'>': (l: any, r: any) => {
|
||||||
|
if (isComparable(l)) return l.gt(r)
|
||||||
|
if (isComparable(r)) return r.lt(l)
|
||||||
|
return l > r
|
||||||
|
},
|
||||||
|
'<': (l: any, r: any) => {
|
||||||
|
if (isComparable(l)) return l.lt(r)
|
||||||
|
if (isComparable(r)) return r.gt(l)
|
||||||
|
return l < r
|
||||||
|
},
|
||||||
|
'>=': (l: any, r: any) => {
|
||||||
|
if (isComparable(l)) return l.geq(r)
|
||||||
|
if (isComparable(r)) return r.leq(l)
|
||||||
|
return l >= r
|
||||||
|
},
|
||||||
|
'<=': (l: any, r: any) => {
|
||||||
|
if (isComparable(l)) return l.leq(r)
|
||||||
|
if (isComparable(r)) return r.geq(l)
|
||||||
|
return l <= r
|
||||||
|
},
|
||||||
'contains': (l: any, r: any) => {
|
'contains': (l: any, r: any) => {
|
||||||
if (!l) return false
|
if (!l) return false
|
||||||
if (typeof l.indexOf !== 'function') return false
|
if (typeof l.indexOf !== 'function') return false
|
||||||
@@ -19,41 +48,54 @@ const operators = {
|
|||||||
'or': (l: any, r: any) => isTruthy(l) || isTruthy(r)
|
'or': (l: any, r: any) => isTruthy(l) || isTruthy(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function evalExp (exp: string, scope: Scope): any {
|
export function parseExp (exp: string, scope: Scope): any {
|
||||||
assert(scope, 'unable to evalExp: scope undefined')
|
assert(scope, 'unable to parseExp: scope undefined')
|
||||||
const operatorREs = lexical.operators
|
const operatorREs = lexical.operators
|
||||||
let match
|
let match
|
||||||
for (let i = 0; i < operatorREs.length; i++) {
|
for (let i = 0; i < operatorREs.length; i++) {
|
||||||
const operatorRE = operatorREs[i]
|
const operatorRE = operatorREs[i]
|
||||||
const expRE = new RegExp(`^(${lexical.quoteBalanced.source})(${operatorRE.source})(${lexical.quoteBalanced.source})$`)
|
const expRE = new RegExp(`^(${lexical.quoteBalanced.source})(${operatorRE.source})(${lexical.quoteBalanced.source})$`)
|
||||||
if ((match = exp.match(expRE))) {
|
if ((match = exp.match(expRE))) {
|
||||||
const l = evalExp(match[1], scope)
|
const l = parseExp(match[1], scope)
|
||||||
const op = operators[match[2].trim()]
|
const op = binaryOperators[match[2].trim()]
|
||||||
const r = evalExp(match[3], scope)
|
const r = parseExp(match[3], scope)
|
||||||
return op(l, r)
|
return op(l, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((match = exp.match(lexical.rangeLine))) {
|
if ((match = exp.match(lexical.rangeLine))) {
|
||||||
const low = evalValue(match[1], scope)
|
const low = parseValue(match[1], scope)
|
||||||
const high = evalValue(match[2], scope)
|
const high = parseValue(match[2], scope)
|
||||||
return range(low, high + 1)
|
return range(low, high + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return evalValue(exp, scope)
|
return parseValue(exp, scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function evalValue (str: string, scope: Scope) {
|
export function evalExp (str: string, scope: Scope): any {
|
||||||
|
const value = parseExp(str, scope)
|
||||||
|
return isDrop(value) ? value.value() : value
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseValue (str: string, scope: Scope): any {
|
||||||
if (!str) return null
|
if (!str) return null
|
||||||
str = str.trim()
|
str = str.trim()
|
||||||
|
|
||||||
if (str === 'true') return true
|
if (str === 'true') return true
|
||||||
if (str === 'false') return false
|
if (str === 'false') return false
|
||||||
|
if (str === 'nil' || str === 'null') return new NullDrop()
|
||||||
|
if (str === 'empty') return new EmptyDrop()
|
||||||
|
if (str === 'blank') return new BlankDrop()
|
||||||
if (!isNaN(Number(str))) return Number(str)
|
if (!isNaN(Number(str))) return Number(str)
|
||||||
if ((str[0] === '"' || str[0] === "'") && str[0] === last(str)) return str.slice(1, -1)
|
if ((str[0] === '"' || str[0] === "'") && str[0] === last(str)) return str.slice(1, -1)
|
||||||
return scope.get(str)
|
return scope.get(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function evalValue (str: string, scope: Scope): any {
|
||||||
|
const value = parseValue(str, scope)
|
||||||
|
return isDrop(value) ? value.value() : value
|
||||||
|
}
|
||||||
|
|
||||||
export function isTruthy (val: any): boolean {
|
export function isTruthy (val: any): boolean {
|
||||||
return !isFalsy(val)
|
return !isFalsy(val)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export default interface IContext {
|
|
||||||
[key: string]: any;
|
|
||||||
liquid_method_missing?: (key: string) => any; // eslint-disable-line
|
|
||||||
}
|
|
||||||
+12
-6
@@ -3,11 +3,17 @@ import { __assign } from 'tslib'
|
|||||||
import assert from '../util/assert'
|
import assert from '../util/assert'
|
||||||
import { NormalizedFullOptions, applyDefault } from '../liquid-options'
|
import { NormalizedFullOptions, applyDefault } from '../liquid-options'
|
||||||
import BlockMode from './block-mode'
|
import BlockMode from './block-mode'
|
||||||
import IContext from './icontext'
|
|
||||||
|
export type Context = {
|
||||||
|
[key: string]: any
|
||||||
|
liquid_method_missing?: (key: string) => any // eslint-disable-line
|
||||||
|
to_liquid?: () => any // eslint-disable-line
|
||||||
|
toLiquid?: () => any // eslint-disable-line
|
||||||
|
}
|
||||||
|
|
||||||
export default class Scope {
|
export default class Scope {
|
||||||
opts: NormalizedFullOptions
|
opts: NormalizedFullOptions
|
||||||
contexts: Array<IContext>
|
contexts: Array<Context>
|
||||||
blocks: object = {}
|
blocks: object = {}
|
||||||
groups: {[key: string]: number} = {}
|
groups: {[key: string]: number} = {}
|
||||||
blockMode: BlockMode = BlockMode.OUTPUT
|
blockMode: BlockMode = BlockMode.OUTPUT
|
||||||
@@ -67,10 +73,10 @@ export default class Scope {
|
|||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
private readProperty (obj: IContext, key: string) {
|
private readProperty (obj: Context, key: string) {
|
||||||
let val
|
let val
|
||||||
if (_.isNil(obj)) {
|
if (_.isNil(obj)) {
|
||||||
val = undefined
|
val = obj
|
||||||
} else {
|
} else {
|
||||||
obj = toLiquid(obj)
|
obj = toLiquid(obj)
|
||||||
val = key === 'size' ? readSize(obj) : obj[key]
|
val = key === 'size' ? readSize(obj) : obj[key]
|
||||||
@@ -144,7 +150,7 @@ export default class Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toLiquid (obj: IContext) {
|
function toLiquid (obj: Context) {
|
||||||
if (_.isFunction(obj.to_liquid)) {
|
if (_.isFunction(obj.to_liquid)) {
|
||||||
return obj.to_liquid()
|
return obj.to_liquid()
|
||||||
}
|
}
|
||||||
@@ -154,7 +160,7 @@ function toLiquid (obj: IContext) {
|
|||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
function readSize (obj: IContext) {
|
function readSize (obj: Context) {
|
||||||
if (!_.isNil(obj.size)) return obj.size
|
if (!_.isNil(obj.size)) return obj.size
|
||||||
if (_.isArray(obj) || _.isString(obj)) return obj.length
|
if (_.isArray(obj) || _.isString(obj)) return obj.length
|
||||||
return obj.size
|
return obj.size
|
||||||
|
|||||||
@@ -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: any) {
|
export function isString (value: any): value is string {
|
||||||
return toStr.call(value) === '[object String]'
|
return toStr.call(value) === '[object String]'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isFunction (value: any) {
|
export function isFunction (value: any): value is Function {
|
||||||
return typeof value === 'function'
|
return typeof value === 'function'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ export function stringify (value: any): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function defaultToString (value: any): string {
|
function defaultToString (value: any): string {
|
||||||
const cache: string[] = []
|
const cache: any[] = []
|
||||||
return JSON.stringify(value, (key, value) => {
|
return JSON.stringify(value, (key, value) => {
|
||||||
if (isObject(value)) {
|
if (isObject(value)) {
|
||||||
if (cache.indexOf(value) !== -1) {
|
if (cache.indexOf(value) !== -1) {
|
||||||
@@ -57,12 +57,12 @@ export function isNil (value: any): boolean {
|
|||||||
return value === null || value === undefined
|
return value === null || value === undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isArray (value: any): boolean {
|
export function isArray (value: any): value is any[] {
|
||||||
// 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: any): boolean {
|
export function isError (value: any): value is Error {
|
||||||
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' ||
|
||||||
@@ -102,7 +102,7 @@ export function last (arr: any[] | string): any | string {
|
|||||||
* @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: any): boolean {
|
export function isObject (value: any): value is object {
|
||||||
const type = typeof value
|
const type = typeof value
|
||||||
return value !== null && (type === 'object' || type === 'function')
|
return value !== null && (type === 'object' || type === 'function')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import Liquid from '../..'
|
||||||
|
import { expect, use } from 'chai'
|
||||||
|
import * as chaiAsPromised from 'chai-as-promised'
|
||||||
|
|
||||||
|
use(chaiAsPromised)
|
||||||
|
|
||||||
|
describe('drop', function () {
|
||||||
|
var engine: Liquid
|
||||||
|
beforeEach(function () {
|
||||||
|
engine = new Liquid()
|
||||||
|
})
|
||||||
|
it('should test blank strings', async function () {
|
||||||
|
const src = `
|
||||||
|
{% unless settings.fp_heading == blank %}
|
||||||
|
<h1>{{ settings.fp_heading }}</h1>
|
||||||
|
{% endunless %}`
|
||||||
|
var ctx = { settings: { fp_heading: '' } }
|
||||||
|
const html = await engine.parseAndRender(src, ctx)
|
||||||
|
return expect(html).to.match(/^\s+$/)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -62,4 +62,9 @@ describe('.parseAndRender()', function () {
|
|||||||
const html = await engine.parseAndRender(src)
|
const html = await engine.parseAndRender(src)
|
||||||
return expect(html).to.equal('apples')
|
return expect(html).to.equal('apples')
|
||||||
})
|
})
|
||||||
|
it('should support nil(null, undefined) literal', async function () {
|
||||||
|
const src = '{% if notexist == nil %}true{% endif %}'
|
||||||
|
const html = await engine.parseAndRender(src)
|
||||||
|
expect(html).to.equal('true')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -19,22 +19,20 @@ describe('tags/case', function () {
|
|||||||
const html = await liquid.parseAndRender(src)
|
const html = await liquid.parseAndRender(src)
|
||||||
return expect(html).to.equal('foo')
|
return expect(html).to.equal('foo')
|
||||||
})
|
})
|
||||||
it('should resolve empty string if not hit', async function () {
|
it('should resolve blank as empty string', async function () {
|
||||||
const src = '{% case empty %}' +
|
const src = '{% case blank %}{% when ""%}bar{%endcase%}'
|
||||||
'{% when "foo" %}foo{% when ""%}bar' +
|
const html = await liquid.parseAndRender(src)
|
||||||
'{%endcase%}'
|
return expect(html).to.equal('bar')
|
||||||
const ctx = {
|
})
|
||||||
empty: ''
|
it('should resolve empty as empty string', async function () {
|
||||||
}
|
const src = '{% case empty %}{% when ""%}bar{%endcase%}'
|
||||||
const html = await liquid.parseAndRender(src, ctx)
|
const html = await liquid.parseAndRender(src)
|
||||||
return expect(html).to.equal('bar')
|
return expect(html).to.equal('bar')
|
||||||
})
|
})
|
||||||
it('should accept empty string as branch name', async function () {
|
it('should accept empty string as branch name', async function () {
|
||||||
const src = '{% case false %}' +
|
const src = '{% case "" %}{% when ""%}bar{%endcase%}'
|
||||||
'{% when "foo" %}foo{% when ""%}bar' +
|
|
||||||
'{%endcase%}'
|
|
||||||
const html = await liquid.parseAndRender(src)
|
const html = await liquid.parseAndRender(src)
|
||||||
return expect(html).to.equal('')
|
return expect(html).to.equal('bar')
|
||||||
})
|
})
|
||||||
it('should support boolean case', async function () {
|
it('should support boolean case', async function () {
|
||||||
const src = '{% case false %}' +
|
const src = '{% case false %}' +
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import Liquid from 'src/liquid'
|
import Liquid from 'src/liquid'
|
||||||
import { expect, use } from 'chai'
|
import { expect, use } from 'chai'
|
||||||
import * as chaiAsPromised from 'chai-as-promised'
|
import * as chaiAsPromised from 'chai-as-promised'
|
||||||
import IContext from 'src/scope/icontext'
|
import { Context } from 'src/scope/scope'
|
||||||
|
|
||||||
use(chaiAsPromised)
|
use(chaiAsPromised)
|
||||||
|
|
||||||
describe('tags/for', function () {
|
describe('tags/for', function () {
|
||||||
let liquid: Liquid, ctx: IContext
|
let liquid: Liquid, ctx: Context
|
||||||
before(function () {
|
before(function () {
|
||||||
liquid = new Liquid()
|
liquid = new Liquid()
|
||||||
liquid.registerTag('throwingTag', {
|
liquid.registerTag('throwingTag', {
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { expect } from 'chai'
|
||||||
|
import Liquid from 'src/liquid'
|
||||||
|
|
||||||
|
describe('drop/blank-drop', function () {
|
||||||
|
let liquid: Liquid
|
||||||
|
before(() => (liquid = new Liquid()))
|
||||||
|
|
||||||
|
it('render blank drop as blank string', async function () {
|
||||||
|
const html = await liquid.parseAndRender('{{blank}}')
|
||||||
|
expect(html).to.equal('')
|
||||||
|
})
|
||||||
|
it('blank equals nil', async function () {
|
||||||
|
const src = '{%if blank == nil %}blank == nil{%else%}blank != nil{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src)
|
||||||
|
expect(html).to.equal('blank == nil')
|
||||||
|
})
|
||||||
|
it('false is blank', async function () {
|
||||||
|
const src = '{%if false == blank %}false == blank{%else%}false != blank{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src)
|
||||||
|
expect(html).to.equal('false == blank')
|
||||||
|
})
|
||||||
|
it('"" is blank', async function () {
|
||||||
|
const src = '{%if "" == blank %}"" == blank{%else%}"" != blank{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src)
|
||||||
|
expect(html).to.equal('"" == blank')
|
||||||
|
})
|
||||||
|
it('" " is blank', async function () {
|
||||||
|
const src = '{%if " " == blank %}" " == blank{%else%}" " != blank{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src)
|
||||||
|
expect(html).to.equal('" " == blank')
|
||||||
|
})
|
||||||
|
it('{} is blank', async function () {
|
||||||
|
const src = '{%if obj == blank %}{} == blank{%else%}{} != blank{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src, { obj: {} })
|
||||||
|
expect(html).to.equal('{} == blank')
|
||||||
|
})
|
||||||
|
it('{foo: 1} is not blank', async function () {
|
||||||
|
const src = '{%if obj == blank %}{foo: 1} == blank{%else%}{foo: 1} != blank{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src, { obj: { foo: 1 } })
|
||||||
|
expect(html).to.equal('{foo: 1} != blank')
|
||||||
|
})
|
||||||
|
it('[] is blank', async function () {
|
||||||
|
const src = '{%if arr == blank %}[] == blank{%else%}[] != blank{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src, { arr: [] })
|
||||||
|
expect(html).to.equal('[] == blank')
|
||||||
|
})
|
||||||
|
it('[1] is not blank', async function () {
|
||||||
|
const src = '{%if arr == blank %}[1] == blank{%else%}[1] != blank{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src, { arr: [1] })
|
||||||
|
expect(html).to.equal('[1] != blank')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { expect } from 'chai'
|
||||||
|
import Liquid from 'src/liquid'
|
||||||
|
|
||||||
|
describe('drop/empty-drop', function () {
|
||||||
|
let liquid: Liquid
|
||||||
|
before(() => (liquid = new Liquid()))
|
||||||
|
|
||||||
|
it('render empty drop as empty string', async function () {
|
||||||
|
const html = await liquid.parseAndRender('{{empty}}')
|
||||||
|
expect(html).to.equal('')
|
||||||
|
})
|
||||||
|
it('nil is not empty', async function () {
|
||||||
|
const src = '{%if nil == empty %}nil == empty{%else%}nil != empty{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src)
|
||||||
|
expect(html).to.equal('nil != empty')
|
||||||
|
})
|
||||||
|
it('false is not empty', async function () {
|
||||||
|
const src = '{%if false == empty %}false == empty{%else%}false != empty{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src)
|
||||||
|
expect(html).to.equal('false != empty')
|
||||||
|
})
|
||||||
|
it('"" is empty', async function () {
|
||||||
|
const src = '{%if "" == empty %}"" == empty{%else%}"" != empty{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src)
|
||||||
|
expect(html).to.equal('"" == empty')
|
||||||
|
})
|
||||||
|
it('" " is not empty', async function () {
|
||||||
|
const src = '{%if " " == empty %}" " == empty{%else%}" " != empty{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src)
|
||||||
|
expect(html).to.equal('" " != empty')
|
||||||
|
})
|
||||||
|
it('{} is empty', async function () {
|
||||||
|
const src = '{%if obj == empty %}{} == empty{%else%}{} != empty{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src, { obj: {} })
|
||||||
|
expect(html).to.equal('{} == empty')
|
||||||
|
})
|
||||||
|
it('{foo: 1} is not empty', async function () {
|
||||||
|
const src = '{%if obj == empty %}{foo: 1} == empty{%else%}{foo: 1} != empty{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src, { obj: { foo: 1 } })
|
||||||
|
expect(html).to.equal('{foo: 1} != empty')
|
||||||
|
})
|
||||||
|
it('[] is empty', async function () {
|
||||||
|
const src = '{%if arr == empty %}[] == empty{%else%}[] != empty{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src, { arr: [] })
|
||||||
|
expect(html).to.equal('[] == empty')
|
||||||
|
})
|
||||||
|
it('[1] is not empty', async function () {
|
||||||
|
const src = '{%if arr == empty %}[1] == empty{%else%}[1] != empty{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src, { arr: [1] })
|
||||||
|
expect(html).to.equal('[1] != empty')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { expect } from 'chai'
|
||||||
|
import Liquid from 'src/liquid'
|
||||||
|
|
||||||
|
describe('drop/null-drop', function () {
|
||||||
|
let liquid: Liquid
|
||||||
|
before(() => (liquid = new Liquid()))
|
||||||
|
|
||||||
|
it('render nil as empty string', async function () {
|
||||||
|
const html = await liquid.parseAndRender('{{nil}}')
|
||||||
|
expect(html).to.equal('')
|
||||||
|
})
|
||||||
|
it('render null as empty string', async function () {
|
||||||
|
const html = await liquid.parseAndRender('{{null}}')
|
||||||
|
expect(html).to.equal('')
|
||||||
|
})
|
||||||
|
it('undefined variable should equal to null', async function () {
|
||||||
|
const src = '{%if foo == nil %}foo == nil{%else%}foo != nil{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src)
|
||||||
|
expect(html).to.equal('foo == nil')
|
||||||
|
})
|
||||||
|
it('nil equals blank', async function () {
|
||||||
|
const src = '{%if nil == blank %}nil == blank{%else%}nil != blank{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src)
|
||||||
|
expect(html).to.equal('nil == blank')
|
||||||
|
})
|
||||||
|
it('0 should not equal to null', async function () {
|
||||||
|
const src = '{%if 0 == null %}0 == null{%else%}0 != null{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src)
|
||||||
|
expect(html).to.equal('0 != null')
|
||||||
|
})
|
||||||
|
it('nil should equal to null', async function () {
|
||||||
|
const src = '{%if nil == null %}nil == null{%else%}nil != null{% endif %}'
|
||||||
|
const html = await liquid.parseAndRender(src)
|
||||||
|
expect(html).to.equal('nil == null')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -2,7 +2,7 @@ import Scope from 'src/scope/scope'
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { evalExp, evalValue, isTruthy } from 'src/render/syntax'
|
import { evalExp, evalValue, isTruthy } from 'src/render/syntax'
|
||||||
|
|
||||||
describe('expression', function () {
|
describe('render/syntax', function () {
|
||||||
let scope: Scope
|
let scope: Scope
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
@@ -29,10 +29,16 @@ describe('expression', function () {
|
|||||||
expect(evalValue('-23.', scope)).to.equal(-23)
|
expect(evalValue('-23.', scope)).to.equal(-23)
|
||||||
expect(evalValue('23', scope)).to.equal(23)
|
expect(evalValue('23', scope)).to.equal(23)
|
||||||
})
|
})
|
||||||
it('should eval literal', function () {
|
it('should eval string literal', function () {
|
||||||
expect(evalValue('"ab\'c"', scope)).to.equal("ab'c")
|
expect(evalValue('"ab\'c"', scope)).to.equal("ab'c")
|
||||||
expect(evalValue("'ab\"c'", scope)).to.equal('ab"c')
|
expect(evalValue("'ab\"c'", scope)).to.equal('ab"c')
|
||||||
})
|
})
|
||||||
|
it('should eval nil literal', function () {
|
||||||
|
expect(evalValue('nil', scope)).to.be.null
|
||||||
|
})
|
||||||
|
it('should eval null literal', function () {
|
||||||
|
expect(evalValue('null', scope)).to.be.null
|
||||||
|
})
|
||||||
it('should eval scope variables', function () {
|
it('should eval scope variables', function () {
|
||||||
expect(evalValue('one', scope)).to.equal(1)
|
expect(evalValue('one', scope)).to.equal(1)
|
||||||
expect(evalValue('has_value?', scope)).to.equal(true)
|
expect(evalValue('has_value?', scope)).to.equal(true)
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import * as chai from 'chai'
|
import * as chai from 'chai'
|
||||||
import Scope from 'src/scope/scope'
|
import Scope, { Context } from 'src/scope/scope'
|
||||||
import IContext from 'src/scope/icontext'
|
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
|
||||||
describe('scope', function () {
|
describe('scope', function () {
|
||||||
let scope: Scope, ctx: IContext
|
let scope: Scope, ctx: Context
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
ctx = {
|
ctx = {
|
||||||
foo: 'zoo',
|
foo: 'zoo',
|
||||||
@@ -64,7 +63,7 @@ describe('scope', function () {
|
|||||||
expect(scope.get('foo')).equal('zoo')
|
expect(scope.get('foo')).equal('zoo')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should get undefined property', function () {
|
it('undefined property should yield undefined', function () {
|
||||||
function fn () {
|
function fn () {
|
||||||
scope.get('notdefined')
|
scope.get('notdefined')
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user