chore: migrate test cases from Chai to Jest

This commit is contained in:
Harttle
2023-03-20 00:41:06 +08:00
committed by Jun Yang
parent dccb90c591
commit c6cde9cd10
97 changed files with 8163 additions and 26440 deletions
+9
View File
@@ -0,0 +1,9 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/*.spec.ts'],
collectCoverageFrom: [
'src/**/*.ts'
]
}
+6277 -24377
View File
File diff suppressed because it is too large Load Diff
+10 -16
View File
@@ -16,8 +16,8 @@
"scripts": {
"lint": "eslint \"**/*.mjs\" \"**/*.ts\" .",
"check": "npm run build && npm test && npm run lint && npm run perf:diff",
"test": "nyc mocha \"test/**/*.ts\"",
"test:e2e": "mocha \"test/e2e/**/*.ts\"",
"test": "jest --coverage",
"test:e2e": "jest test/e2e",
"perf": "cd benchmark && npm ci && npm start",
"perf:diff": "bin/perf-diff.sh",
"perf:engines": "cd benchmark && npm run engines",
@@ -63,23 +63,18 @@
"@semantic-release/changelog": "^3.0.2",
"@semantic-release/commit-analyzer": "^6.1.0",
"@semantic-release/git": "^7.0.8",
"@semantic-release/npm": "^5.1.8",
"@semantic-release/npm": "^9.0.2",
"@semantic-release/release-notes-generator": "^7.1.4",
"@types/benchmark": "^1.0.31",
"@types/chai": "^4.1.7",
"@types/chai-as-promised": "^7.1.0",
"@types/express": "^4.17.2",
"@types/jest": "^29.4.0",
"@types/jsdom": "^12.2.2",
"@types/mocha": "^5.2.6",
"@types/sinon": "^7.0.6",
"@types/sinon-chai": "^3.2.2",
"@types/sinon": "^10.0.13",
"@types/supertest": "^2.0.7",
"@typescript-eslint/eslint-plugin": "^5.6.0",
"@typescript-eslint/parser": "^5.6.0",
"all-contributors-cli": "^6.24.0",
"benchmark": "^2.1.4",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"coveralls": "^3.0.2",
"cross-env": "^5.2.0",
"eslint": "^7.32.0",
@@ -92,22 +87,21 @@
"eslint-plugin-standard": "^4.0.0",
"express": "^4.16.4",
"husky": "^4.2.5",
"jest": "^29.5.0",
"jsdom": "^16.5.0",
"mocha": "^9.0.1",
"nyc": "^15.1.0",
"regenerator-runtime": "^0.12.1",
"rollup": "^2.61.0",
"rollup-plugin-replace": "^2.1.0",
"rollup-plugin-typescript2": "^0.31.1",
"rollup-plugin-uglify": "^6.0.4",
"rollup-plugin-uglify": "^5.0.2",
"rollup-plugin-version-injector": "^1.3.3",
"semantic-release": "^19.0.3",
"sinon": "^7.5.0",
"sinon-chai": "^3.3.0",
"sinon": "^15.0.2",
"supertest": "^3.4.2",
"ts-jest": "^29.0.5",
"ts-node": "^8.0.2",
"tslib": "^2.3.1",
"typedoc": "^0.19.2",
"typedoc": "^0.23.26",
"typedoc-plugin-markdown": "^2.2.17",
"typescript": "^4.5.3"
},
+3 -3
View File
@@ -42,17 +42,17 @@ const input = './src/index.ts'
const browserFS = {
include: './src/liquid-options.ts',
delimiters: ['', ''],
'./fs/node': './fs/browser'
'./fs/fs-impl': './build/fs-impl-browser'
}
const browserStream = {
include: './src/emitters/index.ts',
delimiters: ['', ''],
'./streamed-emitter': './streamed-emitter-browser'
'./streamed-emitter': '../build/streamed-emitter-browser'
}
const esmRequire = {
include: './src/fs/node.ts',
delimiters: ['', ''],
'./node-require': './node-require.mjs'
'./node-require': '../build/node-require.mjs'
}
const nodeCjs = {
@@ -1,16 +1,12 @@
import * as fs from '../../../src/fs/browser'
import * as fs from './fs-impl-browser'
import * as sinon from 'sinon'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
import { JSDOM } from 'jsdom'
describe('fs/browser', function () {
if (+(process.version.match(/^v(\d+)/) as RegExpMatchArray)[1] < 8) {
console.info('jsdom not supported, skipping template-browser...')
return
}
const JSDOM = require('jsdom').JSDOM
beforeEach(function () {
const dom = new JSDOM(``, {
url: 'https://example.com/foo/bar/',
@@ -24,51 +20,51 @@ describe('fs/browser', function () {
})
describe('#resolve()', function () {
it('should support relative root', function () {
expect(fs.resolve('./views/', 'foo', '')).to.equal('https://example.com/foo/bar/views/foo')
expect(fs.resolve('./views/', 'foo', '')).toBe('https://example.com/foo/bar/views/foo')
})
it('should treat root as directory', function () {
expect(fs.resolve('./views', 'foo', '')).to.equal('https://example.com/foo/bar/views/foo')
expect(fs.resolve('./views', 'foo', '')).toBe('https://example.com/foo/bar/views/foo')
})
it('should support absolute root', function () {
expect(fs.resolve('/views', 'foo', '')).to.equal('https://example.com/views/foo')
expect(fs.resolve('/views', 'foo', '')).toBe('https://example.com/views/foo')
})
it('should support empty root', function () {
expect(fs.resolve('', 'page.html', '')).to.equal('https://example.com/foo/bar/page.html')
expect(fs.resolve('', 'page.html', '')).toBe('https://example.com/foo/bar/page.html')
})
it('should support full url as root', function () {
expect(fs.resolve('https://example.com/views/', 'page.html', '')).to.equal('https://example.com/views/page.html')
expect(fs.resolve('https://example.com/views/', 'page.html', '')).toBe('https://example.com/views/page.html')
})
it('should add extname when absent', function () {
expect(fs.resolve('https://example.com/views/', 'page', '.html')).to.equal('https://example.com/views/page.html')
expect(fs.resolve('https://example.com/views/', 'page', '.html')).toBe('https://example.com/views/page.html')
})
it('should add extname for urls have searchParams', function () {
expect(fs.resolve('https://example.com/views/', 'page?foo=bar', '.html')).to.equal('https://example.com/views/page.html?foo=bar')
expect(fs.resolve('https://example.com/views/', 'page?foo=bar', '.html')).toBe('https://example.com/views/page.html?foo=bar')
})
it('should not add extname when full url is given', function () {
expect(fs.resolve('https://example.com/views/', 'https://google.com/page.php', '.html')).to.equal('https://google.com/page.php')
expect(fs.resolve('https://example.com/views/', 'https://google.com/page.php', '.html')).toBe('https://google.com/page.php')
})
it('should not add extname when already have one', function () {
expect(fs.resolve('https://example.com/views/', 'page.php', '.html')).to.equal('https://example.com/views/page.php')
expect(fs.resolve('https://example.com/views/', 'page.php', '.html')).toBe('https://example.com/views/page.php')
})
})
describe('#dirname()', () => {
it('should return dirname of file', async function () {
const val = fs.dirname('https://example.com/views/foo/bar')
expect(val).to.equal('https://example.com/views/foo/')
expect(val).toBe('https://example.com/views/foo/')
})
})
describe('#exists()', () => {
it('should always return true', async function () {
const val = await fs.exists('/foo/bar')
expect(val).to.equal(true)
expect(val).toBe(true)
})
})
describe('#existsSync()', () => {
it('should always return true', function () {
expect(fs.existsSync('/foo/bar')).to.equal(true)
expect(fs.existsSync('/foo/bar')).toBe(true)
})
})
@@ -87,15 +83,15 @@ describe('fs/browser', function () {
})
it('should get corresponding text', async function () {
const html = await fs.readFile('https://example.com/views/hello.html')
return expect(html).to.equal('hello {{name}}')
return expect(html).toBe('hello {{name}}')
})
it('should throw 404', () => {
return expect(fs.readFile('https://example.com/not/exist.html'))
.to.be.rejectedWith('Not Found')
.rejects.toHaveProperty('message', 'Not Found')
})
it('should throw error', function () {
const result = expect(fs.readFile('https://example.com/views/hello.html'))
.to.be.rejectedWith('An error occurred whilst receiving the response.')
.rejects.toHaveProperty('message', 'An error occurred whilst receiving the response.')
server.requests[0].error()
return result
})
@@ -118,11 +114,11 @@ describe('fs/browser', function () {
})
it('should get corresponding text', function () {
const html = fs.readFileSync('https://example.com/views/hello.html')
return expect(html).to.equal('hello {{name}}')
return expect(html).toBe('hello {{name}}')
})
it('should throw 404', () => {
return expect(() => fs.readFileSync('https://example.com/not/exist.html'))
.to.throw('Not Found')
.toThrow('Not Found')
})
})
})
@@ -0,0 +1,7 @@
import { StreamedEmitter } from './streamed-emitter-browser'
describe('build/streamed-emitter-browser', () => {
it('should throw when try to constructing', () => {
expect(() => new StreamedEmitter()).toThrow(/streaming not supported/)
})
})
@@ -1,4 +1,4 @@
import { Emitter } from './emitter'
import { Emitter } from '../emitters'
export class StreamedEmitter implements Emitter {
public buffer = '';
+26 -27
View File
@@ -1,68 +1,67 @@
import { expect } from 'chai'
import { LRU } from '../../../src/cache/lru'
import { LRU } from './lru'
describe('LRU', () => {
it('should perform read()/write()', () => {
const lru = new LRU(2)
expect(lru.limit).to.equal(2)
expect(lru.limit).toEqual(2)
lru.write('foo', 'FOO')
lru.write('bar', 'BAR')
expect(lru.read('foo')).to.equal('FOO')
expect(lru.read('bar')).to.equal('BAR')
expect(lru.read('foo')).toEqual('FOO')
expect(lru.read('bar')).toEqual('BAR')
})
it('should perform clear()', () => {
const lru = new LRU(2)
lru.write('foo', 'FOO')
lru.write('bar', 'BAR')
expect(lru.size).to.equal(2)
expect(lru.size).toEqual(2)
lru.clear()
expect(lru.size).to.equal(0)
expect(lru.read('foo')).to.be.undefined
expect(lru.size).toEqual(0)
expect(lru.read('foo')).toBe(undefined)
})
it('should remove lrc item when full(limit=-1)', () => {
const lru = new LRU(-1)
lru.write('foo', 'FOO')
lru.write('bar', 'BAR')
expect(lru.size).to.equal(0)
expect(lru.read('foo')).to.be.undefined
expect(lru.read('bar')).to.be.undefined
expect(lru.size).toEqual(0)
expect(lru.read('foo')).toBe(undefined)
expect(lru.read('bar')).toBe(undefined)
})
it('should remove lrc item when full(limit=0)', () => {
const lru = new LRU(0)
lru.write('foo', 'FOO')
lru.write('bar', 'BAR')
expect(lru.size).to.equal(0)
expect(lru.read('foo')).to.be.undefined
expect(lru.read('bar')).to.be.undefined
expect(lru.size).toEqual(0)
expect(lru.read('foo')).toBe(undefined)
expect(lru.read('bar')).toBe(undefined)
})
it('should remove lrc item when full(limit=1)', () => {
const lru = new LRU(1)
lru.write('foo', 'FOO')
lru.write('bar', 'BAR')
expect(lru.size).to.equal(1)
expect(lru.read('foo')).to.be.undefined
expect(lru.read('bar')).to.equal('BAR')
expect(lru.size).toEqual(1)
expect(lru.read('foo')).toBe(undefined)
expect(lru.read('bar')).toEqual('BAR')
})
it('should remove lrc item when full(limit=2)', () => {
const lru = new LRU(2)
expect(lru.size).to.equal(0)
expect(lru.size).toEqual(0)
lru.write('foo', 'FOO')
expect(lru.size).to.equal(1)
expect(lru.size).toEqual(1)
lru.write('bar', 'BAR')
expect(lru.size).to.equal(2)
expect(lru.size).toEqual(2)
lru.write('coo', 'COO')
expect(lru.size).to.equal(2)
expect(lru.read('foo')).to.be.undefined
expect(lru.read('bar')).to.equal('BAR')
expect(lru.read('coo')).to.equal('COO')
expect(lru.size).toEqual(2)
expect(lru.read('foo')).toBe(undefined)
expect(lru.read('bar')).toEqual('BAR')
expect(lru.read('coo')).toEqual('COO')
})
it('should overwrite item the with same key', () => {
const lru = new LRU(2)
lru.write('foo', 'FOO')
expect(lru.size).to.equal(1)
expect(lru.size).toEqual(1)
lru.write('foo', 'BAR')
expect(lru.size).to.equal(1)
expect(lru.read('foo')).to.equal('BAR')
expect(lru.size).toEqual(1)
expect(lru.read('foo')).toEqual('BAR')
})
})
@@ -1,8 +1,5 @@
import * as chai from 'chai'
import { Context } from '../../../src/context/context'
import { Scope } from '../../../src/context/scope'
const expect = chai.expect
import { Context } from './context'
import { Scope } from './scope'
describe('Context', function () {
let ctx: any, scope: Scope
@@ -30,67 +27,68 @@ describe('Context', function () {
describe('#get()', function () {
it('should get direct property', async function () {
expect(ctx.get(['foo'])).equal('zoo')
expect(ctx.get(['foo'])).toEqual('zoo')
})
it('should read nested property', async function () {
expect(ctx.get(['obj', 'first'])).to.equal('f')
expect(ctx.get(['obj', 'last'])).to.equal('l')
expect(ctx.get(['obj', 'size'])).to.equal(2)
expect(ctx.get(['obj', 'first'])).toEqual('f')
expect(ctx.get(['obj', 'last'])).toEqual('l')
expect(ctx.get(['obj', 'size'])).toEqual(2)
})
it('undefined property should yield undefined', async function () {
expect(ctx.get(['notdefined'])).to.equal(undefined)
expect(ctx.get([false as any])).to.equal(undefined)
expect(ctx.get(['notdefined'])).toEqual(undefined)
expect(ctx.get([false as any])).toEqual(undefined)
})
it('should respect to toLiquid', async function () {
const scope = new Context({ foo: {
toLiquid: () => ({ bar: 'BAR' }),
bar: 'bar'
} })
expect(scope.get(['foo', 'bar'])).to.equal('BAR')
// eslint-disable-next-line deprecation/deprecation
expect(scope.get(['foo', 'bar'])).toEqual('BAR')
})
it('should return undefined when not exist', async function () {
expect(ctx.get(['foo', 'foo', 'foo'])).to.be.undefined
expect(ctx.get(['foo', 'foo', 'foo'])).toBeUndefined()
})
it('should return string length as size', async function () {
expect(ctx.get(['foo', 'size'])).to.equal(3)
expect(ctx.get(['foo', 'size'])).toEqual(3)
})
it('should return array length as size', async function () {
expect(ctx.get(['bar', 'arr', 'size'])).to.equal(2)
expect(ctx.get(['bar', 'arr', 'size'])).toEqual(2)
})
it('should return map size as size', async function () {
expect(ctx.get(['map', 'size'])).to.equal(1)
expect(ctx.get(['map', 'size'])).toEqual(1)
})
it('should return undefined if not have a size', async function () {
expect(ctx.get(['one', 'size'])).to.equal(undefined)
expect(ctx.get(['non-exist', 'size'])).to.equal(undefined)
expect(ctx.get(['one', 'size'])).toBeUndefined()
expect(ctx.get(['non-exist', 'size'])).toBeUndefined()
})
it('should read .first of array', async function () {
expect(ctx.get(['bar', 'arr', 'first'])).to.equal('a')
expect(ctx.get(['bar', 'arr', 'first'])).toEqual('a')
})
it('should read .last of array', async function () {
expect(ctx.get(['bar', 'arr', 'last'])).to.equal('b')
expect(ctx.get(['bar', 'arr', 'last'])).toEqual('b')
})
it('should read element of array', async function () {
expect(ctx.get(['arr', 1])).to.equal('b')
expect(ctx.get(['arr', 1])).toEqual('b')
})
it('should read element of array from end', async function () {
expect(ctx.get(['arr', -2])).to.equal('c')
expect(ctx.get(['arr', -2])).toEqual('c')
})
it('should call function', async function () {
expect(ctx.get(['func'])).to.equal('FUNC')
expect(ctx.get(['func'])).toEqual('FUNC')
})
it('should call function before read nested property', async function () {
expect(ctx.get(['objFunc', 'prop'])).to.equal('PROP')
expect(ctx.get(['objFunc', 'prop'])).toEqual('PROP')
})
})
describe('#getFromScope()', function () {
it('should support string', () => {
expect(ctx.getFromScope({ obj: { foo: 'FOO' } }, 'obj.foo')).to.equal('FOO')
expect(ctx.getFromScope({ obj: { foo: 'FOO' } }, 'obj.foo')).toEqual('FOO')
})
})
describe('strictVariables', async function () {
describe('strictVariables', function () {
let ctx: Context
beforeEach(function () {
ctx = new Context(ctx, {
@@ -98,26 +96,26 @@ describe('Context', function () {
} as any)
})
it('should throw when variable not defined', function () {
return expect(() => ctx.get(['notdefined'])).to.throw(/undefined variable: notdefined/)
return expect(() => ctx.getSync(['notdefined'])).toThrow(/undefined variable: notdefined/)
})
it('should throw when deep variable not exist', async function () {
ctx.push({ foo: 'FOO' })
return expect(() => ctx.get(['foo', 'bar', 'not', 'defined'])).to.throw(/undefined variable: foo.bar/)
return expect(() => ctx.getSync(['foo', 'bar', 'not', 'defined'])).toThrow(/undefined variable: foo.bar/)
})
it('should throw when itself not defined', async function () {
ctx.push({ foo: 'FOO' })
return expect(() => ctx.get(['foo', 'BAR'])).to.throw(/undefined variable: foo.BAR/)
return expect(() => ctx.getSync(['foo', 'BAR'])).toThrow(/undefined variable: foo.BAR/)
})
it('should find variable in parent scope', async function () {
ctx.push({ 'foo': 'foo' })
ctx.push({
'bar': 'bar'
})
expect(ctx.get(['foo'])).to.equal('foo')
expect(ctx.getSync(['foo'])).toEqual('foo')
})
})
describe('ownPropertyOnly', async function () {
describe('ownPropertyOnly', function () {
let ctx: Context
beforeEach(function () {
ctx = new Context(ctx, {
@@ -126,56 +124,56 @@ describe('Context', function () {
})
it('should return undefined for prototype object property', function () {
ctx.push({ foo: Object.create({ bar: 'BAR' }) })
return expect(ctx.get(['foo', 'bar'])).to.equal(undefined)
return expect(ctx.getSync(['foo', 'bar'])).toEqual(undefined)
})
it('should use prototype when ownPropertyOnly=false', function () {
ctx = new Context({ foo: Object.create({ bar: 'BAR' }) }, { ownPropertyOnly: false } as any)
return expect(ctx.get(['foo', 'bar'])).to.equal('BAR')
return expect(ctx.getSync(['foo', 'bar'])).toEqual('BAR')
})
it('renderOptions.ownPropertyOnly should override options.ownPropertyOnly', function () {
ctx = new Context({ foo: Object.create({ bar: 'BAR' }) }, { ownPropertyOnly: false } as any, { ownPropertyOnly: true })
return expect(ctx.get(['foo', 'bar'])).to.equal(undefined)
return expect(ctx.getSync(['foo', 'bar'])).toEqual(undefined)
})
it('should return undefined for Array.prototype.reduce', function () {
ctx.push({ foo: [] })
return expect(ctx.get(['foo', 'reduce'])).to.equal(undefined)
return expect(ctx.getSync(['foo', 'reduce'])).toEqual(undefined)
})
it('should return undefined for function prototype property', function () {
function Foo () {}
Foo.prototype.bar = 'BAR'
ctx.push({ foo: new (Foo as any)() })
return expect(ctx.get(['foo', 'bar'])).to.equal(undefined)
return expect(ctx.getSync(['foo', 'bar'])).toEqual(undefined)
})
it('should allow function constructor properties', function () {
function Foo (this: any) { this.bar = 'BAR' }
ctx.push({ foo: new (Foo as any)() })
return expect(ctx.get(['foo', 'bar'])).to.equal('BAR')
return expect(ctx.getSync(['foo', 'bar'])).toEqual('BAR')
})
it('should return undefined for class method', function () {
class Foo { bar () {} }
ctx.push({ foo: new Foo() })
return expect(ctx.get(['foo', 'bar'])).to.equal(undefined)
return expect(ctx.getSync(['foo', 'bar'])).toEqual(undefined)
})
it('should allow class property', function () {
class Foo { bar = 'BAR' }
ctx.push({ foo: new Foo() })
return expect(ctx.get(['foo', 'bar'])).to.equal('BAR')
return expect(ctx.getSync(['foo', 'bar'])).toEqual('BAR')
})
it('should allow Array.prototype.length', function () {
ctx.push({ foo: [1, 2] })
return expect(ctx.get(['foo', 'length'])).to.equal(2)
return expect(ctx.getSync(['foo', 'length'])).toEqual(2)
})
it('should allow size to access Array.prototype.length', function () {
ctx.push({ foo: [1, 2] })
return expect(ctx.get(['foo', 'size'])).to.equal(2)
return expect(ctx.getSync(['foo', 'size'])).toEqual(2)
})
it('should allow size to access Set.prototype.size', function () {
ctx.push({ foo: new Set([1, 2]) })
return expect(ctx.get(['foo', 'size'])).to.equal(2)
return expect(ctx.getSync(['foo', 'size'])).toEqual(2)
})
it('should allow size to access Object key count', function () {
ctx.push({ foo: { bar: 'BAR', coo: 'COO' } })
return expect(ctx.get(['foo', 'size'])).to.equal(2)
return expect(ctx.getSync(['foo', 'size'])).toEqual(2)
})
it('should throw when property is hidden and strictVariables is true', function () {
ctx = new Context(ctx, {
@@ -183,13 +181,13 @@ describe('Context', function () {
strictVariables: true
} as any)
ctx.push({ foo: Object.create({ bar: 'BAR' }) })
return expect(() => ctx.get(['foo', 'bar'])).to.throw(/undefined variable: foo.bar/)
return expect(() => ctx.getSync(['foo', 'bar'])).toThrow(/undefined variable: foo.bar/)
})
})
describe('.getAll()', function () {
it('should get all properties when arguments empty', async function () {
expect(ctx.getAll()).deep.equal(scope)
expect(ctx.getAll()).toEqual(scope)
})
})
@@ -199,14 +197,14 @@ describe('Context', function () {
ctx.push({
foo: 'foo'
})
expect(ctx.get(['foo'])).to.equal('foo')
expect(ctx.get(['bar'])).to.equal('bar')
expect(ctx.getSync(['foo'])).toEqual('foo')
expect(ctx.getSync(['bar'])).toEqual('bar')
})
it('should hide deep properties by push', async function () {
ctx.push({ bar: { bar: 'bar' } })
ctx.push({ bar: { foo: 'foo' } })
expect(ctx.get(['bar', 'foo'])).to.equal('foo')
expect(ctx.get(['bar', 'bar'])).to.equal(undefined)
expect(ctx.getSync(['bar', 'foo'])).toEqual('foo')
expect(ctx.getSync(['bar', 'bar'])).toEqual(undefined)
})
})
describe('.pop()', function () {
@@ -215,7 +213,7 @@ describe('Context', function () {
foo: 'foo'
})
ctx.pop()
expect(ctx.get(['foo'])).to.equal('zoo')
expect(ctx.getSync(['foo'])).toEqual('zoo')
})
})
})
+4 -1
View File
@@ -58,9 +58,12 @@ export class Context {
.reduce((ctx, val) => __assign(ctx, val), {})
}
/**
* @deprecated use `_get()` instead
* @deprecated use `_get()` or `getSync()` instead
*/
public get (paths: PropertyKey[]): unknown {
return this.getSync(paths)
}
public getSync (paths: PropertyKey[]): unknown {
return toValueSync(this._get(paths))
}
public * _get (paths: PropertyKey[]): IterableIterator<unknown> {
+12 -16
View File
@@ -1,57 +1,53 @@
import * as fs from '../../../src/fs/node'
import * as fs from './fs-impl'
import * as path from 'path'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('fs/node', function () {
describe('fs-impl', function () {
describe('.resolve()', function () {
it('should resolve based on root', async function () {
const filepath = fs.resolve('/foo', 'bar.html', '.liquid')
const expected = path.resolve('/foo/bar.html')
return expect(filepath).to.equal(expected)
return expect(filepath).toBe(expected)
})
it('should add extension if it has no extension', async function () {
const filepath = fs.resolve('/foo', 'bar', '.liquid')
const expected = path.resolve('/foo/bar.liquid')
return expect(filepath).to.equal(expected)
return expect(filepath).toBe(expected)
})
})
describe('.existsSync', () => {
it('should resolve as false if not exists', () => {
expect(fs.existsSync('/foo/bar')).to.be.false
expect(fs.existsSync('/foo/bar')).toBeFalsy()
})
it('should resolve as true if exists', () => {
expect(fs.existsSync(__filename)).to.be.true
expect(fs.existsSync(__filename)).toBeTruthy()
})
})
describe('.exists', () => {
it('should resolve as false if not exists', async () => {
const result = await fs.exists('/foo/bar')
expect(result).to.be.false
expect(result).toBeFalsy()
})
it('should resolve as true if exists', async () => {
const result = await fs.exists(__filename)
expect(result).to.be.true
expect(result).toBeTruthy()
})
})
describe('.readFileSync', function () {
it('should throw when not exist', function () {
return expect(() => fs.readFileSync('/foo/bar')).to.throw('ENOENT')
return expect(() => fs.readFileSync('/foo/bar')).toThrow('ENOENT')
})
it('should read content if exists', function () {
const content = fs.readFileSync(__filename)
expect(content).to.contain('should read content if exists')
expect(content).toContain('should read content if exists')
})
})
describe('.readFile', function () {
it('should throw when not exist', function () {
return expect(fs.readFile('/foo/bar')).to.rejectedWith('ENOENT')
return expect(fs.readFile('/foo/bar')).rejects.toHaveProperty('message', expect.stringMatching('ENOENT'))
})
it('should read content if exists', async function () {
const content = await fs.readFile(__filename)
expect(content).to.contain('should read content if exists')
expect(content).toContain('should read content if exists')
})
})
})
@@ -1,26 +1,22 @@
import { expect, use } from 'chai'
import * as fs from '../../../src/fs/node'
import * as chaiAsPromised from 'chai-as-promised'
import { Loader } from '../../../src/fs/loader'
use(chaiAsPromised)
import * as fs from './fs-impl'
import { Loader } from './loader'
describe('fs/loader', function () {
describe('.candidates()', function () {
it('should resolve relatively', async function () {
const loader = new Loader({ relativeReference: true, fs, extname: '' } as any)
const candidates = [...loader.candidates('./foo/bar', ['/root', '/root/foo'], '/root/current', true)]
expect(candidates).to.contain('/root/foo/bar')
expect(candidates).toContain('/root/foo/bar')
})
it('should not include out of root candidates', async function () {
const loader = new Loader({ relativeReference: true, fs, extname: '' } as any)
const candidates = [...loader.candidates('../foo/bar', ['/root'], '/root/current', true)]
expect(candidates).to.have.lengthOf(0)
expect(candidates).toHaveLength(0)
})
it('should treat root as a terminated path', async function () {
const loader = new Loader({ relativeReference: true, fs, extname: '' } as any)
const candidates = [...loader.candidates('../root-dir/bar', ['/root'], '/root/current', true)]
expect(candidates).to.have.lengthOf(0)
expect(candidates).toHaveLength(0)
})
it('should default `.contains()` to () => true', async function () {
const customFs = {
@@ -29,7 +25,7 @@ describe('fs/loader', function () {
}
const loader = new Loader({ relativeReference: true, fs: customFs, extname: '' } as any)
const candidates = [...loader.candidates('../foo/bar', ['/root'], '/root/current', true)]
expect(candidates).to.contain('/foo/bar')
expect(candidates).toContain('/foo/bar')
})
})
})
@@ -1,11 +1,10 @@
import { normalize } from '../../src/liquid-options'
import { expect } from 'chai'
import { normalize } from './liquid-options'
describe('liquid-options', () => {
describe('.normalize()', () => {
it('should set cache to undefined if specified to falsy', () => {
const options = normalize({ cache: false })
expect(options.cache).to.equal(undefined)
expect(options.cache).toBeUndefined()
})
})
})
+3 -3
View File
@@ -1,7 +1,7 @@
import { assert, isArray, isString, isFunction } from './util'
import { LRU, LiquidCache } from './cache'
import { FS, LookupType } from './fs'
import * as fs from './fs/node'
import * as fs from './fs/fs-impl'
import { defaultOperators, Operators } from './render'
import { json } from './filters/misc'
import { escape } from './filters/html'
@@ -180,8 +180,8 @@ export function normalize (options: LiquidOptions): NormalizedFullOptions {
options.cache = cache
}
options = { ...defaultOptions, ...(options.jekyllInclude ? { dynamicPartials: false } : {}), ...options }
if (!options.fs!.dirname && options.relativeReference) {
console.warn('[LiquidJS] `fs.dirname` is required for relativeReference, set relativeReference to `false` to suppress this warning, or provide implementation for `fs.dirname`')
if ((!options.fs!.dirname || !options.fs!.sep) && options.relativeReference) {
console.warn('[LiquidJS] `fs.dirname` and `fs.sep` are required for relativeReference, set relativeReference to `false` to suppress this warning')
options.relativeReference = false
}
options.root = normalizeDirectoryList(options.root)
+28
View File
@@ -0,0 +1,28 @@
import { matchOperator } from './match-operator'
import { defaultOperators } from '..'
import { createTrie } from '../util/operator-trie'
describe('parser/matchOperator()', function () {
const trie = createTrie(defaultOperators)
it('should match contains', () => {
expect(matchOperator('contains', 0, trie)).toBe(8)
})
it('should match comparision', () => {
expect(matchOperator('>', 0, trie)).toBe(1)
expect(matchOperator('>=', 0, trie)).toBe(2)
expect(matchOperator('<', 0, trie)).toBe(1)
expect(matchOperator('<=', 0, trie)).toBe(2)
})
it('should match binary logic', () => {
expect(matchOperator('and', 0, trie)).toBe(3)
expect(matchOperator('or', 0, trie)).toBe(2)
})
it('should not match if word not terminate', () => {
expect(matchOperator('true1', 0, trie)).toBe(-1)
expect(matchOperator('containsa', 0, trie)).toBe(-1)
})
it('should match if word boundary found', () => {
expect(matchOperator('>=1', 0, trie)).toBe(2)
expect(matchOperator('contains b', 0, trie)).toBe(8)
})
})
@@ -1,8 +1,5 @@
import * as chai from 'chai'
import { ParseStream } from '../../../src/parser'
import { Token } from '../../../src/tokens'
const expect = chai.expect
import { ParseStream } from './parse-stream'
import { Token } from '../tokens'
describe('parseStream', () => {
it('should trigger "token" event', () => {
@@ -10,6 +7,6 @@ describe('parseStream', () => {
const ps = new ParseStream([token], (token) => ({ token } as any))
let got
ps.on('token', token => { got = token }).start()
expect(got).to.equal(token)
expect(got).toEqual(token)
})
})
+34
View File
@@ -0,0 +1,34 @@
import { parseStringLiteral } from './parse-string-literal'
describe('parseStringLiteral()', function () {
it('should parse octal escape', () => {
expect(parseStringLiteral(String.raw`"\1010"`)).toBe('A0')
expect(parseStringLiteral(String.raw`"\12"`)).toBe('\n')
expect(parseStringLiteral(String.raw`"\01"`)).toBe('\u0001')
expect(parseStringLiteral(String.raw`"\0"`)).toBe('\0')
})
it('should skip invalid octal escape', () => {
expect(parseStringLiteral(String.raw`"\9"`)).toBe('9')
})
it('should parse \\n, \\t, \\r', () => {
expect(parseStringLiteral(String.raw`"fo\no"`)).toBe('fo\no')
expect(parseStringLiteral(String.raw`'fo\to'`)).toBe('fo\to')
expect(parseStringLiteral(String.raw`'fo\ro'`)).toBe('fo\ro')
})
it('should parse unicode(hex) escape', () => {
expect(parseStringLiteral('"\\u003C"')).toBe('<')
expect(parseStringLiteral('"\\u003cZ"')).toBe('<Z')
expect(parseStringLiteral('"\\u41"')).toBe('A')
})
it('should skip invalid unicode(hex) escape', () => {
expect(parseStringLiteral('"\\u41Z"')).toBe('AZ')
expect(parseStringLiteral('"\\uZ"')).toBe('\0Z')
})
it('should parse quote escape', () => {
expect(parseStringLiteral(String.raw`"fo\'o"`)).toBe("fo'o")
expect(parseStringLiteral(String.raw`'fo\"o'`)).toBe('fo"o')
})
it('should parse slash escape', () => {
expect(parseStringLiteral(String.raw`'fo\\o'`)).toBe('fo\\o')
})
})
+546
View File
@@ -0,0 +1,546 @@
import { LiquidTagToken, HTMLToken, QuotedToken, OutputToken, TagToken, OperatorToken, RangeToken, PropertyAccessToken, NumberToken, IdentifierToken } from '../tokens'
import { Tokenizer } from './tokenizer'
describe('Tokenizer', function () {
it('should read quoted', () => {
expect(new Tokenizer('"foo" ff').readQuoted()!.getText()).toBe('"foo"')
expect(new Tokenizer(' "foo"ff').readQuoted()!.getText()).toBe('"foo"')
})
it('should read value', () => {
expect(new Tokenizer('a[ b][ "c d" ]').readValueOrThrow().getText()).toBe('a[ b][ "c d" ]')
expect(new Tokenizer('a.b[c[d.e]]').readValueOrThrow().getText()).toBe('a.b[c[d.e]]')
})
it('should read identifier', () => {
expect(new Tokenizer('foo bar').readIdentifier()).toHaveProperty('content', 'foo')
// eslint-disable-next-line deprecation/deprecation
expect(new Tokenizer('foo bar').readWord()).toHaveProperty('content', 'foo')
})
it('should read number value', () => {
const token: NumberToken = new Tokenizer('2.33.2').readValueOrThrow() as any
expect(token).toBeInstanceOf(NumberToken)
expect(token.whole.getText()).toBe('2')
expect(token.decimal!.getText()).toBe('33')
expect(token.getText()).toBe('2.33')
})
it('should read quoted value', () => {
const value = new Tokenizer('"foo"a').readValue()
expect(value).toBeInstanceOf(QuotedToken)
expect(value!.getText()).toBe('"foo"')
})
it('should read property access value', () => {
expect(new Tokenizer('a[b]["c d"]').readValueOrThrow().getText()).toBe('a[b]["c d"]')
})
it('should read quoted property access value', () => {
const value = new Tokenizer('["a prop"]').readValue()
expect(value).toBeInstanceOf(PropertyAccessToken)
expect((value as PropertyAccessToken).variable.getText()).toBe('"a prop"')
})
it('should throw for broken quoted property access', () => {
const tokenizer = new Tokenizer('[5]')
expect(() => tokenizer.readValueOrThrow()).toThrow()
})
it('should throw for incomplete quoted property access', () => {
const tokenizer = new Tokenizer('["a prop"')
expect(() => tokenizer.readValueOrThrow()).toThrow()
})
it('should read hash', () => {
const hash1 = new Tokenizer('foo: 3').readHash()
expect(hash1!.name.content).toBe('foo')
expect(hash1!.value!.getText()).toBe('3')
const hash2 = new Tokenizer(', foo: a[ "bar"]').readHash()
expect(hash2!.name.content).toBe('foo')
expect(hash2!.value!.getText()).toBe('a[ "bar"]')
})
it('should read multiple hashs', () => {
const hashes = new Tokenizer(', limit: 3 reverse offset:off').readHashes()
expect(hashes).toHaveLength(3)
const [limit, reverse, offset] = hashes
expect(limit.name.content).toBe('limit')
expect(limit.value!.getText()).toBe('3')
expect(reverse.name.content).toBe('reverse')
expect(reverse.value).toBeUndefined()
expect(offset.name.content).toBe('offset')
expect(offset.value!.getText()).toBe('off')
})
it('should read hash value with property access', () => {
const hashes = new Tokenizer('cols: 2, rows: data["rows"]').readHashes()
expect(hashes).toHaveLength(2)
const [cols, rols] = hashes
expect(cols.name.content).toBe('cols')
expect(cols.value!.getText()).toBe('2')
expect(rols.name.content).toBe('rows')
expect(rols.value!.getText()).toBe('data["rows"]')
})
describe('#readTopLevelTokens()', () => {
it('should read HTML token', function () {
const html = '<html><body><p>Lorem Ipsum</p></body></html>'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).toBe(1)
expect(tokens[0]).toBeInstanceOf(HTMLToken)
expect((tokens[0] as HTMLToken).getContent()).toBe(html)
})
it('should read tag token', function () {
const html = '<p>{% for p in a[1]%}</p>'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).toBe(3)
const tag = tokens[1] as TagToken
expect(tag).toBeInstanceOf(TagToken)
expect(tag.name).toBe('for')
expect(tag.args).toBe('p in a[1]')
})
it('should allow unclosed tag inside {% raw %}', function () {
const html = '{%raw%} {%if%} {%else {%endraw%}'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).toBe(3)
expect(tokens[0]).toHaveProperty('name', 'raw')
expect((tokens[1] as any).getContent()).toBe(' {%if%} {%else ')
})
it('should allow unclosed endraw tag inside {% raw %}', function () {
const html = '{%raw%} {%endraw {%raw%} {%endraw%}'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).toBe(3)
expect(tokens[0]).toHaveProperty('name', 'raw')
expect((tokens[1] as any).getContent()).toBe(' {%endraw {%raw%} ')
})
it('should throw when {% raw %} not closed', function () {
const html = '{%raw%} {%endraw {%raw%}'
const tokenizer = new Tokenizer(html)
expect(() => tokenizer.readTopLevelTokens()).toThrow('raw "{%raw%} {%end..." not closed, line:1, col:8')
})
it('should read output token', function () {
const html = '<p>{{foo | date: "%Y-%m-%d"}}</p>'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).toBe(3)
const output = tokens[1] as OutputToken
expect(output).toBeInstanceOf(OutputToken)
expect(output.content).toBe('foo | date: "%Y-%m-%d"')
})
it('should handle consecutive value and tags', function () {
const html = '{{foo}}{{bar}}{%foo%}{%bar%}'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).toBe(4)
const o1 = tokens[0] as OutputToken
const o2 = tokens[1] as OutputToken
const t1 = tokens[2] as TagToken
const t2 = tokens[3] as TagToken
expect(o1).toBeInstanceOf(OutputToken)
expect(o2).toBeInstanceOf(OutputToken)
expect(t1).toBeInstanceOf(TagToken)
expect(t2).toBeInstanceOf(TagToken)
expect(o1.content).toBe('foo')
expect(o2.content).toBe('bar')
expect(t1.name).toBe('foo')
expect(t1.args).toBe('')
expect(t2.name).toBe('bar')
expect(t2.args).toBe('')
})
it('should keep white spaces and newlines', function () {
const html = '{%foo%}\n{%bar %} \n {%alice%}'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).toBe(5)
expect(tokens[1]).toBeInstanceOf(HTMLToken)
expect(tokens[1].getText()).toBe('\n')
expect(tokens[3]).toBeInstanceOf(HTMLToken)
expect(tokens[3].getText()).toBe(' \n ')
})
it('should handle multiple lines tag', function () {
const html = '{%foo\na:a\nb:1.23\n%}'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).toBe(1)
expect(tokens[0]).toBeInstanceOf(TagToken)
expect((tokens[0] as TagToken).args).toBe('a:a\nb:1.23')
expect(tokens[0].getText()).toBe('{%foo\na:a\nb:1.23\n%}')
})
it('should handle multiple lines value', function () {
const html = '{{foo\n|date:\n"%Y-%m-%d"\n}}'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).toBe(1)
expect(tokens[0]).toBeInstanceOf(OutputToken)
expect(tokens[0].getText()).toBe('{{foo\n|date:\n"%Y-%m-%d"\n}}')
})
it('should handle complex object property access', function () {
const html = '{{ obj["my:property with anything"] }}'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).toBe(1)
const output = tokens[0] as OutputToken
expect(output).toBeInstanceOf(OutputToken)
expect(output.content).toBe('obj["my:property with anything"]')
})
it('should throw if tag not closed', function () {
const html = '{% assign foo = bar {{foo}}'
const tokenizer = new Tokenizer(html)
expect(() => tokenizer.readTopLevelTokens()).toThrow(/tag "{% assign foo..." not closed/)
})
it('should throw if output not closed', function () {
const tokenizer = new Tokenizer('{{name}')
expect(() => tokenizer.readTopLevelTokens()).toThrow(/output "{{name}" not closed/)
})
})
describe('#readTagToken()', () => {
it('should skip quoted delimiters', function () {
const html = '{% assign a = "%} {% }} {{" %}'
const tokenizer = new Tokenizer(html)
const token = tokenizer.readTagToken()
expect(token).toBeInstanceOf(TagToken)
expect(token.name).toBe('assign')
expect(token.args).toBe('a = "%} {% }} {{"')
})
})
describe('#readOutputToken()', () => {
it('should skip quoted delimiters', function () {
const html = '{{ "%} {%" | append: "}} {{" }}'
const tokenizer = new Tokenizer(html)
const token = tokenizer.readOutputToken()
expect(token).toBeInstanceOf(OutputToken)
expect(token.content).toBe('"%} {%" | append: "}} {{"')
})
})
describe('#readRange()', () => {
it('should read `(1..3)`', () => {
const range = new Tokenizer('(1..3)').readRange()
expect(range).toBeInstanceOf(RangeToken)
expect(range!.getText()).toEqual('(1..3)')
const { lhs, rhs } = range!
expect(lhs).toBeInstanceOf(NumberToken)
expect(lhs.getText()).toBe('1')
expect(rhs).toBeInstanceOf(NumberToken)
expect(rhs.getText()).toBe('3')
})
it('should throw for `(..3)`', () => {
expect(() => new Tokenizer('(..3)').readRange()).toThrow('unexpected token "..3)", value expected')
})
it('should read `(a.b..c["..d"])`', () => {
const range = new Tokenizer('(a.b..c["..d"])').readRange()
expect(range).toBeInstanceOf(RangeToken)
expect(range!.getText()).toEqual('(a.b..c["..d"])')
})
})
describe('#readFilter()', () => {
it('should read a simple filter', function () {
const tokenizer = new Tokenizer('| plus')
const token = tokenizer.readFilter()
expect(token).toHaveProperty('name', 'plus')
expect(token).toHaveProperty('args', [])
})
it('should read a filter with argument', function () {
const tokenizer = new Tokenizer(' | plus: 1')
const token = tokenizer.readFilter()
expect(token).toHaveProperty('name', 'plus')
expect(token!.args).toHaveLength(1)
const one: NumberToken = token!.args[0] as any
expect(one).toBeInstanceOf(NumberToken)
expect(one.getText()).toBe('1')
})
it('should read a filter with colon but no argument', function () {
const tokenizer = new Tokenizer('| plus:')
const token = tokenizer.readFilter()
expect(token).toHaveProperty('name', 'plus')
expect(token).toHaveProperty('args', [])
})
it('should read null if name not found', function () {
const tokenizer = new Tokenizer('|')
const token = tokenizer.readFilter()
expect(token).toBeNull()
})
it('should read a filter with k/v argument', function () {
const tokenizer = new Tokenizer(' | plus: a:1')
const token = tokenizer.readFilter()
expect(token).toHaveProperty('name', 'plus')
expect(token!.args).toHaveLength(1)
const [k, v]: [string, NumberToken] = token!.args[0] as any
expect(k).toBe('a')
expect(v).toBeInstanceOf(NumberToken)
expect(v.getText()).toBe('1')
})
it('should read a filter with "arr[0]" argument', function () {
const tokenizer = new Tokenizer('| plus: arr[0]')
const token = tokenizer.readFilter()
expect(token).toHaveProperty('name', 'plus')
expect(token!.args).toHaveLength(1)
const pa: PropertyAccessToken = token!.args[0] as any
expect(token!.args[0]).toBeInstanceOf(PropertyAccessToken)
expect((pa.variable as any).content).toBe('arr')
expect(pa.props).toHaveLength(1)
expect(pa.props[0]).toBeInstanceOf(NumberToken)
expect(pa.props[0].getText()).toBe('0')
})
it('should read a filter with obj.foo argument', function () {
const tokenizer = new Tokenizer('| plus: obj.foo')
const token = tokenizer.readFilter()
expect(token).toHaveProperty('name', 'plus')
expect(token!.args).toHaveLength(1)
const pa: PropertyAccessToken = token!.args[0] as any
expect(token!.args[0]).toBeInstanceOf(PropertyAccessToken)
expect((pa.variable as any).content).toBe('obj')
expect(pa.props).toHaveLength(1)
expect(pa.props[0]).toBeInstanceOf(IdentifierToken)
expect(pa.props[0].getText()).toBe('foo')
})
it('should read a filter with obj["foo"] argument', function () {
const tokenizer = new Tokenizer('| plus: obj["good luck"]')
const token = tokenizer.readFilter()
expect(token).toHaveProperty('name', 'plus')
expect(token!.args).toHaveLength(1)
const pa: PropertyAccessToken = token!.args[0] as any
expect(token!.args[0]).toBeInstanceOf(PropertyAccessToken)
expect(pa.getText()).toBe('obj["good luck"]')
expect((pa.variable as any).content).toBe('obj')
expect(pa.props[0].getText()).toBe('"good luck"')
})
})
describe('#readFilters()', () => {
it('should read simple filters', function () {
const tokenizer = new Tokenizer('| plus: 3 | capitalize')
const tokens = tokenizer.readFilters()
expect(tokens).toHaveLength(2)
expect(tokens[0]).toHaveProperty('name', 'plus')
expect(tokens[0].args).toHaveLength(1)
expect(tokens[0].args[0]).toBeInstanceOf(NumberToken)
expect((tokens[0].args[0] as any).getText()).toBe('3')
expect(tokens[1]).toHaveProperty('name', 'capitalize')
expect(tokens[1].args).toHaveLength(0)
})
it('should read filters', function () {
const tokenizer = new Tokenizer('| plus: a:3 | capitalize | append: foo[a.b["c d"]]')
const tokens = tokenizer.readFilters()
expect(tokens).toHaveLength(3)
expect(tokens[0]).toHaveProperty('name', 'plus')
expect(tokens[0].args).toHaveLength(1)
const [k, v]: [string, NumberToken] = tokens[0].args[0] as any
expect(k).toBe('a')
expect(v).toBeInstanceOf(NumberToken)
expect(v.getText()).toBe('3')
expect(tokens[1]).toHaveProperty('name', 'capitalize')
expect(tokens[1].args).toHaveLength(0)
expect(tokens[2]).toHaveProperty('name', 'append')
expect(tokens[2].args).toHaveLength(1)
expect(tokens[2].args[0]).toBeInstanceOf(PropertyAccessToken)
expect((tokens[2].args[0] as any).getText()).toBe('foo[a.b["c d"]]')
expect((tokens[2].args[0] as any).props[0].getText()).toBe('a.b["c d"]')
})
})
describe('#readExpression()', () => {
it('should read expression `a `', () => {
const exp = [...new Tokenizer('a ').readExpressionTokens()]
expect(exp).toHaveLength(1)
expect(exp[0]).toBeInstanceOf(PropertyAccessToken)
expect(exp[0].getText()).toEqual('a')
})
it('should read expression `a[][b]`', () => {
const exp = [...new Tokenizer('a[][b]').readExpressionTokens()]
expect(exp).toHaveLength(1)
const pa = exp[0] as PropertyAccessToken
expect(pa).toBeInstanceOf(PropertyAccessToken)
expect((pa.variable as any).content).toEqual('a')
expect(pa.props).toHaveLength(2)
const [p1, p2] = pa.props
expect(p1).toBeInstanceOf(IdentifierToken)
expect(p1.getText()).toBe('')
expect(p2).toBeInstanceOf(PropertyAccessToken)
expect(p2.getText()).toBe('b')
})
it('should read expression `a.`', () => {
const exp = [...new Tokenizer('a.').readExpressionTokens()]
expect(exp).toHaveLength(1)
const pa = exp[0] as PropertyAccessToken
expect(pa).toBeInstanceOf(PropertyAccessToken)
expect((pa.variable as any).content).toEqual('a')
expect(pa.props).toHaveLength(0)
})
it('should read expression `a ==`', () => {
const exp = [...new Tokenizer('a ==').readExpressionTokens()]
expect(exp).toHaveLength(2)
expect(exp[0]).toBeInstanceOf(PropertyAccessToken)
expect(exp[0].getText()).toEqual('a')
expect(exp[1]).toBeInstanceOf(OperatorToken)
expect(exp[1].getText()).toEqual('==')
})
it('should read expression `a==b`', () => {
const exp = new Tokenizer('a==b').readExpressionTokens()
const [a, equals, b] = exp
expect(a).toBeInstanceOf(PropertyAccessToken)
expect(a.getText()).toEqual('a')
expect(equals).toBeInstanceOf(OperatorToken)
expect(equals.getText()).toBe('==')
expect(b).toBeInstanceOf(PropertyAccessToken)
expect(b.getText()).toEqual('b')
})
it('should read expression `^`', () => {
const exp = new Tokenizer('^').readExpressionTokens()
expect([...exp]).toEqual([])
})
it('should read expression `a == b`', () => {
const exp = new Tokenizer('a == b').readExpressionTokens()
const [a, equals, b] = exp
expect(a).toBeInstanceOf(PropertyAccessToken)
expect(a.getText()).toEqual('a')
expect(equals).toBeInstanceOf(OperatorToken)
expect(equals.getText()).toBe('==')
expect(b).toBeInstanceOf(PropertyAccessToken)
expect(b.getText()).toEqual('b')
})
it('should read expression `(1..3) contains 3`', () => {
const exp = new Tokenizer('(1..3) contains 3').readExpressionTokens()
const [range, contains, rhs] = exp
expect(range).toBeInstanceOf(RangeToken)
expect(range.getText()).toEqual('(1..3)')
expect(contains).toBeInstanceOf(OperatorToken)
expect(contains.getText()).toBe('contains')
expect(rhs).toBeInstanceOf(NumberToken)
expect(rhs.getText()).toEqual('3')
})
it('should read expression `a[b] == c`', () => {
const exp = new Tokenizer('a[b] == c').readExpressionTokens()
const [lhs, contains, rhs] = exp
expect(lhs).toBeInstanceOf(PropertyAccessToken)
expect(lhs.getText()).toEqual('a[b]')
expect(contains).toBeInstanceOf(OperatorToken)
expect(contains.getText()).toBe('==')
expect(rhs).toBeInstanceOf(PropertyAccessToken)
expect(rhs.getText()).toEqual('c')
})
it('should read expression `c[a["b"]] >= c`', () => {
const exp = new Tokenizer('c[a["b"]] >= c').readExpressionTokens()
const [lhs, op, rhs] = exp
expect(lhs).toBeInstanceOf(PropertyAccessToken)
expect(lhs.getText()).toEqual('c[a["b"]]')
expect(op).toBeInstanceOf(OperatorToken)
expect(op.getText()).toBe('>=')
expect(rhs).toBeInstanceOf(PropertyAccessToken)
expect(rhs.getText()).toEqual('c')
})
it('should read expression `"][" == var`', () => {
const exp = new Tokenizer('"][" == var').readExpressionTokens()
const [lhs, equals, rhs] = exp
expect(lhs).toBeInstanceOf(QuotedToken)
expect(lhs.getText()).toEqual('"]["')
expect(equals).toBeInstanceOf(OperatorToken)
expect(equals.getText()).toBe('==')
expect(rhs).toBeInstanceOf(PropertyAccessToken)
expect(rhs.getText()).toEqual('var')
})
it('should read expression `"\\\'" == "\\""`', () => {
const exp = new Tokenizer('"\\\'" == "\\""').readExpressionTokens()
const [lhs, equals, rhs] = exp
expect(lhs).toBeInstanceOf(QuotedToken)
expect(lhs.getText()).toEqual('"\\\'"')
expect(equals).toBeInstanceOf(OperatorToken)
expect(equals.getText()).toBe('==')
expect(rhs).toBeInstanceOf(QuotedToken)
expect(rhs.getText()).toEqual('"\\""')
})
})
describe('#readLiquidTagTokens', () => {
it('should read newline terminated tokens', () => {
const tokenizer = new Tokenizer('echo \'hello\'')
const tokens = tokenizer.readLiquidTagTokens()
expect(tokens.length).toBe(1)
const tag = tokens[0]
expect(tag).toBeInstanceOf(LiquidTagToken)
expect(tag.name).toBe('echo')
expect(tag.args).toBe('\'hello\'')
})
it('should gracefully handle empty lines', () => {
const tokenizer = new Tokenizer(`
echo 'hello'
decrement foo
`)
const tokens = tokenizer.readLiquidTagTokens()
expect(tokens.length).toBe(2)
})
it('should throw if line does not start with an identifier', () => {
const tokenizer = new Tokenizer('!')
expect(() => tokenizer.readLiquidTagTokens()).toThrow(/illegal liquid tag syntax/)
})
})
describe('#read inline comment tags', () => {
it('should allow hash characters in tag names', () => {
const tokenizer = new Tokenizer('{% # some comment %}')
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).toBe(1)
const tag = tokens[0] as TagToken
expect(tag).toBeInstanceOf(TagToken)
expect(tag.name).toBe('#')
expect(tag.args).toBe('some comment')
})
it('should handle leading whitespace', () => {
const tokenizer = new Tokenizer('{%\n # some comment %}')
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).toBe(1)
const tag = tokens[0] as TagToken
expect(tag).toBeInstanceOf(TagToken)
expect(tag.name).toBe('#')
expect(tag.args).toBe('some comment')
})
it('should handle no trailing whitespace', () => {
const tokenizer = new Tokenizer('{%\n #some comment %}')
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).toBe(1)
const tag = tokens[0] as TagToken
expect(tag).toBeInstanceOf(TagToken)
expect(tag.name).toBe('#')
expect(tag.args).toBe('some comment')
})
})
})
+1 -2
View File
@@ -199,10 +199,9 @@ export class Tokenizer {
}
/**
* @deprecated
* @deprecated use #readIdentifier instead
*/
readWord () {
console.warn('Tokenizer#readWord() will be removed, use #readIdentifier instead')
return this.readIdentifier()
}
+94
View File
@@ -0,0 +1,94 @@
import { isTruthy, isFalsy } from './boolean'
import { Context } from '../context'
describe('boolean Shopify', function () {
describe('.isTruthy()', function () {
const ctx = {
opts: {
jsTruthy: false
}
} as unknown as Context
//
// Spec: https://shopify.github.io/liquid/basics/truthy-and-falsy/
it('true is truthy', function () {
expect(isTruthy(true, ctx)).toBeTruthy()
})
it('false is falsy', function () {
expect(isTruthy(false, ctx)).toBeFalsy()
})
it('null is falsy', function () {
expect(isTruthy(null, ctx)).toBeFalsy()
})
it('"foo" is truthy', function () {
expect(isTruthy('foo', ctx)).toBeTruthy()
})
it('"" is truthy', function () {
expect(isTruthy('', ctx)).toBeTruthy()
})
it('0 is truthy', function () {
expect(isTruthy(0, ctx)).toBeTruthy()
})
it('1 is truthy', function () {
expect(isTruthy(1, ctx)).toBeTruthy()
})
it('1.1 is truthy', function () {
expect(isTruthy(1.1, ctx)).toBeTruthy()
})
it('[1] is truthy', function () {
expect(isTruthy([1], ctx)).toBeTruthy()
})
it('[] is truthy', function () {
expect(isTruthy([], ctx)).toBeTruthy()
})
})
})
describe('boolean jsTruthy', function () {
const ctx = {
opts: {
jsTruthy: true
}
} as unknown as Context
describe('.isFalsy()', function () {
it('null is always falsy', function () {
expect(isFalsy(null, ctx)).toBeTruthy()
})
})
describe('.isTruthy()', function () {
it('true is truthy', function () {
expect(isTruthy(true, ctx)).toBeTruthy()
})
it('false is falsy', function () {
expect(isTruthy(false, ctx)).toBeFalsy()
})
it('null is always falsy', function () {
expect(isTruthy(null, ctx)).toBeFalsy()
})
it('null is always falsy', function () {
expect(isTruthy(null, ctx)).toBeFalsy()
})
it('"foo" is truthy', function () {
expect(isTruthy('foo', ctx)).toBeTruthy()
})
it('"" is falsy', function () {
expect(isTruthy('', ctx)).toBeFalsy()
})
it('0 is falsy', function () {
expect(isTruthy(0, ctx)).toBeFalsy()
})
it('1 is truthy', function () {
expect(isTruthy(1, ctx)).toBeTruthy()
})
it('1.1 is truthy', function () {
expect(isTruthy(1.1, ctx)).toBeTruthy()
})
it('[1] is truthy', function () {
expect(isTruthy([1], ctx)).toBeTruthy()
})
it('[] is truthy', function () {
expect(isTruthy([], ctx)).toBeTruthy()
})
})
})
@@ -1,8 +1,7 @@
import { Tokenizer } from '../../../src/parser/tokenizer'
import { expect } from 'chai'
import { Drop } from '../../../src/drop/drop'
import { Context } from '../../../src/context/context'
import { toPromise, toValueSync } from '../../../src/util/async'
import { Tokenizer } from '../parser'
import { Drop } from '../drop'
import { Context } from '../context'
import { toPromise, toValueSync } from '../util'
describe('Expression', function () {
const ctx = new Context({})
@@ -12,26 +11,26 @@ describe('Expression', function () {
toPromise(create('foo').evaluate(undefined!, false))
.then(() => done(new Error('should not resolved')))
.catch(err => {
expect(err.message).to.match(/context not defined/)
expect(err.message).toMatch(/context not defined/)
done()
})
})
describe('single value', function () {
it('should eval literal', async function () {
expect(await toPromise(create('2.4').evaluate(ctx, false))).to.equal(2.4)
expect(await toPromise(create('"foo"').evaluate(ctx, false))).to.equal('foo')
expect(await toPromise(create('false').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('2.4').evaluate(ctx, false))).toBe(2.4)
expect(await toPromise(create('"foo"').evaluate(ctx, false))).toBe('foo')
expect(await toPromise(create('false').evaluate(ctx, false))).toBe(false)
})
it('should eval range expression', async function () {
const ctx = new Context({ two: 2 })
expect(await toPromise(create('(2..4)').evaluate(ctx, false))).to.deep.equal([2, 3, 4])
expect(await toPromise(create('(two..4)').evaluate(ctx, false))).to.deep.equal([2, 3, 4])
expect(await toPromise(create('(2..4)').evaluate(ctx, false))).toEqual([2, 3, 4])
expect(await toPromise(create('(two..4)').evaluate(ctx, false))).toEqual([2, 3, 4])
})
it('should eval literal', async function () {
expect(await toPromise(create('2.4').evaluate(ctx, false))).to.equal(2.4)
expect(await toPromise(create('"foo"').evaluate(ctx, false))).to.equal('foo')
expect(await toPromise(create('false').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('2.4').evaluate(ctx, false))).toBe(2.4)
expect(await toPromise(create('"foo"').evaluate(ctx, false))).toBe('foo')
expect(await toPromise(create('false').evaluate(ctx, false))).toBe(false)
})
it('should eval property access', async function () {
@@ -40,18 +39,18 @@ describe('Expression', function () {
coo: 'bar',
doo: { foo: 'bar', bar: { foo: 'bar' } }
})
expect(await toPromise(create('foo.bar').evaluate(ctx, false))).to.equal('BAR')
expect(await toPromise(create('foo["bar"]').evaluate(ctx, false))).to.equal('BAR')
expect(await toPromise(create('foo[coo]').evaluate(ctx, false))).to.equal('BAR')
expect(await toPromise(create('foo[doo.foo]').evaluate(ctx, false))).to.equal('BAR')
expect(await toPromise(create('foo[doo["foo"]]').evaluate(ctx, false))).to.equal('BAR')
expect(await toPromise(create('doo[coo].foo').evaluate(ctx, false))).to.equal('bar')
expect(await toPromise(create('foo.bar').evaluate(ctx, false))).toBe('BAR')
expect(await toPromise(create('foo["bar"]').evaluate(ctx, false))).toBe('BAR')
expect(await toPromise(create('foo[coo]').evaluate(ctx, false))).toBe('BAR')
expect(await toPromise(create('foo[doo.foo]').evaluate(ctx, false))).toBe('BAR')
expect(await toPromise(create('foo[doo["foo"]]').evaluate(ctx, false))).toBe('BAR')
expect(await toPromise(create('doo[coo].foo').evaluate(ctx, false))).toBe('bar')
})
})
describe('simple expression', function () {
it('should return false for "1==2"', async () => {
expect(await toPromise(create('1==2').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('1==2').evaluate(ctx, false))).toBe(false)
})
it('should apply deep equal for arrays', async () => {
const ctx = new Context({
@@ -59,133 +58,133 @@ describe('Expression', function () {
arr2: [1, 2],
arr3: [1, 2, 3]
})
expect(await toPromise(create('arr1==arr2').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('arr1==arr3').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('arr1==arr2').evaluate(ctx, false))).toBe(true)
expect(await toPromise(create('arr1==arr3').evaluate(ctx, false))).toBe(false)
})
it('should return true for "1<2"', async () => {
expect(await toPromise(create('1<2').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('1<2').evaluate(ctx, false))).toBe(true)
})
it('should return true for "1 < 2"', async () => {
expect(await toPromise(create('1 < 2').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('1 < 2').evaluate(ctx, false))).toBe(true)
})
it('should return true for "1 < 2"', async () => {
expect(await toPromise(create('1 < 2').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('1 < 2').evaluate(ctx, false))).toBe(true)
})
it('should return true for "2 <= 2"', async () => {
expect(await toPromise(create('2 <= 2').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('2 <= 2').evaluate(ctx, false))).toBe(true)
})
it('should return true for "one <= two"', async () => {
const ctx = new Context({ one: 1, two: 2 })
expect(await toPromise(create('one <= two').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('one <= two').evaluate(ctx, false))).toBe(true)
})
it('should return false for "x contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toPromise(create('x contains "x"').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('x contains "x"').evaluate(ctx, false))).toBe(false)
})
it('should return true for "x contains "X""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toPromise(create('x contains "X"').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('x contains "X"').evaluate(ctx, false))).toBe(true)
})
it('should return false for "1 contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toPromise(create('1 contains "x"').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('1 contains "x"').evaluate(ctx, false))).toBe(false)
})
it('should return false for "y contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toPromise(create('y contains "x"').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('y contains "x"').evaluate(ctx, false))).toBe(false)
})
it('should return false for "z contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toPromise(create('z contains "x"').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('z contains "x"').evaluate(ctx, false))).toBe(false)
})
it('should return true for "(1..5) contains 3"', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toPromise(create('(1..5) contains 3').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('(1..5) contains 3').evaluate(ctx, false))).toBe(true)
})
it('should return false for "(1..5) contains 6"', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toPromise(create('(1..5) contains 6').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('(1..5) contains 6').evaluate(ctx, false))).toBe(false)
})
it('should return true for ""<=" == "<=""', async () => {
expect(await toPromise(create('"<=" == "<="').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('"<=" == "<="').evaluate(ctx, false))).toBe(true)
})
})
it('should allow space in quoted value', async function () {
const ctx = new Context({ space: ' ' })
expect(await toPromise(create('" " == space').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('" " == space').evaluate(ctx, false))).toBe(true)
})
describe('escape', () => {
it('should escape quote', async function () {
const ctx = new Context({ quote: '"' })
expect(await toPromise(create('"\\"" == quote').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('"\\"" == quote').evaluate(ctx, false))).toBe(true)
})
it('should escape square bracket', async function () {
const ctx = new Context({ obj: { ']': 'bracket' } })
expect(await toPromise(create('obj["]"] == "bracket"').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('obj["]"] == "bracket"').evaluate(ctx, false))).toBe(true)
})
})
describe('complex expression', function () {
it('should support value or value', async function () {
expect(await toPromise(create('false or true').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('false or true').evaluate(ctx, false))).toBe(true)
})
it('should support < and contains', async function () {
expect(await toPromise(create('1 < 2 and x contains "x"').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('1 < 2 and x contains "x"').evaluate(ctx, false))).toBe(false)
})
it('should support < or contains', async function () {
expect(await toPromise(create('1 < 2 or x contains "x"').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('1 < 2 or x contains "x"').evaluate(ctx, false))).toBe(true)
})
it('should support Drops for "x contains "x""', async () => {
class TemplateDrop extends Drop {
valueOf () { return 'X' }
}
const ctx = new Context({ x: 'XXX', X: new TemplateDrop() })
expect(await toPromise(create('x contains X').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('x contains X').evaluate(ctx, false))).toBe(true)
})
it('should support value and !=', async function () {
const ctx = new Context({ empty: '' })
expect(await toPromise(create('empty and empty != ""').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('empty and empty != ""').evaluate(ctx, false))).toBe(false)
})
it('should recognize quoted value', async function () {
expect(await toPromise(create('">"').evaluate(ctx, false))).to.equal('>')
expect(await toPromise(create('">"').evaluate(ctx, false))).toBe('>')
})
it('should evaluate from right to left', async function () {
expect(await toPromise(create('true or false and false').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('true and false and false or true').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('true or false and false').evaluate(ctx, false))).toBe(true)
expect(await toPromise(create('true and false and false or true').evaluate(ctx, false))).toBe(false)
})
it('should recognize property access', async function () {
const ctx = new Context({ obj: { foo: true } })
expect(await toPromise(create('obj["foo"] and true').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('obj["foo"] and true').evaluate(ctx, false))).toBe(true)
})
it('should allow nested property access', async function () {
const ctx = new Context({ obj: { foo: 'FOO' }, keys: { "what's this": 'foo' } })
expect(await toPromise(create('obj[keys["what\'s this"]]').evaluate(ctx, false))).to.equal('FOO')
expect(await toPromise(create('obj[keys["what\'s this"]]').evaluate(ctx, false))).toBe('FOO')
})
it('should support not', async function () {
expect(await toPromise(create('not 1 < 2').evaluate(ctx))).to.equal(false)
expect(await toPromise(create('not 1 < 2').evaluate(ctx))).toBe(false)
})
it('not should have higher precedence than and/or', async function () {
expect(await toPromise(create('not 1 < 2 or not 1 > 2').evaluate(ctx))).to.equal(true)
expect(await toPromise(create('not 1 < 2 and not 1 > 2').evaluate(ctx))).to.equal(false)
expect(await toPromise(create('not 1 < 2 or not 1 > 2').evaluate(ctx))).toBe(true)
expect(await toPromise(create('not 1 < 2 and not 1 > 2').evaluate(ctx))).toBe(false)
})
})
describe('sync', function () {
it('should eval literal', function () {
expect(toValueSync(create('2.4').evaluate(ctx, false))).to.equal(2.4)
expect(toValueSync(create('2.4').evaluate(ctx, false))).toBe(2.4)
})
it('should return false for "1==2"', () => {
expect(toValueSync(create('1==2').evaluate(ctx, false))).to.equal(false)
expect(toValueSync(create('1==2').evaluate(ctx, false))).toBe(false)
})
it('should escape quote', function () {
const ctx = new Context({ quote: '"' })
expect(toValueSync(create('"\\"" == quote').evaluate(ctx, false))).to.equal(true)
expect(toValueSync(create('"\\"" == quote').evaluate(ctx, false))).toBe(true)
})
it('should allow nested property access', function () {
const ctx = new Context({ obj: { foo: 'FOO' }, keys: { "what's this": 'foo' } })
expect(toValueSync(create('obj[keys["what\'s this"]]').evaluate(ctx, false))).to.equal('FOO')
expect(toValueSync(create('obj[keys["what\'s this"]]').evaluate(ctx, false))).toBe('FOO')
})
})
})
@@ -1,15 +1,13 @@
import { expect } from 'chai'
import { Context } from '../../../src/context/context'
import { HTMLToken, TagToken } from '../../../src/tokens'
import { Render } from '../../../src/render/render'
import { HTML } from '../../../src/template/html'
import { SimpleEmitter } from '../../../src/emitters/simple-emitter'
import { toPromise } from '../../../src/util/async'
import { Tag } from '../../../src/template/tag'
import { Context } from '../context'
import { HTMLToken, TagToken } from '../tokens'
import { Render } from './render'
import { Tag, HTML } from '../template'
import { SimpleEmitter } from '../emitters'
import { toPromise } from '../util'
describe('render', function () {
let render: Render
before(function () {
beforeEach(function () {
render = new Render()
})
@@ -18,7 +16,7 @@ describe('render', function () {
const scope = new Context()
const token = { getContent: () => '<p>' } as HTMLToken
const html = await toPromise(render.renderTemplates([new HTML(token)], scope, new SimpleEmitter()))
return expect(html).to.equal('<p>')
return expect(html).toBe('<p>')
})
})
@@ -35,7 +33,7 @@ describe('render', function () {
result += data
})
stream.on('end', () => {
expect(result).to.equal('<p></p>')
expect(result).toBe('<p></p>')
done()
})
})
@@ -59,7 +57,7 @@ describe('render', function () {
result += data
})
stream.on('end', () => {
expect(result).to.equal('<p>async tag</p>')
expect(result).toBe('<p>async tag</p>')
done()
})
})
@@ -1,58 +1,55 @@
import * as chai from 'chai'
import * as sinon from 'sinon'
import * as sinonChai from 'sinon-chai'
import { Context } from '../../../../src/context'
import { toPromise } from '../../../../src/util'
import { IdentifierToken, NumberToken, QuotedToken } from '../../../../src/tokens'
import { Filter } from '../../../../src/template'
chai.use(sinonChai)
const expect = chai.expect
import { Context } from '../context'
import { toPromise } from '../util'
import { IdentifierToken, NumberToken, QuotedToken } from '../tokens'
import { Filter } from './filter'
describe('filter', function () {
const ctx = new Context()
const liquid = {} as any
const ctx = new Context({ thirty: 30 })
const liquid = { testVersion: '1.0' } as any
it('should not change input if filter not registered', async function () {
const filter = new Filter('foo', undefined as any, [], liquid)
expect(await toPromise(filter.render('value', ctx))).to.equal('value')
expect(await toPromise(filter.render('value', ctx))).toBe('value')
})
it('should call filter impl with correct arguments', async function () {
const spy = sinon.spy()
const spy = jest.fn()
const thirty = new NumberToken(new IdentifierToken('30', 0, 2), undefined)
const filter = new Filter('foo', spy, [thirty], liquid)
await toPromise(filter.render('foo', ctx))
expect(spy).to.have.been.calledWith('foo', 30)
expect(spy).toHaveBeenCalledWith('foo', 30)
})
it('should call filter impl with correct this', async function () {
const spy = sinon.spy()
const thirty = new NumberToken(new IdentifierToken('33', 0, 2), undefined)
await toPromise(new Filter('foo', spy, [thirty], liquid).render('foo', ctx))
expect(spy).to.have.been.calledOn(sinon.match.has('context', ctx))
expect(spy).to.have.been.calledOn(sinon.match.has('liquid', liquid))
const spy = jest.fn(function * (valStr, diff): Generator<string> {
const val = yield this.context._get([valStr])
return `${this.liquid.testVersion}: ${val + diff}`
})
const ten = new NumberToken(new IdentifierToken('10', 0, 2), undefined)
const filter = new Filter('add', spy, [ten], liquid)
const val = await toPromise(filter.render('thirty', ctx))
expect(val).toEqual('1.0: 40')
})
it('should render a simple filter', async function () {
expect(await toPromise(new Filter('upcase', (x: string) => x.toUpperCase(), [], liquid).render('foo', ctx))).to.equal('FOO')
expect(await toPromise(new Filter('upcase', (x: string) => x.toUpperCase(), [], liquid).render('foo', ctx))).toBe('FOO')
})
it('should render filters with argument', async function () {
const two = new NumberToken(new IdentifierToken('2', 0, 1), undefined)
expect(await toPromise(new Filter('add', (a: number, b: number) => a + b, [two], liquid).render(3, ctx))).to.equal(5)
expect(await toPromise(new Filter('add', (a: number, b: number) => a + b, [two], liquid).render(3, ctx))).toBe(5)
})
it('should render filters with multiple arguments', async function () {
const two = new NumberToken(new IdentifierToken('2', 0, 1), undefined)
const c = new QuotedToken('"c"', 0, 3)
expect(await toPromise(new Filter('add', (a: number, b: number, c: number) => a + b + c, [two, c], liquid).render(3, ctx))).to.equal('5c')
expect(await toPromise(new Filter('add', (a: number, b: number, c: number) => a + b + c, [two, c], liquid).render(3, ctx))).toBe('5c')
})
it('should pass Objects/Drops as it is', async function () {
class Foo {}
expect(await toPromise(new Filter('name', (a: any) => a.constructor.name, [], liquid).render(new Foo(), ctx))).to.equal('Foo')
expect(await toPromise(new Filter('name', (a: any) => a.constructor.name, [], liquid).render(new Foo(), ctx))).toBe('Foo')
})
it('should support key value pairs', async function () {
const two = new NumberToken(new IdentifierToken('2', 0, 1), undefined)
expect(await toPromise(new Filter('add', (a: number, b: number[]) => b[0] + ':' + (a + b[1]), [['num', two]], liquid).render(3, ctx))).to.equal('num:5')
expect(await toPromise(new Filter('add', (a: number, b: number[]) => b[0] + ':' + (a + b[1]), [['num', two]], liquid).render(3, ctx))).toBe('num:5')
})
})
@@ -1,41 +1,38 @@
import * as chai from 'chai'
import { toPromise } from '../../../src/util/async'
import { Hash } from '../../../src/template/hash'
import { Context } from '../../../src/context/context'
const expect = chai.expect
import { toPromise } from '../util'
import { Hash } from './hash'
import { Context } from '../context'
describe('Hash', function () {
it('should parse "reverse"', async function () {
const hash = await toPromise(new Hash('reverse').render(new Context({ foo: 3 })))
expect(hash).to.haveOwnProperty('reverse')
expect(hash.reverse).to.be.true
expect(hash).toHaveProperty('reverse')
expect(hash.reverse).toBeTruthy()
})
it('should parse "num:foo"', async function () {
const hash = await toPromise(new Hash('num:foo').render(new Context({ foo: 3 })))
expect(hash.num).to.equal(3)
expect(hash.num).toBe(3)
})
it('should parse "num:3"', async function () {
const hash = await toPromise(new Hash('num:3').render(new Context()))
expect(hash.num).to.equal(3)
expect(hash.num).toBe(3)
})
it('should parse "num: arr[0]"', async function () {
const hash = await toPromise(new Hash('num:3').render(new Context({ arr: [3] })))
expect(hash.num).to.equal(3)
expect(hash.num).toBe(3)
})
it('should parse "num: 2.3"', async function () {
const hash = await toPromise(new Hash('num:2.3').render(new Context()))
expect(hash.num).to.equal(2.3)
expect(hash.num).toBe(2.3)
})
it('should parse "num:bar.coo"', async function () {
const pending = new Hash('num:bar.coo').render(new Context({ bar: { coo: 3 } }))
const hash = await toPromise(pending)
expect(hash.num).to.equal(3)
expect(hash.num).toBe(3)
})
it('should parse "num1:2.3 reverse,num2:bar.coo\n num3: arr[0]"', async function () {
const ctx = new Context({ bar: { coo: 3 }, arr: [4] })
const hash = await toPromise(new Hash('num1:2.3 reverse,num2:bar.coo\n num3: arr[0]').render(ctx))
expect(hash).to.deep.equal({
expect(hash).toEqual({
num1: 2.3,
reverse: true,
num2: 3,
@@ -1,11 +1,8 @@
import * as chai from 'chai'
import { toPromise } from '../../../src/util/async'
import { Context } from '../../../src/context/context'
import { Output } from '../../../src/template/output'
import { OutputToken } from '../../../src/tokens/output-token'
import { defaultOptions } from '../../../src/liquid-options'
const expect = chai.expect
import { toPromise } from '../util'
import { Context } from '../context'
import { Output } from '../template'
import { OutputToken } from '../tokens'
import { defaultOptions } from '../liquid-options'
describe('Output', function () {
const emitter: any = { write: (html: string) => (emitter.html += html), html: '' }
@@ -18,27 +15,27 @@ describe('Output', function () {
})
const output = new Output({ content: 'foo' } as OutputToken, liquid)
await toPromise(output.render(scope, emitter))
return expect(emitter.html).to.equal('[object Object]')
return expect(emitter.html).toBe('[object Object]')
})
it('should skip function property', async function () {
const scope = new Context({ obj: { foo: 'foo', bar: (x: any) => x } })
const output = new Output({ content: 'obj' } as OutputToken, liquid)
await toPromise(output.render(scope, emitter))
return expect(emitter.html).to.equal('[object Object]')
return expect(emitter.html).toBe('[object Object]')
})
it('should respect to .toString()', async () => {
const scope = new Context({ obj: { toString: () => 'FOO' } })
const output = new Output({ content: 'obj' } as OutputToken, liquid)
await toPromise(output.render(scope, emitter))
return expect(emitter.html).to.equal('FOO')
return expect(emitter.html).toBe('FOO')
})
it('should respect to .toString()', async () => {
const scope = new Context({ obj: { toString: () => 'FOO' } })
const output = new Output({ content: 'obj' } as OutputToken, liquid)
await toPromise(output.render(scope, emitter))
return expect(emitter.html).to.equal('FOO')
return expect(emitter.html).toBe('FOO')
})
context('when keepOutputType is enabled', () => {
describe('when keepOutputType is enabled', () => {
const emitter: any = {
write: (html: any) => {
if (emitter.keepOutputType && typeof html !== 'string') {
@@ -59,7 +56,7 @@ describe('Output', function () {
}, { ...defaultOptions, keepOutputType: true })
const output = new Output({ content: 'foo' } as OutputToken, liquid)
await toPromise(output.render(scope, emitter))
return expect(emitter.html).to.equal(42)
return expect(emitter.html).toBe(42)
})
it('should respect output variable boolean type', async () => {
const scope = new Context({
@@ -67,7 +64,7 @@ describe('Output', function () {
}, { ...defaultOptions, keepOutputType: true })
const output = new Output({ content: 'foo' } as OutputToken, liquid)
await toPromise(output.render(scope, emitter))
return expect(emitter.html).to.equal(true)
return expect(emitter.html).toBe(true)
})
it('should respect output variable object type', async () => {
const scope = new Context({
@@ -75,7 +72,7 @@ describe('Output', function () {
}, { ...defaultOptions, keepOutputType: true })
const output = new Output({ content: 'foo' } as OutputToken, liquid)
await toPromise(output.render(scope, emitter))
return expect(emitter.html).to.equal('test')
return expect(emitter.html).toBe('test')
})
it('should respect output variable string type', async () => {
const scope = new Context({
@@ -83,7 +80,7 @@ describe('Output', function () {
}, { ...defaultOptions, keepOutputType: true })
const output = new Output({ content: 'foo' } as OutputToken, liquid)
await toPromise(output.render(scope, emitter))
return expect(emitter.html).to.deep.equal({ a: { b: 42 } })
return expect(emitter.html).toEqual({ a: { b: 42 } })
})
})
})
+37
View File
@@ -0,0 +1,37 @@
import { Liquid } from '../liquid'
import { QuotedToken } from '../tokens'
import { toPromise } from '../util'
import { Context } from '../context'
import { Value } from '../template'
describe('Value', function () {
const liquid = new Liquid()
describe('#constructor()', function () {
it('should parse filters in value content', function () {
const f = new Value('o | foo: a: "a"', liquid)
expect(f.filters[0].name).toBe('foo')
expect(f.filters[0].args).toHaveLength(1)
const [k, v] = f.filters[0].args[0] as any
expect(k).toBe('a')
expect(v).toBeInstanceOf(QuotedToken)
expect((v as QuotedToken).getText()).toBe('"a"')
})
})
describe('#value()', function () {
it('should call chained filters correctly', async function () {
const date = jest.fn(() => 'y')
const time = jest.fn()
liquid.registerFilter('date', date)
liquid.registerFilter('time', time)
const tpl = new Value('foo.bar | date: "b" | time:2', liquid)
const scope = new Context({
foo: { bar: 'bar' }
})
await toPromise(tpl.value(scope, false))
expect(date).toHaveBeenCalledWith('bar', 'b')
expect(time).toHaveBeenCalledWith('y', 2)
})
})
})
+14
View File
@@ -0,0 +1,14 @@
import { QuotedToken, PropertyAccessToken, IdentifierToken } from '.'
describe('PropertyAccessToken', function () {
describe('#propertyName', function () {
it('should return correct value for IdentifierToken', function () {
const token = new PropertyAccessToken(new IdentifierToken('foo', 0, 3), [], 3)
expect(token.propertyName).toBe('foo')
})
it('should return correct value for QuotedToken', function () {
const token = new PropertyAccessToken(new QuotedToken('"foo bar"', 0, 9), [], 9)
expect(token.propertyName).toBe('foo bar')
})
})
})
@@ -1,19 +1,16 @@
import * as chai from 'chai'
import { assert } from '../../../src/util/assert'
const expect = chai.expect
import { assert } from './assert'
describe('assert', function () {
it('should not throw if predicate is truthy', function () {
const fn = () => assert('foo', () => 'bar')
expect(fn).to.not.throw()
expect(fn).not.toThrow()
})
it('should not throw if predicate is truthy', function () {
const fn = () => assert('', () => 'bar')
expect(fn).to.throw(/bar/)
expect(fn).toThrow(/bar/)
})
it('should populate default message', function () {
const fn = () => assert(false)
expect(fn).to.throw(/expect false to be true/)
expect(fn).toThrow(/expect false to be true/)
})
})
@@ -1,8 +1,4 @@
import { toPromise, toValueSync } from '../../../src/util/async'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
import { toPromise, toValueSync } from './async'
describe('utils/async', () => {
describe('#toPromise()', function () {
@@ -11,21 +7,21 @@ describe('utils/async', () => {
return 'foo'
}
const result = await toPromise(foo())
expect(result).to.equal('foo')
expect(result).toBe('foo')
})
it('should support iterable with single return statement', async () => {
function * foo () {
return 'foo'
}
const result = await toPromise(foo())
expect(result).to.equal('foo')
expect(result).toBe('foo')
})
it('should support promise', async () => {
function foo () {
return Promise.resolve('foo')
}
const result = await toPromise(foo())
expect(result).to.equal('foo')
expect(result).toBe('foo')
})
it('should resolve dependency', async () => {
function * foo (): Generator<Generator<string>> {
@@ -35,14 +31,14 @@ describe('utils/async', () => {
return 'bar'
}
const result = await toPromise(foo())
expect(result).to.equal('bar')
expect(result).toBe('bar')
})
it('should support promise dependency', async () => {
function * foo (): Generator<Promise<string>> {
return yield Promise.resolve('foo')
}
const result = await toPromise(foo())
expect(result).to.equal('foo')
expect(result).toBe('foo')
})
it('should reject Promise if dependency throws syncly', done => {
function * foo (): Generator<Generator<never>> {
@@ -52,7 +48,7 @@ describe('utils/async', () => {
throw new Error('bar')
}
toPromise(foo()).catch(err => {
expect(err.message).to.equal('bar')
expect(err.message).toBe('bar')
done()
return 0 as any
})
@@ -72,7 +68,7 @@ describe('utils/async', () => {
throw new Error('bar')
}
const ret = await toPromise(foo())
expect(ret).to.equal('barfoo')
expect(ret).toBe('barfoo')
})
})
describe('#toValueSync()', function () {
@@ -83,7 +79,7 @@ describe('utils/async', () => {
function * bar (): Generator<never> {
throw new Error('bar')
}
expect(() => toValueSync(foo())).to.throw('bar')
expect(() => toValueSync(foo())).toThrow('bar')
})
it('should resume yield after catch', () => {
function * foo (): Generator<unknown, never, never> {
@@ -95,7 +91,7 @@ describe('utils/async', () => {
function * bar (): Generator<never> {
throw new Error('bar')
}
expect(toValueSync(foo())).to.equal('foo')
expect(toValueSync(foo())).toBe('foo')
})
it('should resume return after catch', () => {
function * foo (): Generator<Generator<never>, string> {
@@ -107,10 +103,10 @@ describe('utils/async', () => {
function * bar (): Generator<never> {
throw new Error('bar')
}
expect(toValueSync(foo())).to.equal('foo')
expect(toValueSync(foo())).toBe('foo')
})
it('should return non iterator value as it is', () => {
expect(toValueSync('foo')).to.equal('foo')
expect(toValueSync('foo')).toBe('foo')
})
})
})
@@ -1,7 +1,5 @@
import * as chai from 'chai'
import { strftime as t } from '../../../src/util/strftime'
import { DateWithTimezone } from '../../stub/date-with-timezone'
const expect = chai.expect
import { strftime as t } from './strftime'
import { DateWithTimezone } from '../../test/stub/date-with-timezone'
describe('util/strftime', function () {
const now = new Date('2016-01-04 13:15:23')
@@ -9,112 +7,112 @@ describe('util/strftime', function () {
describe('Date (Year, Month, Day)', () => {
it('should format %C as century', function () {
expect(t(now, '%C')).to.equal('20')
expect(t(now, '%C')).toBe('20')
})
it('should format %B as month name', function () {
expect(t(now, '%B')).to.equal('January')
expect(t(now, '%B')).toBe('January')
})
it('should format %e as space padded date', function () {
expect(t(now, '%e')).to.equal(' 4')
expect(t(now, '%e')).toBe(' 4')
})
it('should format %y as 2-digit year', function () {
expect(t(now, '%y')).to.equal('16')
expect(t(now, '%y')).toBe('16')
})
describe('%j', function () {
it('should format %j as day of year', function () {
expect(t(then, '%j')).to.equal('066')
expect(t(then, '%j')).toBe('066')
})
it('should take count of leap years', function () {
const date = new Date('2001 03 01')
expect(t(date, '%j')).to.equal('060')
expect(t(date, '%j')).toBe('060')
})
it('should take count of leap years', function () {
const date = new Date('2000 03 01')
expect(t(date, '%j')).to.equal('061')
expect(t(date, '%j')).toBe('061')
})
})
it('should format %q as date suffix', function () {
const st = new Date('2016-03-01 03:05:03')
const nd = new Date('2016-03-02 03:05:03')
const rd = new Date('2016-03-03 03:05:03')
expect(t(st, '%q')).to.equal('st')
expect(t(nd, '%q')).to.equal('nd')
expect(t(rd, '%q')).to.equal('rd')
expect(t(now, '%q')).to.equal('th')
expect(t(st, '%q')).toBe('st')
expect(t(nd, '%q')).toBe('nd')
expect(t(rd, '%q')).toBe('rd')
expect(t(now, '%q')).toBe('th')
})
})
describe('Time (Hour, Minute, Second, Subsecond)', function () {
it('should format %I as 0 padded hour12', function () {
expect(t(now, '%I')).to.equal('01')
expect(t(now, '%I')).toBe('01')
})
it('should format %I as 12 for 00:00', function () {
const date = new Date('2016-01-01 00:00:00')
expect(t(date, '%I')).to.equal('12')
expect(t(date, '%I')).toBe('12')
})
it('should format %k as space padded hour', function () {
expect(t(then, '%k')).to.equal(' 3')
expect(t(then, '%k')).toBe(' 3')
})
it('should format %l as space padded hour12', function () {
expect(t(now, '%l')).to.equal(' 1')
expect(t(now, '%l')).toBe(' 1')
})
it('should format %l as 12 for 00:00', function () {
const date = new Date('2016-01-01 00:00:00')
expect(t(date, '%l')).to.equal('12')
expect(t(date, '%l')).toBe('12')
})
it('should format %L as 0 padded millisecond', function () {
expect(t(then, '%L')).to.equal('000')
expect(t(then, '%L')).toBe('000')
})
it('should format %N as fractional seconds digits', function () {
const time = new Date('2019-12-15 01:21:00.129')
expect(t(time, '%N')).to.equal('129000000')
expect(t(time, '%2N')).to.equal('12')
expect(t(time, '%10N')).to.equal('1290000000')
expect(t(time, '%0N')).to.equal('129000000')
expect(t(time, '%N')).toBe('129000000')
expect(t(time, '%2N')).toBe('12')
expect(t(time, '%10N')).toBe('1290000000')
expect(t(time, '%0N')).toBe('129000000')
})
it('should format %p as upper cased am/pm', function () {
expect(t(now, '%p')).to.equal('PM')
expect(t(then, '%p')).to.equal('AM')
expect(t(now, '%p')).toBe('PM')
expect(t(then, '%p')).toBe('AM')
})
it('should format %P as lower cased am/pm', function () {
expect(t(now, '%P')).to.equal('pm')
expect(t(now, '%^8P')).to.equal(' PM')
expect(t(then, '%P')).to.equal('am')
expect(t(now, '%P')).toBe('pm')
expect(t(now, '%^8P')).toBe(' PM')
expect(t(then, '%P')).toBe('am')
})
})
describe('Weekday', function () {
it('should format %A as Monday', function () {
expect(t(now, '%A')).to.equal('Monday')
expect(t(now, '%^A')).to.equal('MONDAY')
expect(t(now, '%#A')).to.equal('MONDAY')
expect(t(now, '%A')).toBe('Monday')
expect(t(now, '%^A')).toBe('MONDAY')
expect(t(now, '%#A')).toBe('MONDAY')
})
it('should format %a as Mon', function () {
expect(t(now, '%a')).to.equal('Mon')
expect(t(now, '%^a')).to.equal('MON')
expect(t(now, '%a')).toBe('Mon')
expect(t(now, '%^a')).toBe('MON')
})
it('should format %u as day of week(1-7)', function () {
expect(t(now, '%u')).to.be.equal('1')
expect(t(then, '%u')).to.be.equal('7')
expect(t(now, '%u')).toBe('1')
expect(t(then, '%u')).toBe('7')
})
it('should format %w as day of week(0-7)', function () {
expect(t(now, '%w')).to.be.equal('1')
expect(t(then, '%w')).to.be.equal('0')
expect(t(now, '%w')).toBe('1')
expect(t(then, '%w')).toBe('0')
})
})
describe('Seconds since the Unix Epoch', () => {
it('should format %s as UNIX seconds', function () {
expect(t(now, '%s')).to.be.match(/\d+/)
expect(t(now, '%s')).toMatch(/\d+/)
})
})
describe('Week number', () => {
it('should format %U as week of year, starts with 0', function () {
expect(t(now, '%U')).to.equal('01')
expect(t(now, '%U')).toBe('01')
})
it('should format %W as week of year, starts with 1', function () {
expect(t(now, '%W')).to.be.equal('01')
expect(t(now, '%W')).toBe('01')
})
})
@@ -122,98 +120,98 @@ describe('util/strftime', function () {
it('should format %z as time zone', function () {
// suppose we're in +8:00
const now = new DateWithTimezone('2016-01-04 13:15:23', -480)
expect(t(now, '%z')).to.equal('+0800')
expect(t(now, '%z')).toBe('+0800')
})
it('should format %z as negative time zone', function () {
// suppose we're in -8:00
const date = new DateWithTimezone('2016-01-04T13:15:23.000Z', 480)
expect(t(date, '%z')).to.equal('-0800')
expect(t(date, '%z')).toBe('-0800')
})
})
describe('combination', () => {
it('should format %x as local date string', function () {
expect(t(now, '%x')).to.equal(now.toLocaleDateString())
expect(t(now, '%x')).toBe(now.toLocaleDateString())
})
it('should format %X as local time string', function () {
expect(t(now, '%X')).to.equal(now.toLocaleTimeString())
expect(t(now, '%X')).toBe(now.toLocaleTimeString())
})
it('should format detailed datetime', function () {
expect(t(now, '%Y-%m-%d %H:%M:%S')).to.equal('2016-01-04 13:15:23')
expect(t(now, '%Y-%m-%d %H:%M:%S')).toBe('2016-01-04 13:15:23')
})
it('should format %c as local string', function () {
expect(t(now, '%c')).to.equal(now.toLocaleString())
expect(t(now, '%c')).toBe(now.toLocaleString())
})
})
describe('literal strings', () => {
it('should escape %% as %', function () {
expect(t(now, '%%')).to.equal('%')
expect(t(now, '%%')).toBe('%')
})
it('should escape %n as \\n', function () {
expect(t(now, '%n')).to.equal('\n')
expect(t(now, '%n')).toBe('\n')
})
it('should escape %t as \\t', function () {
expect(t(now, '%t')).to.equal('\t')
expect(t(now, '%t')).toBe('\t')
})
it('should retain un-recognized formaters', function () {
expect(t(now, '%o')).to.equal('%o')
expect(t(now, '%o')).toBe('%o')
})
})
describe('width field', () => {
it('should support width field', () => {
expect(t(now, '%8Y')).to.equal('00002016')
expect(t(now, '%8Y')).toBe('00002016')
})
it('should ignore invalid width', () => {
expect(t(then, '%1Y')).to.equal('2016')
expect(t(then, '%1H')).to.equal('3')
expect(t(then, '%1Y')).toBe('2016')
expect(t(then, '%1H')).toBe('3')
})
it('should have higher priority than H', () => {
expect(t(then, '%0H')).to.equal('03')
expect(t(then, '%0H')).toBe('03')
})
})
describe('modifier field', () => {
it('should ignore E modifier', () => {
expect(t(now, '%EY')).to.equal('2016')
expect(t(now, '%EY')).toBe('2016')
})
it('should ignore O modifier', () => {
expect(t(now, '%OY')).to.equal('2016')
expect(t(now, '%OY')).toBe('2016')
})
it('should support modifier with width field', () => {
expect(t(now, '%8EY')).to.equal('00002016')
expect(t(now, '%8EY')).toBe('00002016')
})
})
describe('flags field', () => {
it('should support - flag', () => {
expect(t(now, '%-m')).to.equal('1')
expect(t(now, '%-m')).toBe('1')
})
it('should support _ flag', () => {
expect(t(now, '%_m')).to.equal(' 1')
expect(t(now, '%_m')).toBe(' 1')
})
it('should support 0 flag', () => {
expect(t(now, '%0m')).to.equal('01')
expect(t(now, '%0m')).toBe('01')
})
it('should support ^ flag', () => {
expect(t(now, '%^B')).to.equal('JANUARY')
expect(t(now, '%^B')).toBe('JANUARY')
})
it('should respect to specific conversion', () => {
expect(t(now, '%^P')).to.equal('PM')
expect(t(now, '%P')).to.equal('pm')
expect(t(now, '%^P')).toBe('PM')
expect(t(now, '%P')).toBe('pm')
})
it('should support # flag', () => {
expect(t(now, '%#B')).to.equal('JANUARY')
expect(t(now, '%#P')).to.equal('PM')
expect(t(now, '%#B')).toBe('JANUARY')
expect(t(now, '%#P')).toBe('PM')
})
it('should support : flag', () => {
// suppose we're in +8:00
const date = new DateWithTimezone('2016-01-04T13:15:23.000Z', -480)
expect(t(date, '%:z')).to.equal('+08:00')
expect(t(date, '%z')).to.equal('+0800')
expect(t(date, '%:z')).toBe('+08:00')
expect(t(date, '%z')).toBe('+0800')
})
it('should support multiple flags', () => {
expect(t(now, '%^08P')).to.equal('000000PM')
expect(t(now, '%^08P')).toBe('000000PM')
})
})
})
@@ -1,38 +1,37 @@
import { TimezoneDate } from '../../../src/util/timezone-date'
import { expect } from 'chai'
import { TimezoneDate } from './timezone-date'
describe('TimezoneDate', () => {
it('should respect timezone set to 00:00', () => {
const date = new TimezoneDate('2021-10-06T14:26:00.000+08:00', 0)
expect(date.getTimezoneOffset()).to.equal(0)
expect(date.getHours()).to.equal(6)
expect(date.getMinutes()).to.equal(26)
expect(date.getTimezoneOffset()).toBe(0)
expect(date.getHours()).toBe(6)
expect(date.getMinutes()).toBe(26)
})
it('should respect timezone set to -06:00', () => {
const date = new TimezoneDate('2021-10-06T14:26:00.000+08:00', -360)
expect(date.getTimezoneOffset()).to.equal(-360)
expect(date.getMinutes()).to.equal(26)
expect(date.getTimezoneOffset()).toBe(-360)
expect(date.getMinutes()).toBe(26)
})
it('should support Date as argument', () => {
const date = new TimezoneDate(new Date('2021-10-06T14:26:00.000+08:00'), 0)
expect(date.getHours()).to.equal(6)
expect(date.getHours()).toBe(6)
})
it('should support .getMilliseconds()', () => {
const date = new TimezoneDate('2021-10-06T14:26:00.001+00:00', 0)
expect(date.getMilliseconds()).to.equal(1)
expect(date.getMilliseconds()).toBe(1)
})
it('should support .getDay()', () => {
const date = new TimezoneDate('2021-12-07T00:00:00.001+08:00', -480)
expect(date.getDay()).to.equal(2)
expect(date.getDay()).toBe(2)
})
it('should support .toLocaleTimeString()', () => {
const date = new TimezoneDate('2021-10-06T00:00:00.001+00:00', -480)
expect(date.toLocaleTimeString('en-US')).to.equal('8:00:00 AM')
expect(() => date.toLocaleDateString()).to.not.throw()
expect(date.toLocaleTimeString('en-US')).toBe('8:00:00 AM')
expect(() => date.toLocaleDateString()).not.toThrow()
})
it('should support .toLocaleDateString()', () => {
const date = new TimezoneDate('2021-10-06T22:00:00.001+00:00', -480)
expect(date.toLocaleDateString('en-US')).to.equal('10/7/2021')
expect(() => date.toLocaleDateString()).to.not.throw()
expect(date.toLocaleDateString('en-US')).toBe('10/7/2021')
expect(() => date.toLocaleDateString()).not.toThrow()
})
})
@@ -1,128 +1,122 @@
import * as chai from 'chai'
import * as sinonChai from 'sinon-chai'
import * as sinon from 'sinon'
import * as _ from '../../../src/util/underscore'
const expect = chai.expect
chai.use(sinonChai)
import * as _ from './underscore'
describe('util/underscore', function () {
describe('.isString()', function () {
it('should return true for literal string', function () {
expect(_.isString('foo')).to.be.true
expect(_.isString('foo')).toBeTruthy()
})
it('should return true String instance', function () {
expect(_.isString(String('foo'))).to.be.true
expect(_.isString(String('foo'))).toBeTruthy()
})
it('should return false for 123 ', function () {
expect(_.isString(123)).to.be.false
expect(_.isString(123)).toBeFalsy()
})
})
describe('.isNumber()', function () {
it('should return false for "foo"', function () {
expect(_.isNumber('foo')).to.be.false
expect(_.isNumber('foo')).toBeFalsy()
})
it('should return true for 0', function () {
expect(_.isNumber(0)).to.be.true
expect(_.isNumber(0)).toBeTruthy()
})
})
describe('.stringify()', function () {
it('should return "" for null', function () {
expect(_.stringify(null)).to.equal('')
expect(_.stringify(null)).toBe('')
})
it('should return "" for undefined', function () {
expect(_.stringify(undefined)).to.equal('')
expect(_.stringify(undefined)).toBe('')
})
it('should return regex string for RegExp', function () {
const reg = /foo/g
expect(_.stringify(reg)).to.equal('/foo/g')
expect(_.stringify(reg)).toBe('/foo/g')
})
it('should return locale string for date', function () {
const date = new Date('2018-10-01T14:51:00.000Z')
// Mon Oct 01 2018 22:51:00 GMT+0800 (CST)
expect(_.stringify(date)).to.equal(date.toString())
expect(_.stringify(date)).toBe(date.toString())
})
})
describe('.forOwn()', function () {
it('should iterate all properties', function () {
const spy = sinon.spy()
const spy = jest.fn()
const obj = {
foo: 'bar'
}
_.forOwn(obj, spy)
expect(spy).to.have.been.calledWith('bar', 'foo', obj)
expect(spy).toHaveBeenCalledWith('bar', 'foo', obj)
})
it('should default to empty object', function () {
const spy = sinon.spy()
const spy = jest.fn()
_.forOwn(undefined, spy)
expect(spy).to.have.not.been.called
expect(spy).not.toHaveBeenCalled()
})
it('should not iterate over properties on prototype', function () {
const spy = sinon.spy()
const spy = jest.fn()
const obj = Object.create({
bar: 'foo'
})
obj.foo = 'bar'
_.forOwn(obj, spy)
expect(spy).to.have.been.calledOnce
expect(spy).to.have.been.calledWith('bar', 'foo', obj)
expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toHaveBeenCalledWith('bar', 'foo', obj)
})
it('should break when returned false', function () {
const spy = sinon.stub().returns(false)
const spy = jest.fn(() => false)
_.forOwn({
'foo': 'foo',
'bar': 'foo'
}, spy)
expect(spy).to.have.been.calledOnce
expect(spy).toHaveBeenCalledTimes(1)
})
})
describe('.range()', function () {
it('should return a range of integers', function () {
expect(_.range(3, 5)).to.deep.equal([3, 4])
expect(_.range(3, 5)).toEqual([3, 4])
})
})
describe('.isObject()', function () {
it('should return true for function', function () {
expect(_.isObject((x: any) => x)).to.be.true
expect(_.isObject((x: any) => x)).toBeTruthy()
})
it('should return true for plain object', function () {
expect(_.isObject({})).to.be.true
expect(_.isObject({})).toBeTruthy()
})
it('should return false for null', function () {
expect(_.isObject(null)).to.be.false
expect(_.isObject(null)).toBeFalsy()
})
it('should return false for number', function () {
expect(_.isObject(2)).to.be.false
expect(_.isObject(2)).toBeFalsy()
})
})
describe('.padEnd()', function () {
it('should default ch to " "', () => {
expect(_.padEnd('foo', 5)).to.equal('foo ')
expect(_.padEnd('foo', 5)).toBe('foo ')
})
})
describe('.changeCase()', function () {
it('should to upper case if there is one lowercase', () => {
expect(_.changeCase('fooA')).to.equal('FOOA')
expect(_.changeCase('fooA')).toBe('FOOA')
})
it('should to lower case if all upper case', () => {
expect(_.changeCase('FOOA')).to.equal('fooa')
expect(_.changeCase('FOOA')).toBe('fooa')
})
})
describe('.caseInsensitiveCompare()', function () {
it('should "foo" > "bar"', () => {
expect(_.caseInsensitiveCompare('foo', 'bar')).to.equal(1)
expect(_.caseInsensitiveCompare('foo', 'bar')).toBe(1)
})
it('should "foo" < null', () => {
expect(_.caseInsensitiveCompare('foo', null)).to.equal(-1)
expect(_.caseInsensitiveCompare('foo', null)).toBe(-1)
})
it('should null > "foo"', () => {
expect(_.caseInsensitiveCompare(null, 'foo')).to.equal(1)
expect(_.caseInsensitiveCompare(null, 'foo')).toBe(1)
})
it('should -1 < 0', () => {
expect(_.caseInsensitiveCompare(-1, 0)).to.equal(-1)
expect(_.caseInsensitiveCompare(-1, 0)).toBe(-1)
})
it('should 1 > 0', () => {
expect(_.caseInsensitiveCompare(1, 0)).to.equal(1)
expect(_.caseInsensitiveCompare(1, 0)).toBe(1)
})
})
})
+2 -6
View File
@@ -1,9 +1,5 @@
import { Liquid } from '../../src/liquid'
import { Drop } from '../../src/drop/drop'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
class SettingsDrop extends Drop {
private foo = 'FOO'
@@ -24,7 +20,7 @@ describe('drop', function () {
it('should support liquidMethodMissing', async function () {
const src = `{{settings.foo}},{{settings.bar}},{{settings.coo}}`
const html = await engine.parseAndRender(src, { settings })
return expect(html).to.equal('FOO,BAR,COO')
return expect(html).toBe('FOO,BAR,COO')
})
describe('BlandDrop', function () {
@@ -35,7 +31,7 @@ describe('drop', function () {
{% endunless %}`
var ctx = { settings: { fpHeading: '' } }
const html = await engine.parseAndRender(src, ctx)
return expect(html).to.match(/^\s+$/)
return expect(html).toMatch(/^\s+$/)
})
})
})
@@ -1,4 +1,3 @@
import { expect } from 'chai'
import { Liquid } from '../..'
describe('#evalValueSync()', function () {
@@ -6,6 +5,6 @@ describe('#evalValueSync()', function () {
beforeEach(() => { engine = new Liquid() })
it('should eval value syncly', async function () {
return expect(engine.evalValueSync('true', { opts: {} } as any)).to.equal(true)
return expect(engine.evalValueSync('true', { opts: {} } as any)).toBe(true)
})
})
@@ -1,4 +1,3 @@
import { expect } from 'chai'
import { Liquid } from '../..'
describe('#evalValue()', function () {
@@ -7,16 +6,16 @@ describe('#evalValue()', function () {
it('should support boolean', async function () {
const val = await engine.evalValue('true')
expect(val).to.equal(true)
expect(val).toBe(true)
})
it('should support binary expression with Context', async function () {
const val = await engine.evalValue('a > b', { a: 1, b: 2 })
expect(val).to.equal(false)
expect(val).toBe(false)
})
it('should inherit Liquid options', async function () {
const val = await engine.evalValue('foo')
expect(val).to.equal('FOO')
expect(val).toBe('FOO')
})
})
@@ -1,4 +1,3 @@
import { expect } from 'chai'
import * as request from 'supertest'
import * as express from 'express'
import { resolve } from 'path'
@@ -47,8 +46,8 @@ describe('express()', function () {
const ctx = {}
engine.express().call(view, file, ctx, function (err: any) {
try {
expect(err.code).to.equal('ENOENT')
expect(err.message).to.match(/Failed to lookup/)
expect(err.code).toBe('ENOENT')
expect(err.message).toMatch(/Failed to lookup/)
done()
} catch (e) {
done(e)
+67 -71
View File
@@ -1,18 +1,11 @@
import { TopLevelToken, TagToken, Tokenizer, Context, Liquid, Drop, toValueSync } from '../..'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import * as sinon from 'sinon'
import * as sinonChai from 'sinon-chai'
const LiquidUMD = require('../../dist/liquid.browser.umd.js').Liquid
use(chaiAsPromised)
use(sinonChai)
describe('Issues', function () {
it('#221 unicode blanks are not properly treated', async () => {
const engine = new Liquid({ strictVariables: true, strictFilters: true })
const html = engine.parseAndRenderSync('{{huh | truncate: 11}}', { huh: 'fdsafdsafdsafdsaaaaa' })
expect(html).to.equal('fdsafdsa...')
expect(html).toBe('fdsafdsa...')
})
it('#252 "Not valid identifier" error for a quotes-containing identifier', async () => {
const template = `{% capture "form_classes" -%}
@@ -20,12 +13,12 @@ describe('Issues', function () {
{%- endcapture %}{{form_classes}}`
const engine = new Liquid()
const html = await engine.parseAndRender(template)
expect(html).to.equal('foo')
expect(html).toBe('foo')
})
it('#259 complex property access with braces is not supported', async () => {
const engine = new Liquid()
const html = engine.parseAndRenderSync('{{ ["complex key"] }}', { 'complex key': 'foo' })
expect(html).to.equal('foo')
expect(html).toBe('foo')
})
it('#243 Potential for ReDoS through string replace function', async () => {
const engine = new Liquid()
@@ -38,13 +31,13 @@ describe('Issues', function () {
const html = engine.parseAndRenderSync(template, parameters)
// should stringify the regexp rather than execute it
expect(html).to.equal(INPUT)
expect(html).toBe(INPUT)
})
it('#263 raw/endraw block not ignoring {% characters', () => {
const template = `{% raw %}This is a code snippet showing how {% breaks the raw block.{% endraw %}`
const engine = new Liquid()
const html = engine.parseAndRenderSync(template)
expect(html).to.equal('This is a code snippet showing how {% breaks the raw block.')
expect(html).toBe('This is a code snippet showing how {% breaks the raw block.')
})
it('#268 elsif is not supported for unless', () => {
const template = `{%- unless condition1 -%}
@@ -56,7 +49,7 @@ describe('Issues', function () {
{% endunless %}`
const engine = new Liquid()
const html = engine.parseAndRenderSync(template, { condition1: true, condition2: true })
expect(html).to.equal('<div>Y</div>')
expect(html).toBe('<div>Y</div>')
})
it('#277 Passing liquid in FilterImpl', () => {
const engine = new Liquid()
@@ -67,12 +60,12 @@ describe('Issues', function () {
`{{ subtemplate | render: "foo" }}`,
{ subtemplate: encodeURIComponent('hello {{ name }}') }
)
expect(html).to.equal('hello foo')
expect(html).toBe('hello foo')
})
it('#288 Unexpected behavior when string literals contain }}', async () => {
const engine = new Liquid()
const html = await engine.parseAndRender(`{{ '{{' }}{{ '}}' }}`)
expect(html).to.equal('{{}}')
expect(html).toBe('{{}}')
})
it('#222 Support function calls', async () => {
const engine = new Liquid()
@@ -80,7 +73,7 @@ describe('Issues', function () {
`{{ obj.property }}`,
{ obj: { property: () => 'BAR' } }
)
expect(html).to.equal('BAR')
expect(html).toBe('BAR')
})
it('#313 lenientIf not working as expected in umd', async () => {
const engine = new LiquidUMD({
@@ -88,7 +81,7 @@ describe('Issues', function () {
lenientIf: true
})
const html = await engine.parseAndRender(`{{ name | default: "default name" }}`)
expect(html).to.equal('default name')
expect(html).toBe('default name')
})
it('#321 comparison for empty/nil', async () => {
const engine = new Liquid()
@@ -96,30 +89,31 @@ describe('Issues', function () {
'{% if empty == nil %}true{%else%}false{%endif%}' +
'{% if nil == empty %}true{%else%}false{%endif%}'
)
expect(html).to.equal('falsefalse')
expect(html).toBe('falsefalse')
})
it('#320 newline_to_br filter should output <br /> instead of <br/>', async () => {
const engine = new Liquid()
const html = await engine.parseAndRender(
`{{ 'a \n b \n c' | newline_to_br | split: '<br />' }}`
)
expect(html).to.equal('a \n b \n c')
expect(html).toBe('a \n b \n c')
})
it('#342 New lines in logical operator', async () => {
const engine = new Liquid()
const tpl = `{%\r\nif\r\ntrue\r\nor\r\nfalse\r\n%}\r\ntrue\r\n{%\r\nendif\r\n%}`
const html = await engine.parseAndRender(tpl)
expect(html).to.equal('\r\ntrue\r\n')
expect(html).toBe('\r\ntrue\r\n')
})
it('#401 Timezone Offset Issue', async () => {
const engine = new Liquid({ timezoneOffset: -600 })
const tpl = engine.parse('{{ date | date: "%Y-%m-%d %H:%M %p %z" }}')
const html = await engine.render(tpl, { date: '2021-10-06T15:31:00+08:00' })
expect(html).to.equal('2021-10-06 17:31 PM +1000')
expect(html).toBe('2021-10-06 17:31 PM +1000')
})
it('#412 Pass root as it is to `resolve`', async () => {
const engine = new Liquid({
root: '/tmp',
relativeReference: false,
fs: {
readFileSync: (file: string) => file,
async readFile (file: string) { return 'foo' },
@@ -130,15 +124,16 @@ describe('Issues', function () {
})
const tpl = engine.parse('{% include "foo.liquid" %}')
const html = await engine.renderSync(tpl)
expect(html).to.equal('/tmp/foo.liquid')
expect(html).toBe('/tmp/foo.liquid')
})
it('#416 Templates imported by {% render %} not cached for concurrent async render', async () => {
const readFile = sinon.spy(() => Promise.resolve('HELLO'))
const exists = sinon.spy(() => 'HELLO')
const readFile = jest.fn(() => Promise.resolve('HELLO'))
const exists = jest.fn(() => 'HELLO')
const engine = new Liquid({
cache: true,
extname: '.liquid',
root: '~',
relativeReference: false,
fs: {
exists,
resolve: (root: string, file: string, ext: string) => root + '#' + file + ext,
@@ -150,20 +145,21 @@ describe('Issues', function () {
await Promise.all(Array(5).fill(0).map(
x => engine.parseAndRender("{% render 'template' %}")
))
expect(exists).to.be.calledOnce
expect(readFile).to.be.calledOnce
expect(exists).toHaveBeenCalledTimes(1)
expect(readFile).toHaveBeenCalledTimes(1)
})
it('#431 Error when using Date timezoneOffset in 9.28.5', async () => {
it('#431 Error when using Date timezoneOffset in 9.28.5', () => {
const engine = new Liquid({
timezoneOffset: 0,
preserveTimezones: true
})
const tpl = engine.parse('Welcome to {{ now | date: "%Y-%m-%d" }}!')
expect(engine.render(tpl, { now: new Date('2019/02/01') })).to.eventually.equal('Welcome to 2019-02-01')
const tpl = engine.parse('Welcome to {{ now | date: "%Y-%m-%d" }}')
return expect(engine.render(tpl, { now: new Date('2019-02-01T00:00:00.000Z') })).resolves.toBe('Welcome to 2019-02-01')
})
it('#433 Support Jekyll-like includes', async () => {
const engine = new Liquid({
dynamicPartials: false,
relativeReference: false,
root: '/tmp',
fs: {
readFileSync: (file: string) => file,
@@ -175,7 +171,7 @@ describe('Issues', function () {
})
const tpl = engine.parse('{% include prefix/{{ my_variable | append: "-bar" }}/suffix %}')
const html = await engine.render(tpl, { my_variable: 'foo' })
expect(html).to.equal('CONTENT for /tmp/prefix/foo-bar/suffix')
expect(html).toBe('CONTENT for /tmp/prefix/foo-bar/suffix')
})
it('#428 Implement liquid/echo tags', () => {
const template = `{%- liquid
@@ -192,17 +188,17 @@ describe('Issues', function () {
-%}`
const engine = new Liquid()
const html = engine.parseAndRenderSync(template, { array: [1, 2, 3] })
expect(html).to.equal('4#8#12#6')
expect(html).toBe('4#8#12#6')
})
it('#454 leaking JS prototype getter functions in evaluation', async () => {
const engine = new Liquid({ ownPropertyOnly: true })
const html = engine.parseAndRenderSync('{{foo | size}}-{{bar.coo}}', { foo: 'foo', bar: Object.create({ coo: 'COO' }) })
expect(html).to.equal('3-')
expect(html).toBe('3-')
})
it('#465 Liquidjs divided_by not compatible with Ruby/Shopify Liquid', () => {
const engine = new Liquid({ ownPropertyOnly: true })
const html = engine.parseAndRenderSync('{{ 5 | divided_by: 3, true }}')
expect(html).to.equal('1')
expect(html).toBe('1')
})
it('#479 url_encode throws on undefined value', async () => {
const engine = new Liquid({
@@ -210,7 +206,7 @@ describe('Issues', function () {
})
const tpl = engine.parse('{{ v | url_encode }}')
const html = await engine.render(tpl, { v: undefined })
expect(html).to.equal('')
expect(html).toBe('')
})
it('#481 filters that should not throw', async () => {
const engine = new Liquid()
@@ -225,17 +221,17 @@ describe('Issues', function () {
{{ foo | concat | json }}
`)
const html = await engine.render(tpl, { foo: undefined })
expect(html.trim()).to.equal('[]')
expect(html.trim()).toBe('[]')
})
it('#481 concat should always return an array', async () => {
const engine = new Liquid()
const html = await engine.parseAndRender(`{{ foo | concat | json }}`)
expect(html).to.equal('[]')
expect(html).toBe('[]')
})
it('#486 Access array items from the right with negative indexes', async () => {
const engine = new Liquid()
const html = await engine.parseAndRender(`{% assign a = "x,y,z" | split: ',' -%}{{ a[-1] }} {{ a[-3] }} {{ a[-8] }}`)
expect(html).to.equal('z x ')
expect(html).toBe('z x ')
})
it('#492 contains operator does not support Drop', async () => {
class TemplateDrop extends Drop {
@@ -244,44 +240,44 @@ describe('Issues', function () {
const engine = new Liquid()
const ctx = { template: new TemplateDrop() }
const html = await engine.parseAndRender(`{% if template contains "product" %}contains{%endif%}`, ctx)
expect(html).to.equal('contains')
expect(html).toBe('contains')
})
it('#513 should support large number of templates [async]', async () => {
const engine = new Liquid()
const html = await engine.parseAndRender(`{% for i in (1..10000) %}{{ i }}{% endfor %}`)
expect(html).to.have.lengthOf(38894)
expect(html).toHaveLength(38894)
})
it('#513 should support large number of templates [sync]', () => {
const engine = new Liquid()
const html = engine.parseAndRenderSync(`{% for i in (1..10000) %}{{ i }}{% endfor %}`)
expect(html).to.have.lengthOf(38894)
expect(html).toHaveLength(38894)
})
it('#519 should throw parse error for invalid assign expression', () => {
const engine = new Liquid()
expect(() => engine.parse('{% assign headshot = https://testurl.com/not_enclosed_in_quotes.jpg %}')).to.throw(/unexpected token at ":/)
expect(() => engine.parse('{% assign headshot = https://testurl.com/not_enclosed_in_quotes.jpg %}')).toThrow(/unexpected token at ":/)
})
it('#527 export Liquid Expression', () => {
const tokenizer = new Tokenizer('a > b')
const expression = tokenizer.readExpression()
const result = toValueSync(expression.evaluate(new Context({ a: 1, b: 2 })))
expect(result).to.equal(false)
expect(result).toBe(false)
})
it('#527 export Liquid Expression (evalValue)', async () => {
const liquid = new Liquid()
const result = await liquid.evalValue('a > b', { a: 1, b: 2 })
expect(result).to.equal(false)
expect(result).toBe(false)
})
it('#527 export Liquid Expression (evalValueSync)', async () => {
const liquid = new Liquid()
const result = liquid.evalValueSync('a > b', { a: 1, b: 2 })
expect(result).to.equal(false)
expect(result).toBe(false)
})
it('#276 Promise support in expressions', async () => {
const liquid = new Liquid()
const tpl = '{%if name == "alice" %}true{%endif%}'
const ctx = { name: Promise.resolve('alice') }
const html = await liquid.parseAndRender(tpl, ctx)
expect(html).to.equal('true')
expect(html).toBe('true')
})
it('#533 Nested Promise support for scope object', async () => {
const liquid = new Liquid()
@@ -308,26 +304,26 @@ describe('Issues', function () {
})
}
expect(await liquid.evalValue('a == 1', context)).to.equal(true)
expect(await liquid.evalValue('b == 1', context)).to.equal(true)
expect(await liquid.evalValue('c == 1', context)).to.equal(true)
expect(await liquid.evalValue('d.d == 1', context)).to.equal(true)
expect(await liquid.evalValue('e.e == 1', context)).to.equal(true)
expect(await liquid.evalValue('f.f == 1', context)).to.equal(true)
expect(await liquid.evalValue('g.g == 1', context)).to.equal(true)
expect(await liquid.evalValue('h.h == 1', context)).to.equal(true)
expect(await liquid.evalValue('i.i == 1', context)).to.equal(true)
expect(await liquid.evalValue('j.j == 1', context)).to.equal(true)
expect(await liquid.parseAndRender('{{a}}', context)).to.equal('1')
expect(await liquid.parseAndRender('{{b}}', context)).to.equal('1')
expect(await liquid.parseAndRender('{{c}}', context)).to.equal('1')
expect(await liquid.parseAndRender('{{d.d}}', context)).to.equal('1')
expect(await liquid.parseAndRender('{{e.e}}', context)).to.equal('1')
expect(await liquid.parseAndRender('{{f.f}}', context)).to.equal('1')
expect(await liquid.parseAndRender('{{g.g}}', context)).to.equal('1')
expect(await liquid.parseAndRender('{{h.h}}', context)).to.equal('1')
expect(await liquid.parseAndRender('{{i.i}}', context)).to.equal('1')
expect(await liquid.parseAndRender('{{j.j}}', context)).to.equal('1')
expect(await liquid.evalValue('a == 1', context)).toBe(true)
expect(await liquid.evalValue('b == 1', context)).toBe(true)
expect(await liquid.evalValue('c == 1', context)).toBe(true)
expect(await liquid.evalValue('d.d == 1', context)).toBe(true)
expect(await liquid.evalValue('e.e == 1', context)).toBe(true)
expect(await liquid.evalValue('f.f == 1', context)).toBe(true)
expect(await liquid.evalValue('g.g == 1', context)).toBe(true)
expect(await liquid.evalValue('h.h == 1', context)).toBe(true)
expect(await liquid.evalValue('i.i == 1', context)).toBe(true)
expect(await liquid.evalValue('j.j == 1', context)).toBe(true)
expect(await liquid.parseAndRender('{{a}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{b}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{c}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{d.d}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{e.e}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{f.f}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{g.g}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{h.h}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{i.i}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{j.j}}', context)).toBe('1')
})
it('#559 Case/When should evaluate multiple When statements', async () => {
const liquid = new Liquid()
@@ -344,7 +340,7 @@ describe('Issues', function () {
{% endcase %}
`
const html = await liquid.parseAndRender(tpl)
expect(html).to.match(/^\s*This is a love or luck potion.\s+This is a strength or health or love potion.\s*$/)
expect(html).toMatch(/^\s*This is a love or luck potion.\s+This is a strength or health or love potion.\s*$/)
})
it('#570 tag registration compatible to v9', async () => {
const liquid = new Liquid()
@@ -360,7 +356,7 @@ describe('Issues', function () {
const tpl = '{% metadata_file foo %}'
const ctx = { foo: 'FOO' }
const html = await liquid.parseAndRender(tpl, ctx)
expect(html).to.equal('FOO')
expect(html).toBe('FOO')
})
it('#573 date filter should return parsed input when no format is provided', async () => {
const liquid = new Liquid()
@@ -376,7 +372,7 @@ describe('Issues', function () {
const tpl = `{{ 'now' | date }}`
const html = await liquid.parseAndRender(tpl)
// sample: Thursday, February 2, 2023 at 6:25 pm +0000
expect(html).to.match(/\w+, \w+ \d+, \d\d\d\d at \d+:\d\d [ap]m [-+]\d\d\d\d/)
expect(html).toMatch(/\w+, \w+ \d+, \d\d\d\d at \d+:\d\d [ap]m [-+]\d\d\d\d/)
})
it('#575 Add support for Not operator', async () => {
const liquid = new Liquid()
@@ -388,7 +384,7 @@ describe('Issues', function () {
{% endif %}`
const ctx = { link: 'https://example.com', button: false }
const html = await liquid.parseAndRender(tpl, ctx)
expect(html.trim()).to.equal('<a href="https://example.com">Lot more code here</a>')
expect(html.trim()).toBe('<a href="https://example.com">Lot more code here</a>')
})
it('#70 strip multiline content of <style>', async () => {
const str = `
@@ -398,7 +394,7 @@ describe('Issues', function () {
const engine = new Liquid()
const template = '{{ str | strip_html }}'
const html = await engine.parseAndRender(template, { str })
expect(html).to.match(/^\s*$/)
expect(html).toMatch(/^\s*$/)
})
it('#589 Arrays should compare values', async () => {
const engine = new Liquid()
@@ -408,6 +404,6 @@ describe('Issues', function () {
{% if people1 == people2 %}true{%else%}false{% endif %}
`
const html = await engine.parseAndRender(template)
expect(html).to.contain('true')
expect(html).toContain('true')
})
})
@@ -1,8 +1,4 @@
import { Liquid } from '../..'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('.parseAndRender()', function () {
var engine: Liquid, strictEngine: Liquid
@@ -15,51 +11,50 @@ describe('.parseAndRender()', function () {
it('should stringify array ', async function () {
var ctx = { arr: [-2, 'a'] }
const html = await engine.parseAndRender('{{arr}}', ctx)
return expect(html).to.equal('-2a')
return expect(html).toBe('-2a')
})
it('should render undefined as empty', async function () {
const html = await engine.parseAndRender('foo{{zzz}}bar', {})
return expect(html).to.equal('foobar')
return expect(html).toBe('foobar')
})
it('should render as null when filter undefined', async function () {
const html = await engine.parseAndRender('{{"foo" | filter1}}', {})
return expect(html).to.equal('foo')
return expect(html).toBe('foo')
})
it('should throw upon undefined filter when strictFilters set', function () {
return expect(strictEngine.parseAndRender('{{"foo" | filter1}}', {})).to
.be.rejectedWith(/undefined filter: filter1/)
return expect(strictEngine.parseAndRender('{{"foo" | filter1}}', {})).rejects.toThrow(/undefined filter: filter1/)
})
it('should parse html', function () {
expect(function () {
engine.parse('{{obj}}')
}).to.not.throw()
}).not.toThrow()
expect(function () {
engine.parse('<html><head>{{obj}}</head></html>')
}).to.not.throw()
}).not.toThrow()
})
it('template should be able to be rendered multiple times', async function () {
const ctx = { obj: [1, 2] }
const template = engine.parse('{{obj}}')
const result = await engine.render(template, ctx)
expect(result).to.equal('12')
expect(result).toBe('12')
const result2 = await engine.render(template, ctx)
expect(result2).to.equal('12')
expect(result2).toBe('12')
})
it('should support the "join" filter', async function () {
var ctx = { names: ['alice', 'bob'] }
var template = engine.parse('<p>{{names | join: ","}}</p>')
const html = await engine.render(template, ctx)
return expect(html).to.equal('<p>alice,bob</p>')
return expect(html).toBe('<p>alice,bob</p>')
})
it('should support the "first" filter', async function () {
var src = '{% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}' +
'{{ my_array | first }}'
const html = await engine.parseAndRender(src)
return expect(html).to.equal('apples')
return expect(html).toBe('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')
expect(html).toBe('true')
})
})
@@ -1,10 +1,6 @@
import { Liquid } from '../..'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import { resolve } from 'path'
use(chaiAsPromised)
describe('#renderFile()', function () {
const root = resolve(__dirname, '../stub/root')
const views = resolve(__dirname, '../stub/views')
@@ -17,16 +13,16 @@ describe('#renderFile()', function () {
})
it('should render file', async function () {
const html = await engine.renderFile(resolve(root, 'foo.html'), {})
return expect(html).to.equal('foo')
return expect(html).toBe('foo')
})
it('should find files without extname', async function () {
var engine = new Liquid({ root })
const html = await engine.renderFile(resolve(root, 'bar'), {})
return expect(html).to.equal('bar')
return expect(html).toBe('bar')
})
it('should accept relative path', async function () {
const html = await engine.renderFile('foo.html')
return expect(html).to.equal('foo')
return expect(html).toBe('foo')
})
it('should traverse root array', async function () {
engine = new Liquid({
@@ -34,27 +30,26 @@ describe('#renderFile()', function () {
extname: '.html'
})
const html = await engine.renderFile('foo.html')
return expect(html).to.equal('foo')
return expect(html).toBe('foo')
})
it('should default root to cwd', async function () {
engine = new Liquid()
const html = await engine.renderFile('package.json')
return expect(html).to.contain('"name": "liquidjs"')
return expect(html).toContain('"name": "liquidjs"')
})
it('should render file with context', async function () {
const html = await engine.renderFile(resolve(views, 'name.html'), { name: 'harttle' })
return expect(html).to.equal('My name is harttle.')
return expect(html).toBe('My name is harttle.')
})
it('should use default extname', async function () {
const html = await engine.renderFile(resolve(root, 'foo'))
return expect(html).to.equal('foo')
return expect(html).toBe('foo')
})
it('should throw with lookup list when file not exist', function () {
engine = new Liquid({
root: ['/boo', '/root/'],
extname: '.html'
})
return expect(engine.renderFile('/not/exist.html')).to
.be.rejectedWith(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
return expect(engine.renderFile('/not/exist.html')).rejects.toThrow(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
})
})
@@ -1,10 +1,6 @@
import { expect, use } from 'chai'
import { resolve } from 'path'
import * as chaiAsPromised from 'chai-as-promised'
import { drainStream } from '../stub/stream'
use(chaiAsPromised)
describe('.renderToNodeStream()', function () {
it('should render to stream in Node.js', done => {
const cjs = require('../../dist/liquid.node.cjs')
@@ -15,7 +11,7 @@ describe('.renderToNodeStream()', function () {
stream.on('data', (data: string) => { html += data })
stream.on('end', () => {
try {
expect(html).to.equal('foo')
expect(html).toBe('foo')
done()
} catch (err) {
done(err)
@@ -26,7 +22,7 @@ describe('.renderToNodeStream()', function () {
const cjs = require('../../dist/liquid.browser.umd')
const engine = new cjs.Liquid()
const render = () => engine.renderToNodeStream('foo')
return expect(render).to.throw('streaming not supported in browser')
return expect(render).toThrow('streaming not supported in browser')
})
})
@@ -37,6 +33,6 @@ describe('.renderFileToNodeStream()', function () {
root: resolve(__dirname, '../stub/root/')
})
const stream = await engine.renderFileToNodeStream('foo.html')
expect(drainStream(stream)).to.eventually.equal('foo')
expect(drainStream(stream)).resolves.toBe('foo')
})
})
+25 -28
View File
@@ -1,10 +1,7 @@
import { Liquid } from '../../dist/liquid.browser.umd.js'
import * as sinon from 'sinon'
import { expect, use } from 'chai'
import { JSDOM } from 'jsdom'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
import type { Liquid } from '../..'
const LiquidUMD = require('../../dist/liquid.browser.umd.js').Liquid
describe('xhr', () => {
if (+(process.version.match(/^v(\d+)/) as RegExpMatchArray)[1] < 8) {
@@ -24,7 +21,7 @@ describe('xhr', () => {
});
(global as any).XMLHttpRequest = sinon.FakeXMLHttpRequest;
(global as any).document = dom.window.document
engine = new Liquid({
engine = new LiquidUMD({
root: 'https://example.com/views/',
extname: '.html'
})
@@ -37,21 +34,21 @@ describe('xhr', () => {
describe('#renderFile()', () => {
it('should support without extname', async () => {
const html = await engine.renderFile('hello', { name: 'alice1' })
return expect(html).to.equal('hello alice1')
return expect(html).toBe('hello alice1')
})
it('should support with extname', async () => {
const html = await engine.renderFile('hello.html', { name: 'alice2' })
return expect(html).to.equal('hello alice2')
return expect(html).toBe('hello alice2')
})
it('should support with absolute path', async () => {
server.respondWith('GET', 'https://example.com/foo.html',
[200, { 'Content-Type': 'text/plain' }, 'foo'])
const html = await engine.renderFile('/foo.html')
return expect(html).to.equal('foo')
return expect(html).toBe('foo')
})
it('should support with url', async () => {
const html = await engine.renderFile('https://example.com/views/hello.html', { name: 'alice4' })
return expect(html).to.equal('hello alice4')
return expect(html).toBe('hello alice4')
})
it('should support include', async () => {
server.respondWith('GET', 'https://example.com/views/hello.html',
@@ -59,15 +56,15 @@ describe('xhr', () => {
server.respondWith('GET', 'https://example.com/views/name.html',
[200, { 'Content-Type': 'text/plain' }, '{{name}}'])
const html = await engine.renderFile('hello.html', { name: 'alice5' })
return expect(html).to.equal('hello alice5')
return expect(html).toBe('hello alice5')
})
it('should throw 404', () => {
return expect(engine.renderFile('/not/exist.html'))
.to.be.rejectedWith('Not Found')
.rejects.toThrow('Not Found')
})
it('should throw error', function () {
const result = expect(engine.renderFile('hello.html'))
.to.be.rejectedWith('An error occurred whilst receiving the response.');
.rejects.toThrow('An error occurred whilst receiving the response.');
(global as any).XMLHttpRequest.onCreate = function (request: sinon.SinonFakeXMLHttpRequest) {
setTimeout(() => request.error())
}
@@ -76,53 +73,53 @@ describe('xhr', () => {
})
describe('#renderFile() with root specified', () => {
it('should support undefined root', async () => {
engine = new Liquid({
engine = new LiquidUMD({
extname: '.html'
})
server.respondWith('GET', 'https://example.com/foo/hello.html',
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}'])
const html = await engine.renderFile('hello.html', { name: 'alice5' })
return expect(html).to.equal('hello alice5')
return expect(html).toBe('hello alice5')
})
it('should support empty root', async () => {
engine = new Liquid({
engine = new LiquidUMD({
root: '',
extname: '.html'
})
server.respondWith('https://example.com/foo/hello.html',
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}'])
const html = await engine.renderFile('hello.html', { name: 'alice5' })
return expect(html).to.equal('hello alice5')
return expect(html).toBe('hello alice5')
})
it('should support with relative path', async () => {
engine = new Liquid({
engine = new LiquidUMD({
root: './views/',
extname: '.html'
})
server.respondWith('GET', 'https://example.com/foo/views/hello.html',
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}'])
const html = await engine.renderFile('hello.html', { name: 'alice5' })
return expect(html).to.equal('hello alice5')
return expect(html).toBe('hello alice5')
})
it('should support with absolute path', async () => {
engine = new Liquid({
engine = new LiquidUMD({
root: '/views/',
extname: '.html'
})
server.respondWith('GET', 'https://example.com/views/hello.html',
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}'])
const html = await engine.renderFile('hello.html', { name: 'alice5' })
return expect(html).to.equal('hello alice5')
return expect(html).toBe('hello alice5')
})
it('should support with url', async () => {
engine = new Liquid({
engine = new LiquidUMD({
root: 'https://foo.com/bar/',
extname: '.html'
})
server.respondWith('GET', 'https://foo.com/bar/hello.html',
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}'])
const html = await engine.renderFile('hello.html', { name: 'alice5' })
return expect(html).to.equal('hello alice5')
return expect(html).toBe('hello alice5')
})
})
describe('cache options', () => {
@@ -131,15 +128,15 @@ describe('xhr', () => {
[200, { 'Content-Type': 'text/plain' }, 'foo1'])
return engine.renderFile('foo.html')
.then((html: string) => {
expect(html).to.equal('foo1')
expect(html).toBe('foo1')
server.respondWith('GET', 'https://example.com/views/foo.html',
[200, { 'Content-Type': 'text/plain' }, 'foo2'])
return engine.renderFile('foo.html')
})
.then((html: string) => expect(html).to.equal('foo2'))
.then((html: string) => expect(html).toBe('foo2'))
})
it('should respect cache=true option', () => {
engine = new Liquid({
engine = new LiquidUMD({
root: '/views/',
extname: '.html',
cache: true
@@ -148,12 +145,12 @@ describe('xhr', () => {
[200, { 'Content-Type': 'text/plain' }, 'foo1'])
return engine.renderFile('foo.html')
.then((html: string) => {
expect(html).to.equal('foo1')
expect(html).toBe('foo1')
server.respondWith('GET', 'https://example.com/views/foo.html',
[200, { 'Content-Type': 'text/plain' }, 'foo2'])
return engine.renderFile('foo.html')
})
.then((html: string) => expect(html).to.equal('foo1'))
.then((html: string) => expect(html).toBe('foo1'))
})
})
})
@@ -1,52 +1,51 @@
import { expect } from 'chai'
import { Liquid } from '../../../src/liquid'
describe('drop/blank-drop', function () {
let liquid: Liquid
before(() => (liquid = new Liquid()))
beforeEach(() => (liquid = new Liquid()))
it('render blank drop as blank string', async function () {
const html = await liquid.parseAndRender('{{blank}}')
expect(html).to.equal('')
expect(html).toBe('')
})
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')
expect(html).toBe('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')
expect(html).toBe('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')
expect(html).toBe('"" == 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')
expect(html).toBe('" " == 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')
expect(html).toBe('{} == 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')
expect(html).toBe('{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')
expect(html).toBe('[] == 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')
expect(html).toBe('[1] != blank')
})
})
@@ -1,9 +1,8 @@
import { expect } from 'chai'
import { Liquid, Drop } from '../../../src'
describe('drop/drop', function () {
let liquid: Liquid
before(() => (liquid = new Liquid()))
beforeEach(() => (liquid = new Liquid()))
class CustomDrop extends Drop {
private name = 'NAME'
@@ -27,39 +26,39 @@ describe('drop/drop', function () {
}
it('should call corresponding method when output', async function () {
const html = await liquid.parseAndRender(`{{obj.getName}}`, { obj: new CustomDrop() })
expect(html).to.equal('GET NAME')
expect(html).toBe('GET NAME')
})
it('should call corresponding method when expression evaluates', async function () {
const html = await liquid.parseAndRender(`{% if obj.getName == "GET NAME" %}true{% endif %}`, { obj: new CustomDrop() })
expect(html).to.equal('true')
expect(html).toBe('true')
})
it('should read corresponding property', async function () {
const html = await liquid.parseAndRender(`{{obj.name}}`, { obj: new CustomDrop() })
expect(html).to.equal('NAME')
expect(html).toBe('NAME')
})
it('should output empty string if not exist', async function () {
const html = await liquid.parseAndRender(`{{obj.foo}}`, { obj: new CustomDrop() })
expect(html).to.equal('')
expect(html).toBe('')
})
it('should respect liquidMethodMissing', async function () {
const html = await liquid.parseAndRender(`{{obj.foo}}`, { obj: new CustomDropWithMethodMissing() })
expect(html).to.equal('FOO')
expect(html).toBe('FOO')
})
it('should call corresponding promise method', async function () {
const html = await liquid.parseAndRender(`{{obj.getName}}`, { obj: new PromiseDrop() })
expect(html).to.equal('GET NAME')
expect(html).toBe('GET NAME')
})
it('should read corresponding promise property', async function () {
const html = await liquid.parseAndRender(`{{obj.name}}`, { obj: new PromiseDrop() })
expect(html).to.equal('NAME')
expect(html).toBe('NAME')
})
it('should resolve before calling filters', async function () {
const html = await liquid.parseAndRender(`{{obj.name | downcase}}`, { obj: new PromiseDrop() })
expect(html).to.equal('name')
expect(html).toBe('name')
})
it('should support promise returned by liquidMethodMissing', async function () {
const html = await liquid.parseAndRender(`{{obj.foo}}`, { obj: new PromiseDrop() })
expect(html).to.equal('FOO')
expect(html).toBe('FOO')
})
it('should respect valueOf', async () => {
class CustomDrop extends Drop {
@@ -70,7 +69,7 @@ describe('drop/drop', function () {
}
const tpl = '{{drop}}: {% for field in drop %}{{ field }};{% endfor %}'
const html = await liquid.parseAndRender(tpl, { drop: new CustomDrop() })
expect(html).to.equal('foobar: foo;bar;')
expect(html).toBe('foobar: foo;bar;')
})
it('should support valueOf in == expression', async () => {
class AddressDrop extends Drop {
@@ -82,6 +81,6 @@ describe('drop/drop', function () {
const customer = { default_address: new AddressDrop() }
const tpl = `{% if address == customer.default_address %}{{address}}{% endif %}`
const html = await liquid.parseAndRender(tpl, { address, customer })
expect(html).to.equal('test')
expect(html).toBe('test')
})
})
@@ -1,92 +1,91 @@
import { expect } from 'chai'
import { Liquid } from '../../../src/liquid'
describe('drop/empty-drop', function () {
let liquid: Liquid
before(() => (liquid = new Liquid()))
beforeEach(() => (liquid = new Liquid()))
it('render empty drop as empty string', async function () {
const html = await liquid.parseAndRender('{{empty}}')
expect(html).to.equal('')
expect(html).toBe('')
})
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')
expect(html).toBe('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')
expect(html).toBe('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')
expect(html).toBe('"" == 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')
expect(html).toBe('" " != 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')
expect(html).toBe('{} == 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')
expect(html).toBe('{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')
expect(html).toBe('[] == 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')
expect(html).toBe('[1] != empty')
})
it('1 < empty should be false', async function () {
const src = '{%if 1 < empty %}true{%else%}false{% endif %}'
const html = await liquid.parseAndRender(src)
expect(html).to.equal('false')
expect(html).toBe('false')
})
it('1 <= empty should be false', async function () {
const src = '{%if 1 <= empty %}true{%else%}false{% endif %}'
const html = await liquid.parseAndRender(src)
expect(html).to.equal('false')
expect(html).toBe('false')
})
it('1 > empty should be false', async function () {
const src = '{%if 1 > empty %}true{%else%}false{% endif %}'
const html = await liquid.parseAndRender(src)
expect(html).to.equal('false')
expect(html).toBe('false')
})
it('1 >= empty should be false', async function () {
const src = '{%if 1 >= empty %}true{%else%}false{% endif %}'
const html = await liquid.parseAndRender(src)
expect(html).to.equal('false')
expect(html).toBe('false')
})
it('1 == empty should be false', async function () {
const src = '{%if 1 == empty %}true{%else%}false{% endif %}'
const html = await liquid.parseAndRender(src)
expect(html).to.equal('false')
expect(html).toBe('false')
})
it('1 != empty should be true', async function () {
const src = '{%if 1 != empty %}true{%else%}false{% endif %}'
const html = await liquid.parseAndRender(src)
expect(html).to.equal('true')
expect(html).toBe('true')
})
it('empty != empty', async function () {
const src = '{%if empty == empty %}true{%else%}false{% endif %}'
const html = await liquid.parseAndRender(src)
expect(html).to.equal('false')
expect(html).toBe('false')
})
it('empty != nil', async function () {
const src = '{%if empty == nil %}true{%else%}false{% endif %}'
const html = await liquid.parseAndRender(src)
expect(html).to.equal('false')
expect(html).toBe('false')
})
})
@@ -1,41 +1,40 @@
import { expect } from 'chai'
import { Liquid } from '../../../src/liquid'
describe('drop/null-drop', function () {
let liquid: Liquid
before(() => (liquid = new Liquid()))
beforeEach(() => (liquid = new Liquid()))
it('render nil as empty string', async function () {
const html = await liquid.parseAndRender('{{nil}}')
expect(html).to.equal('')
expect(html).toBe('')
})
it('render null as empty string', async function () {
const html = await liquid.parseAndRender('{{null}}')
expect(html).to.equal('')
expect(html).toBe('')
})
it('undefined == 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')
expect(html).toBe('foo == nil')
})
it('nil != blank', async function () {
const src = '{%if nil == blank %}eq{%else%}neq{% endif %}'
const html = await liquid.parseAndRender(src)
expect(html).to.equal('neq')
expect(html).toBe('neq')
})
it('nil != empty', async function () {
const src = '{%if nil == empty %}eq{%else%}neq{% endif %}'
const html = await liquid.parseAndRender(src)
expect(html).to.equal('neq')
expect(html).toBe('neq')
})
it('0 != 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')
expect(html).toBe('0 != null')
})
it('nil == 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')
expect(html).toBe('nil == null')
})
})
@@ -1,7 +1,4 @@
import { test, render } from '../../stub/render'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('filters/array', function () {
describe('index', function () {
@@ -25,7 +22,7 @@ describe('filters/array', function () {
it('should throw when comma missing', async () => {
const src = '{% assign beatles = "John, Paul, George, Ringo" | split: ", " %}' +
'{{ beatles | join " and " }}'
return expect(render(src)).to.be.rejectedWith('unexpected token at "\\" and \\"", line:1, col:65')
return expect(render(src)).rejects.toThrow('unexpected token at "\\" and \\"", line:1, col:65')
})
})
describe('last', () => {
@@ -104,7 +101,7 @@ describe('filters/array', function () {
const scope = { arr: ['a', 'b', 'c'] }
await render('{{ arr | reverse | join: "" }}', scope)
const html = await render('{{ arr | join: "" }}', scope)
expect(html).to.equal('abc')
expect(html).toBe('abc')
})
})
describe('size', function () {
@@ -1,5 +1,4 @@
import { test } from '../../stub/render'
import { expect } from 'chai'
import { Liquid } from '../../../src/liquid'
describe('filters/html', function () {
@@ -50,24 +49,24 @@ describe('filters/html', function () {
'Ulysses?')
})
it('should strip multiline comments', function () {
expect(liquid.parseAndRenderSync('{{"<!--foo\r\nbar \ncoo\t \r\n -->"|strip_html}}')).to.equal('')
expect(liquid.parseAndRenderSync('{{"<!--foo\r\nbar \ncoo\t \r\n -->"|strip_html}}')).toBe('')
})
it('should strip all style tags and their contents', function () {
return test('{{ "<style>cite { font-style: italic; }</style><cite>Ulysses<cite>?" | strip_html }}',
'Ulysses?')
})
it('should strip multiline styles', function () {
expect(liquid.parseAndRenderSync('{{"<style> \n.header {\r\n color: black;\r\n}\n</style>" | strip_html}}')).to.equal('')
expect(liquid.parseAndRenderSync('{{"<style> \n.header {\r\n color: black;\r\n}\n</style>" | strip_html}}')).toBe('')
})
it('should strip all scripts tags and their contents', function () {
return test('{{ "<script async>console.log(\'hello world\')</script><cite>Ulysses<cite>?" | strip_html }}',
'Ulysses?')
})
it('should strip multiline scripts', function () {
expect(liquid.parseAndRenderSync('{{ "<script> \nfoo\r\nbar\n</script>" | strip_html }}')).to.equal('')
expect(liquid.parseAndRenderSync('{{ "<script> \nfoo\r\nbar\n</script>" | strip_html }}')).toBe('')
})
it('should not strip non-matched <script>', function () {
expect(liquid.parseAndRenderSync('{{ "<script></script>text<script></script>" | strip_html }}')).to.equal('text')
expect(liquid.parseAndRenderSync('{{ "<script></script>text<script></script>" | strip_html }}')).toBe('text')
})
it('should strip until empty', function () {
return test('{{"<br/><br />< p ></p></ p >" | strip_html }}', '')
@@ -1,4 +1,3 @@
import { expect } from 'chai'
import { test, liquid } from '../../stub/render'
describe('filters/math', function () {
@@ -48,7 +47,7 @@ describe('filters/math', function () {
it('should return "3" for 24,7', () => test('{{ 24 | modulo: 7 }}', '3'))
it('should return "3.357" for 183.357,12', async () => {
const html = await liquid.parseAndRender('{{ 183.357 | modulo: 12 }}')
expect(Number(html)).to.be.closeTo(3.357, 0.001)
expect(Number(html)).toBeCloseTo(3.357, 3)
})
it('should convert string', () => test('{{ "24" | modulo: "7" }}', '3'))
})
@@ -1,32 +1,28 @@
import { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('filters/object', function () {
const liquid = new Liquid()
describe('default', function () {
it('false should use default', async () => expect(await liquid.parseAndRender('{{false | default: "a"}}')).to.equal('a'))
it('empty string should use default', async () => expect(await liquid.parseAndRender('{{"" | default: "a"}}')).to.equal('a'))
it('empty array should use default', async () => expect(await liquid.parseAndRender('{{arr | default: "a"}}', { arr: [] })).to.equal('a'))
it('non-empty string should not use default', async () => expect(await liquid.parseAndRender('{{" " | default: "a"}}')).to.equal(' '))
it('nil should use default', async () => expect(await liquid.parseAndRender('{{nil | default: "a"}}')).to.equal('a'))
it('undefined should use default', async () => expect(await liquid.parseAndRender('{{not_defined | default: "a"}}')).to.equal('a'))
it('true should not use default', async () => expect(await liquid.parseAndRender('{{true | default: "a"}}')).to.equal('true'))
it('0 should not use default', async () => expect(await liquid.parseAndRender('{{0 | default: "a"}}')).to.equal('0'))
it('should output false when allow_false=true', async () => expect(await liquid.parseAndRender('{{false | default: true, allow_false: true}}')).to.equal('false'))
it('should output default without allow_false', async () => expect(await liquid.parseAndRender('{{false | default: true}}')).to.equal('true'))
it('should output default when allow_false=false', async () => expect(await liquid.parseAndRender('{{false | default: true, allow_false: false}}')).to.equal('true'))
it('false should use default', async () => expect(await liquid.parseAndRender('{{false | default: "a"}}')).toBe('a'))
it('empty string should use default', async () => expect(await liquid.parseAndRender('{{"" | default: "a"}}')).toBe('a'))
it('empty array should use default', async () => expect(await liquid.parseAndRender('{{arr | default: "a"}}', { arr: [] })).toBe('a'))
it('non-empty string should not use default', async () => expect(await liquid.parseAndRender('{{" " | default: "a"}}')).toBe(' '))
it('nil should use default', async () => expect(await liquid.parseAndRender('{{nil | default: "a"}}')).toBe('a'))
it('undefined should use default', async () => expect(await liquid.parseAndRender('{{not_defined | default: "a"}}')).toBe('a'))
it('true should not use default', async () => expect(await liquid.parseAndRender('{{true | default: "a"}}')).toBe('true'))
it('0 should not use default', async () => expect(await liquid.parseAndRender('{{0 | default: "a"}}')).toBe('0'))
it('should output false when allow_false=true', async () => expect(await liquid.parseAndRender('{{false | default: true, allow_false: true}}')).toBe('false'))
it('should output default without allow_false', async () => expect(await liquid.parseAndRender('{{false | default: true}}')).toBe('true'))
it('should output default when allow_false=false', async () => expect(await liquid.parseAndRender('{{false | default: true, allow_false: false}}')).toBe('true'))
it('should throw for additional args', () => {
const src = `{{ age | default: 'now' date: '%d'}}` // missing `|` before `date`
return expect(liquid.parseAndRender(src)).to.be.rejectedWith(/unexpected character "date: '%d'"/)
return expect(liquid.parseAndRender(src)).rejects.toThrow(/unexpected character "date: '%d'"/)
})
})
describe('json', function () {
it('should stringify string', async () => expect(await liquid.parseAndRender('{{"foo" | json}}')).to.equal('"foo"'))
it('should stringify number', async () => expect(await liquid.parseAndRender('{{2 | json}}')).to.equal('2'))
it('should stringify object', async () => expect(await liquid.parseAndRender('{{obj | json}}', { obj: { foo: 'bar' } })).to.equal('{"foo":"bar"}'))
it('should stringify array', async () => expect(await liquid.parseAndRender('{{arr | json}}', { arr: [-2, 'a'] })).to.equal('[-2,"a"]'))
it('should stringify string', async () => expect(await liquid.parseAndRender('{{"foo" | json}}')).toBe('"foo"'))
it('should stringify number', async () => expect(await liquid.parseAndRender('{{2 | json}}')).toBe('2'))
it('should stringify object', async () => expect(await liquid.parseAndRender('{{obj | json}}', { obj: { foo: 'bar' } })).toBe('{"foo":"bar"}'))
it('should stringify array', async () => expect(await liquid.parseAndRender('{{arr | json}}', { arr: [-2, 'a'] })).toBe('[-2,"a"]'))
})
})
@@ -1,8 +1,4 @@
import { test } from '../../stub/render'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('filters/string', function () {
describe('append', function () {
@@ -10,7 +6,7 @@ describe('filters/string', function () {
() => test('{{ -3 | append: "abc" }}', '-3abc'))
it('should return "abar" for "a", foo', () => test('{{ "a" | append: foo }}', { foo: 'bar' }, 'abar'))
it('should throw if second argument not set', () => {
return expect(test('{{ "abc" | append }}', 'abc')).to.be.rejectedWith(/2 arguments/)
return expect(test('{{ "abc" | append }}', 'abc')).rejects.toThrow(/2 arguments/)
})
it('should return "abcfalse" for "abc", false', () => test('{{ "abc" | append: false }}', 'abcfalse'))
})
@@ -19,7 +15,7 @@ describe('filters/string', function () {
() => test('{{ -3 | prepend: "abc" }}', 'abc-3'))
it('should return "abar" for "a", foo', () => test('{{ "a" | prepend: foo }}', { foo: 'bar' }, 'bara'))
it('should throw if second argument not set', () => {
return expect(test('{{ "abc" | prepend }}', 'abc')).to.be.rejectedWith(/2 arguments/)
return expect(test('{{ "abc" | prepend }}', 'abc')).rejects.toThrow(/2 arguments/)
})
it('should return "falseabc" for "abc", false', () => test('{{ "abc" | prepend: false }}', 'falseabc'))
})
@@ -1,6 +1,6 @@
import { expect } from 'chai'
import { Liquid, Template } from '../../../src/liquid'
import { Liquid } from '../../../src/liquid'
import { mock, restore } from '../../stub/mockfs'
import { Template } from '../../../src/template'
describe('LiquidOptions#cache', function () {
afterEach(restore)
@@ -13,10 +13,10 @@ describe('LiquidOptions#cache', function () {
})
mock({ '/root/files/foo.html': 'foo' })
const x = await engine.renderFile('files/foo')
expect(x).to.equal('foo')
expect(x).toBe('foo')
mock({ '/root/files/foo.html': 'bar' })
const y = await engine.renderFile('files/foo')
expect(y).to.equal('bar')
expect(y).toBe('bar')
})
it('should be disabled when cache <= 0', async function () {
const engine = new Liquid({
@@ -26,10 +26,10 @@ describe('LiquidOptions#cache', function () {
})
mock({ '/root/files/foo.html': 'foo' })
const x = await engine.renderFile('files/foo')
expect(x).to.equal('foo')
expect(x).toBe('foo')
mock({ '/root/files/foo.html': 'bar' })
const y = await engine.renderFile('files/foo')
expect(y).to.equal('bar')
expect(y).toBe('bar')
})
it('should respect cache=true option', async function () {
const engine = new Liquid({
@@ -39,10 +39,10 @@ describe('LiquidOptions#cache', function () {
})
mock({ '/root/files/foo.html': 'foo' })
const x = await engine.renderFile('files/foo')
expect(x).to.equal('foo')
expect(x).toBe('foo')
mock({ '/root/files/foo.html': 'bar' })
const y = await engine.renderFile('files/foo')
expect(y).to.equal('foo')
expect(y).toBe('foo')
})
it('should respect cache=2 option', async function () {
const engine = new Liquid({
@@ -57,12 +57,12 @@ describe('LiquidOptions#cache', function () {
mock({ '/root/files/foo.html': 'FOO' })
await engine.renderFile('files/bar')
const x = await engine.renderFile('files/foo')
expect(x).to.equal('foo')
expect(x).toBe('foo')
await engine.renderFile('files/bar')
await engine.renderFile('files/coo')
const y = await engine.renderFile('files/foo')
expect(y).to.equal('FOO')
expect(y).toBe('FOO')
})
it('should respect cache={read, write} option', async function () {
let last: Template[] | undefined
@@ -70,6 +70,7 @@ describe('LiquidOptions#cache', function () {
root: '/root/',
extname: '.html',
cache: {
remove: () => void (0),
read: (): Template[] | undefined => last,
write: (key: string, value: Template[]) => { last = value }
}
@@ -77,9 +78,9 @@ describe('LiquidOptions#cache', function () {
mock({ '/root/files/foo.html': 'foo' })
mock({ '/root/files/bar.html': 'bar' })
mock({ '/root/files/coo.html': 'coo' })
expect(await engine.renderFile('files/foo')).to.equal('foo')
expect(await engine.renderFile('files/bar')).to.equal('foo')
expect(await engine.renderFile('files/coo')).to.equal('foo')
expect(await engine.renderFile('files/foo')).toBe('foo')
expect(await engine.renderFile('files/bar')).toBe('foo')
expect(await engine.renderFile('files/coo')).toBe('foo')
})
it('should respect cache={ async read, async write } option', async function () {
const cached: { [key: string]: Template[] | undefined } = {}
@@ -87,6 +88,7 @@ describe('LiquidOptions#cache', function () {
root: '/root/',
extname: '.html',
cache: {
remove: (key: string) => { delete cached[key] },
read: (key: string) => Promise.resolve(cached[key]),
write: (key: string, value: Template[]) => { cached[key] = value; Promise.resolve() }
}
@@ -94,11 +96,11 @@ describe('LiquidOptions#cache', function () {
mock({ '/root/files/foo.html': 'foo' })
mock({ '/root/files/bar.html': 'bar' })
mock({ '/root/files/coo.html': 'coo' })
expect(await engine.renderFile('files/foo')).to.equal('foo')
expect(await engine.renderFile('files/bar')).to.equal('bar')
expect(await engine.renderFile('files/coo')).to.equal('coo')
expect(await engine.renderFile('files/foo')).toBe('foo')
expect(await engine.renderFile('files/bar')).toBe('bar')
expect(await engine.renderFile('files/coo')).toBe('coo')
mock({ '/root/files/coo.html': 'COO' })
expect(await engine.renderFile('files/coo')).to.equal('coo')
expect(await engine.renderFile('files/coo')).toBe('coo')
})
it('should handle concurrent cache read/write', async function () {
const engine = new Liquid({
@@ -115,10 +117,10 @@ describe('LiquidOptions#cache', function () {
engine.renderFile('files/bar'),
engine.renderFile('files/coo')
])
expect(foo1).to.equal('foo')
expect(foo2).to.equal('foo')
expect(bar).to.equal('bar')
expect(coo).to.equal('coo')
expect(foo1).toBe('foo')
expect(foo2).toBe('foo')
expect(bar).toBe('bar')
expect(coo).toBe('coo')
})
it('should not cache not exist file', async function () {
const engine = new Liquid({
@@ -132,7 +134,7 @@ describe('LiquidOptions#cache', function () {
mock({ '/root/foo.html': 'foo' })
const html = await engine.renderFile('foo')
expect(html).to.equal('foo')
expect(html).toBe('foo')
})
})
@@ -144,11 +146,11 @@ describe('LiquidOptions#cache', function () {
})
mock({ '/root/foo.html': 'foo' })
const x = engine.renderFileSync('foo')
expect(x).to.equal('foo')
expect(x).toBe('foo')
mock({ '/root/foo.html': 'bar' })
const y = engine.renderFileSync('foo')
expect(y).to.equal('bar')
expect(y).toBe('bar')
})
it('should respect cache=true option', function () {
const engine = new Liquid({
@@ -157,9 +159,9 @@ describe('LiquidOptions#cache', function () {
cache: true
})
mock({ '/root/foo.html': 'foo' })
expect(engine.renderFileSync('foo')).to.equal('foo')
expect(engine.renderFileSync('foo')).toBe('foo')
mock({ '/root/foo.html': 'bar' })
expect(engine.renderFileSync('foo')).to.equal('foo')
expect(engine.renderFileSync('foo')).toBe('foo')
})
it('should not cache not exist file', async function () {
const engine = new Liquid({
@@ -171,7 +173,7 @@ describe('LiquidOptions#cache', function () {
mock({ '/root/foo.html': 'foo' })
const y = await engine.renderFile('foo')
expect(y).to.equal('foo')
expect(y).toBe('foo')
})
it('should cache relative referenced files properly', async function () {
const engine = new Liquid({
@@ -186,10 +188,10 @@ describe('LiquidOptions#cache', function () {
'/root/another/bar.html': 'bar2'
})
const foo1 = await engine.renderFile('foo')
expect(foo1).to.equal('bar1')
expect(foo1).toBe('bar1')
const foo2 = await engine.renderFile('another/foo')
expect(foo2).to.equal('bar2')
expect(foo2).toBe('bar2')
})
})
})
@@ -1,4 +1,3 @@
import { expect } from 'chai'
import { Liquid } from '../../../src/liquid'
describe('LiquidOptions#*_delimiter_*', function () {
@@ -8,7 +7,7 @@ describe('LiquidOptions#*_delimiter_*', function () {
tagDelimiterRight: '%>'
})
const html = await engine.parseAndRender('<%=if true%>foo<%=endif%> ')
return expect(html).to.equal('foo ')
return expect(html).toBe('foo ')
})
it('should respect output_delimiter_*', async function () {
const engine = new Liquid({
@@ -16,7 +15,7 @@ describe('LiquidOptions#*_delimiter_*', function () {
outputDelimiterRight: '>>'
})
const html = await engine.parseAndRender('<< "liquid" | capitalize >>')
return expect(html).to.equal('Liquid')
return expect(html).toBe('Liquid')
})
it('should support trimming with tag_delimiter_* set', async function () {
const engine = new Liquid({
@@ -26,6 +25,6 @@ describe('LiquidOptions#*_delimiter_*', function () {
trimTagRight: true
})
const html = await engine.parseAndRender(' <%=if true%> \tfoo\t <%=endif%> ')
return expect(html).to.equal('foo')
return expect(html).toBe('foo')
})
})
@@ -1,11 +1,10 @@
import { expect, use } from 'chai'
import { Liquid } from '../../../src/liquid'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('LiquidOptions#fs', function () {
let engine: Liquid
const fs = {
sep: '/',
dirname: (x: string) => x.split('/').slice(0, -1).join('/'),
exists: (x: string) => Promise.resolve(!x.match(/not-exist/)),
existsSync: (x: string) => !x.match(/not-exist/),
readFile: (x: string) => Promise.resolve(`content for ${x}`),
@@ -21,17 +20,17 @@ describe('LiquidOptions#fs', function () {
})
it('should be used to read templates', async function () {
const html = await engine.renderFile('files/foo')
expect(html).to.equal('content for /root/files/foo')
expect(html).toBe('content for /root/files/foo')
})
it('should support fallback', async function () {
const html = await engine.renderFile('not-exist/foo')
expect(html).to.equal('content for /root/files/fallback')
expect(html).toBe('content for /root/files/fallback')
})
it('should support renderSync', function () {
const html = engine.renderFileSync('not-exist/foo')
expect(html).to.equal('content for /root/files/fallback')
expect(html).toBe('content for /root/files/fallback')
})
it('should throw lookup failure if fallback not specified', function () {
@@ -40,6 +39,14 @@ describe('LiquidOptions#fs', function () {
fs: { ...fs, fallback: undefined }
} as any)
return expect(engine.renderFile('not-exist/foo'))
.to.be.rejectedWith('Failed to lookup')
.rejects.toThrow('Failed to lookup')
})
it('should disable relativeReference if `sep` and `dirname` not specified', function () {
const engine = new Liquid({
root: '/root/',
fs: { ...fs, sep: undefined, dirname: undefined }
} as any)
expect(engine.options.relativeReference).toBe(false)
})
})
@@ -1,4 +1,3 @@
import { expect } from 'chai'
import { Liquid } from '../../../src/liquid'
describe('LiquidOptions#*keepOutputType*', function () {
@@ -12,13 +11,13 @@ describe('LiquidOptions#*keepOutputType*', function () {
'my-string': 'test'
}
const booleanHtml = await engine.parseAndRender('{{my-boolean}}', context)
expect(booleanHtml).to.equal(true)
expect(booleanHtml).toBe(true)
const numberHtml = await engine.parseAndRender('{{my-number}}', context)
expect(numberHtml).to.equal(42)
expect(numberHtml).toBe(42)
const html = await engine.parseAndRender('{{my-string}}', context)
expect(html).to.equal('test')
expect(html).toBe('test')
const composedHtml = await engine.parseAndRender('{{my-string}}:{{my-number}}', context)
expect(composedHtml).to.equal('test:42')
expect(composedHtml).toBe('test:42')
})
it('should respect keepOutputType = false as default', async function () {
@@ -29,12 +28,12 @@ describe('LiquidOptions#*keepOutputType*', function () {
'my-string': 'test'
}
const booleanHtml = await engine.parseAndRender('{{my-boolean}}', context)
expect(booleanHtml).to.equal('true')
expect(booleanHtml).toBe('true')
const numberHtml = await engine.parseAndRender('{{my-number}}', context)
expect(numberHtml).to.equal('42')
expect(numberHtml).toBe('42')
const html = await engine.parseAndRender('{{my-string}}', context)
expect(html).to.equal('test')
expect(html).toBe('test')
const composedHtml = await engine.parseAndRender('{{my-string}}:{{my-number}}', context)
expect(composedHtml).to.equal('test:42')
expect(composedHtml).toBe('test:42')
})
})
@@ -1,12 +1,7 @@
import * as chai from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import { Liquid, Context, isFalsy } from '../../../src'
import { mock, restore } from '../../stub/mockfs'
import { drainStream } from '../../stub/stream'
const expect = chai.expect
chai.use(chaiAsPromised)
describe('Liquid', function () {
describe('#plugin()', function () {
it('should call plugin on the instance', async function () {
@@ -15,7 +10,7 @@ describe('Liquid', function () {
this.registerFilter('foo', x => `foo${x}foo`)
})
const html = await engine.parseAndRender('{{"bar"|foo}}')
expect(html).to.equal('foobarfoo')
expect(html).toBe('foobarfoo')
})
it('should call plugin with Liquid', async function () {
const engine = new Liquid()
@@ -23,92 +18,92 @@ describe('Liquid', function () {
this.registerFilter('t', function (v) { return isFalsy(v, this.context) })
})
const html = await engine.parseAndRender('{{false|t}}')
expect(html).to.equal('true')
expect(html).toBe('true')
})
})
describe('#parseAndRender', function () {
const engine = new Liquid()
it('should parse and render variable output', async function () {
const html = await engine.parseAndRender('{{"foo"}}')
expect(html).to.equal('foo')
expect(html).toBe('foo')
})
it('should parse and render complex output', async function () {
const tpl = '{{ "Welcome|to]Liquid" | split: "|" | join: "("}}'
const html = await engine.parseAndRender(tpl)
expect(html).to.equal('Welcome(to]Liquid')
expect(html).toBe('Welcome(to]Liquid')
})
it('should support for-in with variable', async function () {
const src = '{% assign total = 3 | minus: 1 %}' +
'{% for i in (1..total) %}{{ i }}{% endfor %}'
const html = await engine.parseAndRender(src, {})
return expect(html).to.equal('12')
return expect(html).toBe('12')
})
it('should support `globals` render option', async function () {
const src = '{{ foo }}'
const html = await engine.parseAndRender(src, {}, { globals: { foo: 'FOO' } })
return expect(html).to.equal('FOO')
return expect(html).toBe('FOO')
})
it('should support `strictVariables` render option', function () {
const src = '{{ foo }}'
return expect(engine.parseAndRender(src, {}, { strictVariables: true })).rejectedWith(/undefined variable/)
return expect(engine.parseAndRender(src, {}, { strictVariables: true })).rejects.toThrow(/undefined variable/)
})
it('should support async variables in output', async () => {
const src = '{{ foo }}'
const html = await engine.parseAndRender(src, { foo: Promise.resolve('FOO') })
expect(html).to.equal('FOO')
expect(html).toBe('FOO')
})
it('should parse and render with Context', async function () {
const html = await engine.parseAndRender('{{foo}}', new Context({ foo: 'FOO' }))
expect(html).to.equal('FOO')
expect(html).toBe('FOO')
})
})
describe('#parseAndRenderSync', function () {
const engine = new Liquid()
it('should parse and render variable output', function () {
const html = engine.parseAndRenderSync('{{"foo"}}')
expect(html).to.equal('foo')
expect(html).toBe('foo')
})
it('should parse and render complex output', function () {
const tpl = '{{ "Welcome|to]Liquid" | split: "|" | join: "("}}'
const html = engine.parseAndRenderSync(tpl)
expect(html).to.equal('Welcome(to]Liquid')
expect(html).toBe('Welcome(to]Liquid')
})
it('should support for-in with variable', function () {
const src = '{% assign total = 3 | minus: 1 %}' +
'{% for i in (1..total) %}{{ i }}{% endfor %}'
const html = engine.parseAndRenderSync(src, {})
return expect(html).to.equal('12')
return expect(html).toBe('12')
})
it('should support `globals` render option', function () {
const src = '{{ foo }}'
const html = engine.parseAndRenderSync(src, {}, { globals: { foo: 'FOO' } })
return expect(html).to.equal('FOO')
return expect(html).toBe('FOO')
})
it('should support `strictVariables` render option', function () {
const src = '{{ foo }}'
return expect(() => engine.parseAndRenderSync(src, {}, { strictVariables: true })).throw(/undefined variable/)
return expect(() => engine.parseAndRenderSync(src, {}, { strictVariables: true })).toThrow(/undefined variable/)
})
})
describe('#express()', function () {
const liquid = new Liquid({ root: '/root' })
const render = liquid.express()
before(function () {
beforeEach(function () {
mock({
'/root/foo': 'foo'
})
})
after(restore)
afterEach(restore)
it('should render single template', function (done) {
render.call({ root: '/root' }, 'foo', null as any, (err: Error | null, result: string | undefined) => {
if (err) return done(err)
expect(result).to.equal('foo')
expect(result).toBe('foo')
done()
})
})
it('should render single template with Array-typed root', function (done) {
render.call({ root: ['/root'] }, 'foo', null as any, (err: Error | null, result: string | undefined) => {
if (err) return done(err)
expect(result).to.equal('foo')
expect(result).toBe('foo')
done()
})
})
@@ -119,8 +114,7 @@ describe('Liquid', function () {
root: ['/boo', '/root/'],
extname: '.html'
})
return expect(engine.renderFile('/not/exist.html')).to
.be.rejectedWith(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
return expect(engine.renderFile('/not/exist.html')).rejects.toThrow(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
})
})
describe('#parseFile', function () {
@@ -129,17 +123,16 @@ describe('Liquid', function () {
root: ['/boo', '/root/'],
extname: '.html'
})
return expect(engine.parseFile('/not/exist.html')).to
.be.rejectedWith(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
return expect(engine.parseFile('/not/exist.html')).rejects.toThrow(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
})
it('should fallback to require.resolve in Node.js', async function () {
const engine = new Liquid({
root: ['/root/'],
extname: '.html'
})
const tpls = await engine.parseFileSync('mocha')
expect(tpls.length).to.gte(1)
expect(tpls[0].token.getText()).to.contain('module.exports')
const tpls = await engine.parseFileSync('jest')
expect(tpls.length).toBeGreaterThanOrEqual(1)
expect(tpls[0].token.getText()).toContain('use strict')
})
})
describe('#evalValue', function () {
@@ -147,12 +140,12 @@ describe('Liquid', function () {
const engine = new Liquid()
const ctx = new Context()
const str = await engine.evalValue('"foo"', ctx)
expect(str).to.equal('foo')
expect(str).toBe('foo')
})
it('should support plain scope', async function () {
const engine = new Liquid()
const str = await engine.evalValue('foo', { foo: 'FOO' })
expect(str).to.equal('FOO')
expect(str).toBe('FOO')
})
})
describe('#evalValueSync', function () {
@@ -160,7 +153,7 @@ describe('Liquid', function () {
const engine = new Liquid()
const ctx = new Context()
const str = engine.evalValueSync('"foo"', ctx)
expect(str).to.equal('foo')
expect(str).toBe('foo')
})
})
describe('#parse', function () {
@@ -173,7 +166,7 @@ describe('Liquid', function () {
'/root/partial.html': 'foo'
})
const tpls = engine.parse('{% render "./partial.html" %}', '/root/index.html')
return expect(engine.renderSync(tpls)).to.equal('foo')
return expect(engine.renderSync(tpls)).toBe('foo')
})
it('should resolve against pwd for relative filepath', function () {
const engine = new Liquid({
@@ -184,7 +177,7 @@ describe('Liquid', function () {
[`${process.cwd()}/partial.html`]: 'foo'
})
const tpls = engine.parse('{% render "./partial.html" %}', './index.html')
return expect(engine.renderSync(tpls)).to.equal('foo')
return expect(engine.renderSync(tpls)).toBe('foo')
})
})
describe('#parseFileSync', function () {
@@ -194,7 +187,7 @@ describe('Liquid', function () {
extname: '.html'
})
return expect(() => engine.parseFileSync('/not/exist.html'))
.to.throw(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
.toThrow(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
})
it('should throw with lookup list when file not exist', function () {
const engine = new Liquid({
@@ -202,19 +195,19 @@ describe('Liquid', function () {
extname: '.html'
})
return expect(() => engine.parseFileSync('/not/exist.html'))
.to.throw(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
.toThrow(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
})
})
describe('#enderToNodeStream', function () {
const engine = new Liquid()
it('should render a simple value', async () => {
const stream = engine.renderToNodeStream(engine.parse('{{"foo"}}'))
expect(drainStream(stream)).to.eventually.equal('foo')
expect(drainStream(stream)).resolves.toBe('foo')
})
})
describe('#enderFileToNodeStream', function () {
let engine: Liquid
before(function () {
beforeEach(function () {
mock({
'/root/foo.html': 'foo',
'/root/error.html': 'A{%throwingTag%}B'
@@ -226,14 +219,14 @@ describe('Liquid', function () {
}
})
})
after(restore)
afterEach(restore)
it('should render a simple value', async () => {
const stream = await engine.renderFileToNodeStream('foo.html')
expect(drainStream(stream)).to.be.eventually.equal('foo')
expect(drainStream(stream)).resolves.toBe('foo')
})
it('should throw RenderError when tag throws', async () => {
const stream = await engine.renderFileToNodeStream('error.html')
expect(drainStream(stream)).to.be.rejectedWith(/intended render error/)
expect(drainStream(stream)).rejects.toThrow(/intended render error/)
})
})
})
@@ -1,4 +1,3 @@
import { expect } from 'chai'
import { Liquid, defaultOperators } from '../../../src'
describe('LiquidOptions#operators', function () {
@@ -8,27 +7,27 @@ describe('LiquidOptions#operators', function () {
engine = new Liquid({
operators: {
...defaultOperators,
isFooBar: (l, r) => l === 'foo' && r === 'bar'
isFooBar: (l: string, r: string) => l === 'foo' && r === 'bar'
}
})
})
it('should evaluate the default operators', async function () {
const result = await engine.parseAndRender('{% if "foo" == "foo" %}True{% endif %}')
expect(result).to.equal('True')
expect(result).toBe('True')
})
it('should evaluate a custom operator', async function () {
const first = await engine.parseAndRender('{% if "foo" isFooBar "bar" %}True{% else %}False{% endif %}')
expect(first).to.equal('True')
expect(first).toBe('True')
const second = await engine.parseAndRender('{% if "foo" isFooBar "foo" %}True{% else %}False{% endif %}')
expect(second).to.equal('False')
expect(second).toBe('False')
})
it('should evaluate a custom operator with the correct precedence', async function () {
const first = await engine.parseAndRender('{% if "foo" isFooBar "bar" or "foo" == "bar" %}True{% else %}False{% endif %}')
expect(first).to.equal('True')
expect(first).toBe('True')
const second = await engine.parseAndRender('{% if "foo" isFooBar "foo" or "foo" == "bar" %}True{% else %}False{% endif %}')
expect(second).to.equal('False')
expect(second).toBe('False')
})
})
@@ -1,11 +1,10 @@
import { expect } from 'chai'
import { Liquid } from '../../../src/liquid'
describe('LiquidOptions#*outputEscape*', function () {
it('when outputEscape is not set', async function () {
const engine = new Liquid()
const html = await engine.parseAndRender('{{"<"}}')
expect(html).to.equal('<')
expect(html).toBe('<')
})
it('should escape when outputEscape="escape"', async function () {
@@ -13,7 +12,7 @@ describe('LiquidOptions#*outputEscape*', function () {
outputEscape: 'escape'
})
const html = await engine.parseAndRender('{{"<"}}')
expect(html).to.equal('&lt;')
expect(html).toBe('&lt;')
})
it('should json stringify when outputEscape="json"', async function () {
@@ -21,7 +20,7 @@ describe('LiquidOptions#*outputEscape*', function () {
outputEscape: 'json'
})
const html = await engine.parseAndRender('{{"<"}}')
expect(html).to.equal('"<"')
expect(html).toBe('"<"')
})
it('should support outputEscape=Function', async function () {
@@ -29,7 +28,7 @@ describe('LiquidOptions#*outputEscape*', function () {
outputEscape: (v: any) => `{${v}}`
})
const html = await engine.parseAndRender('{{"<"}}')
expect(html).to.equal('{<}')
expect(html).toBe('{<}')
})
it('should skip escape for output with filter "| raw"', async function () {
@@ -37,6 +36,6 @@ describe('LiquidOptions#*outputEscape*', function () {
outputEscape: 'escape'
})
const html = await engine.parseAndRender('{{"<" | raw}}')
expect(html).to.equal('<')
expect(html).toBe('<')
})
})
@@ -1,4 +1,3 @@
import { expect } from 'chai'
import { Liquid } from '../../../src/liquid'
describe('liquid#registerFilter()', function () {
@@ -15,13 +14,13 @@ describe('liquid#registerFilter()', function () {
const src = `{{ "a" | obj_test: k1: "v1", k2: foo }}`
const dst = '["a",["k1","v1"],["k2","bar"]]'
const html = await liquid.parseAndRender(src, { foo: 'bar' })
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('should support mixed arguments', async () => {
const src = `{{ "a" | obj_test: "something", k1: "v1", k2: foo }}`
const dst = '["a","something",["k1","v1"],["k2","bar"]]'
const html = await liquid.parseAndRender(src, { foo: 'bar' })
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
})
@@ -33,7 +32,7 @@ describe('liquid#registerFilter()', function () {
const src = `{{ userId | get_user_data | json }}`
const dst = '{"userId":"alice","userName":"ALICE"}'
const html = await liquid.parseAndRender(src, { userId: 'alice' })
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
})
@@ -48,7 +47,7 @@ describe('liquid#registerFilter()', function () {
const src = `{{ "a\nb" | break }}`
const dst = 'a&lt;br/&gt;b'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('should not escape filter output when registered as "raw"', async () => {
liquid.registerFilter('break', {
@@ -58,7 +57,7 @@ describe('liquid#registerFilter()', function () {
const src = `{{ "a\nb" | break }}`
const dst = 'a<br/>b'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
})
})
@@ -1,4 +1,3 @@
import { expect } from 'chai'
import { Liquid } from '../../../src/liquid'
describe('liquid#registerTag()', function () {
@@ -8,7 +7,7 @@ describe('liquid#registerTag()', function () {
render: () => 'B'
})
const html = await liquid.parseAndRender(`A{% simple-string %}C`)
return expect(html).to.equal('ABC')
return expect(html).toBe('ABC')
})
it('should support async tag render', async () => {
const liquid = new Liquid()
@@ -16,7 +15,7 @@ describe('liquid#registerTag()', function () {
render: async () => 'B'
})
const html = await liquid.parseAndRender(`A{% async-string %}C`)
return expect(html).to.equal('ABC')
return expect(html).toBe('ABC')
})
it('should have access to ctx in render()', async () => {
const liquid = new Liquid()
@@ -26,7 +25,7 @@ describe('liquid#registerTag()', function () {
const html = await liquid.parseAndRender(`A{% dynamic-string %}C`, {
c: 'B'
})
return expect(html).to.equal('ABC')
return expect(html).toBe('ABC')
})
it('should have access to tag arguments', async () => {
const liquid = new Liquid()
@@ -37,6 +36,6 @@ describe('liquid#registerTag()', function () {
const html = await liquid.parseAndRender(`A{% argument-reflector variable=c %}C`, {
c: 'B'
})
return expect(html).to.equal('ABC')
return expect(html).toBe('ABC')
})
})
@@ -1,15 +1,14 @@
import { normalize } from '../../../src/liquid-options'
import { expect } from 'chai'
describe('LiquidOptions#root', function () {
describe('#normalize ()', function () {
it('should normalize string typed root array', function () {
const options = normalize({ root: 'foo' })
expect(options.root).to.eql(['foo'])
expect(options.root).toEqual(['foo'])
})
it('should normalize null typed root as empty array', function () {
const options = normalize({ root: null } as any)
expect(options.root).to.eql([])
expect(options.root).toEqual([])
})
})
})
@@ -1,9 +1,4 @@
import { Liquid } from '../../../src/liquid'
import * as chai from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
chai.use(chaiAsPromised)
const expect = chai.expect
describe('LiquidOptions#strict*', function () {
let engine: Liquid
@@ -16,7 +11,7 @@ describe('LiquidOptions#strict*', function () {
})
it('should not throw when strictVariables false (default)', async function () {
const html = await engine.parseAndRender('before{{notdefined}}after', ctx)
return expect(html).to.equal('beforeafter')
return expect(html).toBe('beforeafter')
})
it('should throw when strictVariables true', function () {
const tpl = engine.parse('before{{notdefined}}after')
@@ -25,8 +20,7 @@ describe('LiquidOptions#strict*', function () {
extname: '.html',
strictVariables: true
})
return expect(engine.render(tpl, ctx)).to
.be.rejectedWith(/undefined variable: notdefined/)
return expect(engine.render(tpl, ctx)).rejects.toThrow(/undefined variable: notdefined/)
})
it('should pass strictVariables to render by parseAndRender', function () {
const html = 'before{{notdefined}}after'
@@ -35,8 +29,7 @@ describe('LiquidOptions#strict*', function () {
extname: '.html',
strictVariables: true
})
return expect(engine.parseAndRender(html, ctx)).to
.be.rejectedWith(/undefined variable: notdefined/)
return expect(engine.parseAndRender(html, ctx)).rejects.toThrow(/undefined variable: notdefined/)
})
describe('with strictVariables and lenientIf', function () {
beforeEach(() => {
@@ -50,31 +43,31 @@ describe('LiquidOptions#strict*', function () {
it('should not throw in `if` with a single variable', async function () {
const tpl = engine.parse('before{% if notdefined %}{{notdefined}}{% endif %}after')
const html = await engine.render(tpl, ctx)
return expect(html).to.equal('beforeafter')
return expect(html).toBe('beforeafter')
})
it('should support elsif with undefined variables', async function () {
const tpl = engine.parse('{% if notdefined1 %}a{% elsif notdefined2 %}b{% elsif defined3 %}{{defined3}}{% else %}d{% endif %}')
const html = await engine.render(tpl, { 'defined3': 'bla' })
return expect(html).to.equal('bla')
return expect(html).toBe('bla')
})
it('should not throw in `unless` with a single variable', async function () {
const tpl = engine.parse('before{% unless notdefined %}X{% else %}{{notdefined}}{% endunless %}after')
const html = await engine.render(tpl, ctx)
return expect(html).to.equal('beforeXafter')
return expect(html).toBe('beforeXafter')
})
it('should still throw with an undefined variable in a compound `if` expression', function () {
const tpl = engine.parse('{% if notdefined == 15 %}a{% endif %}')
const fhtml = engine.render(tpl, ctx)
return expect(fhtml).to.be.rejectedWith(/undefined variable: notdefined/)
return expect(fhtml).rejects.toThrow(/undefined variable: notdefined/)
})
it('should allow an undefined variable when before the `default` filter', async function () {
const tpl = engine.parse('{{notdefined | default: "a" | tolower}}')
const html = await engine.render(tpl, ctx)
return expect(html).to.equal('a')
return expect(html).toBe('a')
})
it('should not allow undefined variable even if `lenientIf` set', async function () {
const tpl = engine.parse('{{notdefined | tolower}}')
return expect(() => engine.renderSync(tpl, ctx)).to.throw('undefined variable: notdefined')
return expect(() => engine.renderSync(tpl, ctx)).toThrow('undefined variable: notdefined')
})
})
})
@@ -1,4 +1,3 @@
import { expect } from 'chai'
import { Liquid } from '../../../src/liquid'
describe('LiquidOptions#trimming', function () {
@@ -8,34 +7,34 @@ describe('LiquidOptions#trimming', function () {
it('should respect trimTagLeft', async function () {
const engine = new Liquid({ trimTagLeft: true })
const html = await engine.parseAndRender(' \n \t{%if true%}foo{%endif%} ')
return expect(html).to.equal('foo ')
return expect(html).toBe('foo ')
})
it('should respect trimTagRight', async function () {
const engine = new Liquid({ trimTagRight: true } as any)
const html = await engine.parseAndRender('\t{%if true%}foo{%endif%} \n')
return expect(html).to.equal('\tfoo')
return expect(html).toBe('\tfoo')
})
it('should not trim value', async function () {
const engine = new Liquid({ trimTagLeft: true, trimTagRight: true } as any)
const html = await engine.parseAndRender('{%if true%}a {{name}} b{%endif%}', ctx)
return expect(html).to.equal('a harttle b')
return expect(html).toBe('a harttle b')
})
})
describe('value trimming', function () {
it('should respect trimOutputLeft', async function () {
const engine = new Liquid({ trimOutputLeft: true } as any)
const html = await engine.parseAndRender(' \n \t{{name}} ', ctx)
return expect(html).to.equal('harttle ')
return expect(html).toBe('harttle ')
})
it('should respect trimOutputRight', async function () {
const engine = new Liquid({ trimOutputRight: true } as any)
const html = await engine.parseAndRender(' \n \t{{name}} ', ctx)
return expect(html).to.equal(' \n \tharttle')
return expect(html).toBe(' \n \tharttle')
})
it('should respect not trim tag', async function () {
const engine = new Liquid({ trimOutputLeft: true, trimOutputRight: true } as any)
const html = await engine.parseAndRender('\t{% if true %} aha {%endif%}\t')
return expect(html).to.equal('\t aha \t')
return expect(html).toBe('\t aha \t')
})
})
describe('greedy', function () {
@@ -43,12 +42,12 @@ describe('LiquidOptions#trimming', function () {
it('should enable greedy by default', async function () {
const engine = new Liquid()
const html = await engine.parseAndRender(src, ctx)
return expect(html).to.equal('aharttle')
return expect(html).toBe('aharttle')
})
it('should allow greedy:false', async function () {
const engine = new Liquid({ greedy: false } as any)
const html = await engine.parseAndRender(src, ctx)
return expect(html).to.equal('\n a \nharttle ')
return expect(html).toBe('\n a \nharttle ')
})
})
describe('markup', function () {
@@ -64,7 +63,7 @@ describe('LiquidOptions#trimming', function () {
].join('\n')
const dst = 'Wow, John G. Chalmers-Smith, you have a long name!'
const html = await engine.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('should not trim when not specified', async function () {
const engine = new Liquid()
@@ -78,7 +77,7 @@ describe('LiquidOptions#trimming', function () {
].join('\n')
const dst = '\n\n Wow, John G. Chalmers-Smith, you have a long name!\n'
const html = await engine.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
})
})
@@ -1,4 +1,3 @@
import { expect } from 'chai'
import { Liquid } from '../../../src/liquid'
const liquid = new Liquid()
@@ -444,7 +443,7 @@ describe('Whitespace Control', function () {
item.text,
async () => {
const html = await liquid.parseAndRender(item.text)
expect(html).to.equal(item.expected)
expect(html).toBe(item.expected)
}
))
})
@@ -1,41 +1,37 @@
import { Liquid } from '../../../src/liquid'
import { ParseError } from '../../../src'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/assign', function () {
const liquid = new Liquid()
it('should throw when variable name illegal', function () {
const src = '{% assign / %}'
const ctx = {}
return expect(liquid.parseAndRender(src, ctx)).to.be.rejectedWith(/illegal/)
return expect(liquid.parseAndRender(src, ctx)).rejects.toThrow(/illegal/)
})
it('should support assign to a string', async function () {
const src = '{% assign foo="bar" %}{{foo}}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('bar')
return expect(html).toBe('bar')
})
it('should throw when variable value illegal', function () {
const src = '{% assign foo = “bar” %}'
expect(() => liquid.parse(src)).to.throw(/unexpected token at "“bar”"/)
expect(() => liquid.parse(src)).to.throw(ParseError)
expect(() => liquid.parse(src)).toThrow(/unexpected token at "“bar”"/)
expect(() => liquid.parse(src)).toThrow(ParseError)
})
it('should support assign to a number', async function () {
const src = '{% assign foo=10086 %}{{foo}}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('10086')
return expect(html).toBe('10086')
})
it('should assign as array', async function () {
const src = '{% assign foo=(1..3) %}{{foo}}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('123')
return expect(html).toBe('123')
})
it('should assign as filter result', async function () {
const src = '{% assign foo="a b" | capitalize | split: " " | first %}{{foo}}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('A')
return expect(html).toBe('A')
})
it('should assign as filter across multiple lines as result', async function () {
const src = `{% assign foo="a b"
@@ -43,64 +39,64 @@ describe('tags/assign', function () {
| split: " "
| first %}{{foo}}`
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('A')
return expect(html).toBe('A')
})
it('should assign var-1', async function () {
const src = '{% assign var-1 = 5 %}{{ var-1 }}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('5')
return expect(html).toBe('5')
})
it('should assign var-', async function () {
const src = '{% assign var- = 5 %}{{ var- }}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('5')
return expect(html).toBe('5')
})
it('should assign -var', async function () {
const src = '{% assign -let = 5 %}{{ -let }}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('5')
return expect(html).toBe('5')
})
it('should assign -5-5', async function () {
const src = '{% assign -5-5 = 5 %}{{ -5-5 }}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('5')
return expect(html).toBe('5')
})
it('should assign 4-3', async function () {
const src = '{% assign 4-3 = 5 %}{{ 4-3 }}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('5')
return expect(html).toBe('5')
})
it('should not assign -6', async function () {
const src = '{% assign -6 = 5 %}{{ -6 }}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('-6')
return expect(html).toBe('-6')
})
it('should allow reassignment', async function () {
const src = '{% assign var = 1 %}{% assign var = 2 %}{{ var }}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('2')
return expect(html).toBe('2')
})
describe('scope', function () {
it('should read from parent scope', async function () {
const src = '{%for a in (1..2)%}{{num}}{%endfor%}'
const html = await liquid.parseAndRender(src, { num: 1 })
return expect(html).to.equal('11')
return expect(html).toBe('11')
})
it('should write to the root scope', async function () {
const src = '{%for a in (1..2)%}{%assign num = a%}{{a}}{%endfor%}'
const html = await liquid.parseAndRender(src, { num: 1 })
return expect(html).to.equal('12')
return expect(html).toBe('12')
})
it('should not change input scope', async function () {
const src = '{%for a in (1..2)%}{%assign num = a%}{{a}}{%endfor%} {{num}}'
const ctx = { num: 1 }
await liquid.parseAndRender(src, ctx)
return expect(ctx.num).to.equal(1)
return expect(ctx.num).toBe(1)
})
})
it('should support sync', function () {
const src = '{% assign foo="bar" %}{{foo}}'
const html = liquid.parseAndRenderSync(src)
return expect(html).to.equal('bar')
return expect(html).toBe('bar')
})
})
@@ -1,8 +1,4 @@
import { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/capture', function () {
const liquid = new Liquid()
@@ -10,37 +6,37 @@ describe('tags/capture', function () {
it('should support capture', async function () {
const src = '{% capture f %}{{"a" | capitalize}}{%endcapture%}{{f}}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('A')
return expect(html).toBe('A')
})
it('should support quoted variable name', async function () {
const src = '{% capture "f" %}{{"a" | capitalize}}{%endcapture%}{{f}}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('A')
return expect(html).toBe('A')
})
it('should not change root scope', async function () {
const src = '{% capture var %}10{% endcapture %}{{var}}'
const ctx = { 'var': 20 }
const html = await liquid.parseAndRender(src, ctx)
expect(html).to.equal('10')
expect(ctx.var).to.equal(20)
expect(html).toBe('10')
expect(ctx.var).toBe(20)
})
it('should throw on invalid identifier', function () {
const src = '{% capture = %}{%endcapture%}'
return expect(liquid.parseAndRender(src))
.to.be.rejectedWith(/= not valid identifier/)
.rejects.toThrow(/= not valid identifier/)
})
it('should throw when capture not closed', function () {
const src = '{%capture c%}{{c}}'
return expect(liquid.parseAndRender(src))
.to.be.rejectedWith(/tag .* not closed/)
.rejects.toThrow(/tag .* not closed/)
})
it('should support sync', function () {
const src = '{% capture f %}{{"a" | capitalize}}{%endcapture%}{{f}}'
const html = liquid.parseAndRenderSync(src)
return expect(html).to.equal('A')
return expect(html).toBe('A')
})
})
@@ -1,8 +1,4 @@
import { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/case', function () {
const liquid = new Liquid()
@@ -10,43 +6,43 @@ describe('tags/case', function () {
it('should reject if not closed', function () {
const src = '{% case "foo"%}'
return expect(liquid.parseAndRender(src))
.to.be.rejectedWith(/{% case "foo"%} not closed/)
.rejects.toThrow(/{% case "foo"%} not closed/)
})
it('should hit the specified case', async function () {
const src = '{% case "foo"%}' +
'{% when "foo" %}foo{% when "bar"%}bar' +
'{%endcase%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('foo')
return expect(html).toBe('foo')
})
it('should resolve blank as empty string', async function () {
const src = '{% case blank %}{% when ""%}bar{%endcase%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('bar')
return expect(html).toBe('bar')
})
it('should resolve empty as empty string', async function () {
const src = '{% case empty %}{% when ""%}bar{%endcase%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('bar')
return expect(html).toBe('bar')
})
it('should accept empty string as branch name', async function () {
const src = '{% case "" %}{% when ""%}bar{%endcase%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('bar')
return expect(html).toBe('bar')
})
it('should support boolean case', async function () {
const src = '{% case false %}' +
'{% when "foo" %}foo{% when false%}bar' +
'{%endcase%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('bar')
return expect(html).toBe('bar')
})
it('should support else branch', async function () {
const src = '{% case "a" %}' +
'{% when "b" %}b{% when "c"%}c{%else %}d' +
'{%endcase%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('d')
return expect(html).toBe('d')
})
describe('sync support', function () {
it('should hit the specified case', function () {
@@ -54,14 +50,14 @@ describe('tags/case', function () {
'{% when "foo" %}foo{% when "bar"%}bar' +
'{%endcase%}'
const html = liquid.parseAndRenderSync(src)
return expect(html).to.equal('foo')
return expect(html).toBe('foo')
})
it('should support else branch', function () {
const src = '{% case "a" %}' +
'{% when "b" %}b{% when "c"%}c{%else %}d' +
'{%endcase%}'
const html = liquid.parseAndRenderSync(src)
return expect(html).to.equal('d')
return expect(html).toBe('d')
})
})
it('should support case with multiple values', async function () {
@@ -69,7 +65,7 @@ describe('tags/case', function () {
'{% when "a", "b" %}foo' +
'{%endcase%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('foo')
return expect(html).toBe('foo')
})
it('should render multiple matching branches', async function () {
const src = '{% case "b" %}' +
@@ -77,6 +73,6 @@ describe('tags/case', function () {
'{% when "b" %}second' +
'{%endcase%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('firstsecond')
return expect(html).toBe('firstsecond')
})
})
@@ -1,41 +1,37 @@
import { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/comment', function () {
const liquid = new Liquid()
it('should support empty content', function () {
const src = '{% comment %}{% raw%}'
return expect(liquid.parseAndRender(src))
.to.be.rejectedWith(/{% comment %} not closed/)
.rejects.toThrow(/{% comment %} not closed/)
})
it('should ignore plain string', async function () {
const src = 'My name is {% comment %}super{% endcomment %} Shopify.'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('My name is Shopify.')
return expect(html).toBe('My name is Shopify.')
})
it('should ignore output tokens', async function () {
const src = '{% comment %}\n{{ foo}} \n{% endcomment %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('')
return expect(html).toBe('')
})
it('should ignore tag tokens', async function () {
const src = '{% comment %}{%if true%}true{%else%}false{%endif%}{% endcomment %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('')
return expect(html).toBe('')
})
it('should ignore un-balenced tag tokens', async function () {
const src = '{% comment %}{%if true%}true{%else%}false{% endcomment %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('')
return expect(html).toBe('')
})
describe('sync support', function () {
it('should ignore plain string', function () {
const src = 'My name is {% comment %}super{% endcomment %} Shopify.'
const html = liquid.parseAndRenderSync(src)
return expect(html).to.equal('My name is Shopify.')
return expect(html).toBe('My name is Shopify.')
})
})
})
@@ -1,8 +1,4 @@
import { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/cycle', function () {
const liquid = new Liquid()
@@ -10,12 +6,12 @@ describe('tags/cycle', function () {
it('should support cycle', async function () {
const src = "{% cycle '1', '2', '3' %}"
const html = await liquid.parseAndRender(src + src + src + src)
return expect(html).to.equal('1231')
return expect(html).toBe('1231')
})
it('should throw when cycle candidates empty', function () {
return expect(liquid.parseAndRender('{%cycle%}'))
.to.be.rejectedWith(/empty candidates/)
.rejects.toThrow(/empty candidates/)
})
it('should support cycle in for block', async function () {
@@ -24,7 +20,7 @@ describe('tags/cycle', function () {
one: 1
}
const html = await liquid.parseAndRender(src, ctx)
return expect(html).to.equal('1e1e1')
return expect(html).toBe('1e1e1')
})
it('should considered different groups for different arguments', async function () {
@@ -32,7 +28,7 @@ describe('tags/cycle', function () {
"{% cycle '1', '2'%}" +
"{% cycle '1', '2', '3'%}"
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('112')
return expect(html).toBe('112')
})
it('should support cycle group', async function () {
@@ -41,11 +37,11 @@ describe('tags/cycle', function () {
"{% cycle 2: '1', '2', '3'%}"
const ctx = { one: 1 }
const html = await liquid.parseAndRender(src, ctx)
return expect(html).to.equal('121')
return expect(html).toBe('121')
})
it('should support sync', function () {
const src = "{% cycle '1', '2', '3' %}"
const html = liquid.parseAndRenderSync(src + src + src + src)
return expect(html).to.equal('1231')
return expect(html).toBe('1231')
})
})
@@ -1,8 +1,4 @@
import { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/decrement', function () {
const liquid = new Liquid()
@@ -10,44 +6,44 @@ describe('tags/decrement', function () {
it('should decrement undefined variable', async function () {
const src = '{% decrement var %}{% decrement var %}{% decrement var %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('-1-2-3')
return expect(html).toBe('-1-2-3')
})
it('should decrement defined variable', async function () {
const src = '{% decrement var %}{% decrement var %}{% decrement var %}'
const ctx = { 'var': 10 }
const html = await liquid.parseAndRender(src, ctx)
expect(html).to.equal('987')
expect(ctx.var).to.equal(7)
expect(html).toBe('987')
expect(ctx.var).toBe(7)
})
it('should be independent from assign', async function () {
const src = '{% assign var=10 %}{% decrement var %}{% decrement var %}{% decrement var %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('-1-2-3')
return expect(html).toBe('-1-2-3')
})
it('should be independent from capture', async function () {
const src = '{% capture var %}10{% endcapture %}{% decrement var %}{% decrement var %}{% decrement var %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('-1-2-3')
return expect(html).toBe('-1-2-3')
})
it('should not shading assign', async function () {
const src = '{% assign var=10 %}{% decrement var %}{% decrement var %}{% decrement var %} {{var}}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('-1-2-3 10')
return expect(html).toBe('-1-2-3 10')
})
it('should not shading capture', async function () {
const src = '{% capture var %}10{% endcapture %}{% decrement var %}{% decrement var %}{% decrement var %} {{var}}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('-1-2-3 10')
return expect(html).toBe('-1-2-3 10')
})
it('should share the same variable with increment', async function () {
const src = '{%increment var%}{%increment var%}{%decrement var%}{%decrement var%}{%increment var%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('01100')
return expect(html).toBe('01100')
})
})
@@ -1,8 +1,4 @@
import { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/echo', function () {
const liquid = new Liquid()
@@ -10,25 +6,25 @@ describe('tags/echo', function () {
it('should output literals', async function () {
const src = '{% echo 1 %} {% echo "1" %} {% echo 1.1 %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('1 1 1.1')
return expect(html).toBe('1 1 1.1')
})
it('should output variables', async function () {
const src = '{% echo people.users[0].name %}'
const html = await liquid.parseAndRender(src, { people: { users: [ { name: 'Sally' } ] } })
return expect(html).to.equal('Sally')
return expect(html).toBe('Sally')
})
it('should apply filters before output', async function () {
const src = '{% echo user.name | upcase | prepend: "Hello, " | append: "!" %}'
const html = await liquid.parseAndRender(src, { user: { name: 'Sally' } })
return expect(html).to.equal('Hello, SALLY!')
return expect(html).toBe('Hello, SALLY!')
})
it('should handle empty tag', async function () {
const src = '{% echo %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('')
return expect(html).toBe('')
})
it('should handle extra whitespace', async function () {
@@ -38,6 +34,6 @@ describe('tags/echo', function () {
"Hello, " | append: "!"
%}`
const html = await liquid.parseAndRender(src, { user: { name: 'Sally' } })
return expect(html).to.equal('Hello, SALLY!')
return expect(html).toBe('Hello, SALLY!')
})
})
@@ -1,14 +1,10 @@
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import { Liquid } from '../../../src/liquid'
import { Drop } from '../../../src/drop/drop'
import { Scope } from '../../../src/context/scope'
use(chaiAsPromised)
describe('tags/for', function () {
let liquid: Liquid, scope: Scope
before(function () {
beforeEach(function () {
liquid = new Liquid()
liquid.registerTag('throwingTag', {
render: function () { throw new Error('intended render error') }
@@ -29,57 +25,57 @@ describe('tags/for', function () {
it('should support array', async function () {
const src = '{%for c in alpha%}{{c}}{%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('abc')
return expect(html).toBe('abc')
})
it('should support promise of array', async function () {
const src = '{%for c in promiseArray%}{{c}}{%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('abc')
return expect(html).toBe('abc')
})
it('should support object', async function () {
const src = '{%for item in obj%}{{item[0]}},{{item[1]}}-{%else%}b{%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('foo,bar-coo,haa-')
return expect(html).toBe('foo,bar-coo,haa-')
})
it('should output forloop', async function () {
const src = '{%for i in (1..1)%}{{forloop}}{%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('{"i":0,"length":1,"name":"i-(1..1)"}')
return expect(html).toBe('{"i":0,"length":1,"name":"i-(1..1)"}')
})
it('should output forloop collection name', async function () {
const src = '{%for c in alpha%}{{forloop.name}}-{{c}}{%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('c-alpha-ac-alpha-bc-alpha-c')
return expect(html).toBe('c-alpha-ac-alpha-bc-alpha-c')
})
it('should output forloop property accessor name', async function () {
const src = '{%for c in obj.foo%}{{forloop.name}}-{{c}}{%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('c-obj.foo-bar')
return expect(html).toBe('c-obj.foo-bar')
})
it('should output forloop quoted name', async function () {
const src = '{%for str in "string"%}{{forloop.name}}-{{str}}{%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('str-"string"-string')
return expect(html).toBe('str-"string"-string')
})
describe('illegal', function () {
it('should reject when for not closed', function () {
const src = '{%for c in alpha%}{{c}}'
return expect(liquid.parseAndRender(src, scope))
.to.be.rejectedWith(/tag .* not closed/)
.rejects.toThrow(/tag .* not closed/)
})
it('should reject when for in not found', function () {
const src = '{%for c alpha%}{{c}}'
return expect(liquid.parseAndRender(src, scope))
.to.be.rejectedWith('illegal tag: {%for c alpha%}, line:1, col:1')
.rejects.toThrow('illegal tag: {%for c alpha%}, line:1, col:1')
})
it('should reject when inner templates rejected', function () {
const src = '{%for c in alpha%}{%throwingTag%}{%endfor%}'
return expect(liquid.parseAndRender(src, scope))
.to.be.rejectedWith(/intended render error/)
.rejects.toThrow(/intended render error/)
})
})
@@ -87,38 +83,38 @@ describe('tags/for', function () {
it('should goto else for empty array', async function () {
const src = '{%for c in emptyArray%}a{%else%}b{%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('b')
return expect(html).toBe('b')
})
it('should treat non-empty string as one single element', async function () {
const src = '{%for c in "abc"%}x{{c}}{%else%}y{%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('xabc')
return expect(html).toBe('xabc')
})
it('should goto else for empty string', async function () {
const src = '{%for c in ""%}a{%else%}b{%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('b')
return expect(html).toBe('b')
})
it('should goto else for empty string object', async function () {
// it should be false although `new String` is none-conform
const src = '{%for c in strObj%}a{%else%}b{%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('b')
return expect(html).toBe('b')
})
it('should goto else for empty object', async function () {
const src = '{%for c in emptyObj%}a{%else%}b{%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('b')
return expect(html).toBe('b')
})
it('should goto else for null-prototyped object', async function () {
const src = '{%for c in nullProtoObj%}a{%else%}b{%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('b')
return expect(html).toBe('b')
})
})
@@ -133,7 +129,7 @@ describe('tags/for', function () {
'false.2.1.false.3.2.1b\n' +
'false.3.2.true.3.1.0c\n'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
describe('continue', function () {
@@ -142,7 +138,7 @@ describe('tags/for', function () {
'{% if i == 4 %}continue{% continue %}{% endif %}{{i}}' +
'{% endfor %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('123continue5')
return expect(html).toBe('123continue5')
})
it('should output contents before continue', async function () {
const src = '{% for i in (1..5) %}' +
@@ -150,7 +146,7 @@ describe('tags/for', function () {
'{{ i }}' +
'{% endfor %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('123continue5')
return expect(html).toBe('123continue5')
})
})
describe('break', function () {
@@ -160,7 +156,7 @@ describe('tags/for', function () {
'{{ i }}' +
'{% endfor %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('123')
return expect(html).toBe('123')
})
it('should output contents before break', async function () {
const src = '{% for i in (1..5) %}' +
@@ -168,7 +164,7 @@ describe('tags/for', function () {
'{{ i }}' +
'{% endfor %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('123breaking')
return expect(html).toBe('123breaking')
})
})
@@ -176,22 +172,22 @@ describe('tags/for', function () {
it('should support for with limit', async function () {
const src = '{% for i in (1..5) limit:2 %}{{ i }}{% endfor %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('12')
return expect(html).toBe('12')
})
it('should set forloop.last properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.last}} {%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('false true ')
return expect(html).toBe('false true ')
})
it('should set forloop.first properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.first}} {%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('true false ')
return expect(html).toBe('true false ')
})
it('should set forloop.length properly', function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.length}} {%endfor%}'
return expect(liquid.parseAndRender(src, scope))
.to.eventually.equal('2 2 ')
.resolves.toBe('2 2 ')
})
})
@@ -199,48 +195,48 @@ describe('tags/for', function () {
it('should support offset with limit', async function () {
const src = '{% for i in (1..10) limit:2 offset:5%}{{ i }}{% endfor %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('67')
return expect(html).toBe('67')
})
it('should set index properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.index}} {%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('1 2 ')
return expect(html).toBe('1 2 ')
})
it('should set index0 properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.index0}} {%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('0 1 ')
return expect(html).toBe('0 1 ')
})
it('should set rindex properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.rindex}} {%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('2 1 ')
return expect(html).toBe('2 1 ')
})
it('should set rindex0 properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.rindex0}} {%endfor%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('1 0 ')
return expect(html).toBe('1 0 ')
})
it('should continue from limit-ed loop', async function () {
const src = '{%for i in arr limit:2%}{{i}}{%endfor%}-{%for i in arr offset:continue%}{{i}}{%endfor%}'
const html = await liquid.parseAndRender(src, { arr: [1, 2, 3, 4, 5] })
return expect(html).to.equal('12-345')
return expect(html).toBe('12-345')
})
it('should continue nothing for fully iterated loop', async function () {
const src = '{%for i in arr%}{{i}}{%endfor%}-{%for i in arr offset:continue%}{{i}}{%endfor%}'
const html = await liquid.parseAndRender(src, { arr: [1, 2, 3, 4, 5] })
return expect(html).to.equal('12345-')
return expect(html).toBe('12345-')
})
it('should treat different variable names as different forloop', async function () {
const src = '{%for i in (1..5)%}{{i}}{%endfor%}-{%for j in (1..5) offset:continue%}{{j}}{%endfor%}'
const html = await liquid.parseAndRender(src, {})
return expect(html).to.equal('12345-12345')
return expect(html).toBe('12345-12345')
})
it('should treat different collection names as different forloop', async function () {
const src = '{%for i in arr1%}{{i}}{%endfor%}-{%for i in arr2 offset:continue%}{{i}}{%endfor%}'
const arr = [1, 2, 3, 4, 5]
const html = await liquid.parseAndRender(src, { arr1: arr, arr2: arr })
return expect(html).to.equal('12345-12345')
return expect(html).toBe('12345-12345')
})
})
@@ -248,33 +244,33 @@ describe('tags/for', function () {
it('should support for reversed in the last position', async function () {
const src = '{% for i in (1..8) limit:2 reversed %}{{ i }}{% endfor %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('21')
return expect(html).toBe('21')
})
it('should support for reversed in the first position', async function () {
const src = '{% for i in (1..8) reversed limit:2 %}{{ i }}{% endfor %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('21')
return expect(html).toBe('21')
})
it('should support for reversed in the first position with orderedFilterParameters=true', async function () {
const liquid = new Liquid({ orderedFilterParameters: true })
const src = '{% for i in (1..8) reversed limit:2 %}{{ i }}{% endfor %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('87')
return expect(html).toBe('87')
})
it('should support for reversed in the middle position', async function () {
const src = '{% for i in (1..8) offset:2 reversed limit:3 %}{{ i }}{% endfor %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('543')
return expect(html).toBe('543')
})
it('should support for reversed in the middle position with orderedFilterParameters=true', async function () {
const liquid = new Liquid({ orderedFilterParameters: true })
const src = '{% for i in (1..8) offset:2 reversed limit:3 %}{{ i }}{% endfor %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('876')
return expect(html).toBe('876')
})
})
@@ -282,7 +278,7 @@ describe('tags/for', function () {
it('should support sync', function () {
const src = '{% for i in (1..5) %}{{i}}{%endfor%}'
const html = liquid.parseAndRenderSync(src)
return expect(html).to.equal('12345')
return expect(html).toBe('12345')
})
it('should output contents before break', function () {
const src = '{% for i in (1..5) %}' +
@@ -290,19 +286,19 @@ describe('tags/for', function () {
'{{ i }}' +
'{% endfor %}'
const html = liquid.parseAndRenderSync(src, scope)
return expect(html).to.equal('123breaking')
return expect(html).toBe('123breaking')
})
it('should support for with continue', function () {
const src = '{% for i in (1..5) %}' +
'{% if i == 4 %}continue{% continue %}{% endif %}{{i}}' +
'{% endfor %}'
const html = liquid.parseAndRenderSync(src, scope)
return expect(html).to.equal('123continue5')
return expect(html).toBe('123continue5')
})
it('should goto else for empty array', async function () {
const src = '{%for c in emptyArray%}a{%else%}b{%endfor%}'
const html = await liquid.parseAndRenderSync(src, scope)
return expect(html).to.equal('b')
return expect(html).toBe('b')
})
})
@@ -333,37 +329,37 @@ describe('tags/for', function () {
it('should loop over iterable objects', function () {
const src = '{% for i in someIterable %}{{i}}{%endfor%}'
const html = liquid.parseAndRenderSync(src, { someIterable: new MockIterable() })
return expect(html).to.equal('abc')
return expect(html).toBe('abc')
})
it('should loop over iterable drops', function () {
const src = '{{ someDrop }}: {% for i in someDrop %}{{i}}{%endfor%}'
const html = liquid.parseAndRenderSync(src, { someDrop: new MockIterableDrop() })
return expect(html).to.equal('MockIterableDrop: abc')
return expect(html).toBe('MockIterableDrop: abc')
})
it('should loop over iterable objects with a limit', function () {
const src = '{% for i in someIterable limit:2 %}{{i}}{%endfor%}'
const html = liquid.parseAndRenderSync(src, { someIterable: new MockIterable() })
return expect(html).to.equal('ab')
return expect(html).toBe('ab')
})
it('should loop over iterable objects with an offset', function () {
const src = '{% for i in someIterable offset:1 %}{{i}}{%endfor%}'
const html = liquid.parseAndRenderSync(src, { someIterable: new MockIterable() })
return expect(html).to.equal('bc')
return expect(html).toBe('bc')
})
it('should loop over iterable objects in reverse', function () {
const src = '{% for i in someIterable reversed %}{{i}}{%endfor%}'
const html = liquid.parseAndRenderSync(src, { someIterable: new MockIterable() })
return expect(html).to.equal('cba')
return expect(html).toBe('cba')
})
it('should go to else for an empty iterable', function () {
const src = '{% for i in emptyIterable reversed %}{{i}}{%else%}EMPTY{%endfor%}'
const html = liquid.parseAndRenderSync(src, { emptyIterable: new MockEmptyIterable() })
return expect(html).to.equal('EMPTY')
return expect(html).toBe('EMPTY')
})
it('should support iterable names', function () {
const src = '{% for i in someDrop %}{{forloop.name}} {%else%}EMPTY{%endfor%}'
const html = liquid.parseAndRenderSync(src, { someDrop: new MockIterableDrop() })
return expect(html).to.equal('i-someDrop i-someDrop i-someDrop ')
return expect(html).toBe('i-someDrop i-someDrop i-someDrop ')
})
})
})
@@ -1,9 +1,4 @@
import { Liquid } from '../../../src/liquid'
import * as chai from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
const expect = chai.expect
chai.use(chaiAsPromised)
describe('tags/if', function () {
const liquid = new Liquid()
@@ -17,67 +12,67 @@ describe('tags/if', function () {
it('should throw if not closed', function () {
const src = '{% if false%}yes'
return expect(liquid.parseAndRender(src, scope))
.to.be.rejectedWith(/tag {% if false%} not closed/)
.rejects.toThrow(/tag {% if false%} not closed/)
})
it('should support nested', async function () {
const src = '{%if false%}{%if true%}{%else%}a{%endif%}{%endif%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('')
return expect(html).toBe('')
})
describe('single value as condition', function () {
it('should support boolean', async function () {
const src = '{% if false %}1{%elsif true%}2{%else%}3{%endif%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('2')
return expect(html).toBe('2')
})
it('should treat Array truthy', async function () {
const src = '{%if emptyArray%}a{%endif%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('a')
return expect(html).toBe('a')
})
it('should return true if empty string', async function () {
const src = '{%if emptyString%}a{%endif%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('a')
return expect(html).toBe('a')
})
})
describe('expression as condition', function () {
it('should support ==', async function () {
const src = '{% if 2 == 3 %}yes{%else%}no{%endif%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('no')
return expect(html).toBe('no')
})
it('should support >=', async function () {
const src = '{% if 1 >= 2 and one<two %}a{%endif%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('')
return expect(html).toBe('')
})
it('should support !=', async function () {
const src = '{% if one != two %}yes{%else%}no{%endif%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('yes')
return expect(html).toBe('yes')
})
it('should support value and expression', async function () {
const src = `X{%if version and version != '' %}x{{version}}y{%endif%}Y`
const scope = { 'version': '' }
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('XY')
return expect(html).toBe('XY')
})
it('should evaluate right to left', async function () {
const src = `{% if false and false or true %}true{%endif%}`
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('')
return expect(html).toBe('')
})
it('should allow no spaces around operator for literal', async function () {
const src = `{% if true==true %}success{%else%}fail{% endif %}`
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('success')
return expect(html).toBe('success')
})
it('should allow no spaces around operator for variables', async function () {
const src = `{%assign var = 1%}{%if var ==1%}success{%else%}fail{%endif%}`
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('success')
return expect(html).toBe('success')
})
})
describe('filters as condition', function () {
@@ -85,67 +80,67 @@ describe('tags/if', function () {
liquid.registerFilter('negate', (val) => !val)
const src = '{% if 2 == 3 | negate %}yes{%else%}no{%endif%}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('yes')
return expect(html).toBe('yes')
})
})
describe('compare to null', function () {
it('should evaluate false for null < 10', async function () {
const src = '{% if null < 10 %}yes{% else %}no{% endif %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('no')
return expect(html).toBe('no')
})
it('should evaluate false for null > 10', async function () {
const src = '{% if null > 10 %}yes{% else %}no{% endif %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('no')
return expect(html).toBe('no')
})
it('should evaluate false for null <= 10', async function () {
const src = '{% if null <= 10 %}yes{% else %}no{% endif %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('no')
return expect(html).toBe('no')
})
it('should evaluate false for null >= 10', async function () {
const src = '{% if null >= 10 %}yes{% else %}no{% endif %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('no')
return expect(html).toBe('no')
})
it('should evaluate false for 10 < null', async function () {
const src = '{% if 10 < null %}yes{% else %}no{% endif %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('no')
return expect(html).toBe('no')
})
it('should evaluate false for 10 > null', async function () {
const src = '{% if 10 > null %}yes{% else %}no{% endif %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('no')
return expect(html).toBe('no')
})
it('should evaluate false for 10 <= null', async function () {
const src = '{% if 10 <= null %}yes{% else %}no{% endif %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('no')
return expect(html).toBe('no')
})
it('should evaluate false for 10 >= null', async function () {
const src = '{% if 10 >= null %}yes{% else %}no{% endif %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('no')
return expect(html).toBe('no')
})
})
it('should support sync', function () {
const src = '{%if true%}true{%else%}false{%endif%}'
const html = liquid.parseAndRenderSync(src, scope)
return expect(html).to.equal('true')
return expect(html).toBe('true')
})
it('should support async variables', async () => {
const src = `{%if var == 'var' %}success{%endif%}`
const scope = { 'var': Promise.resolve('var') }
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('success')
return expect(html).toBe('success')
})
})
@@ -1,11 +1,10 @@
import { Liquid } from '../../../src/liquid'
import { Drop } from '../../../src/drop/drop'
import { expect } from 'chai'
import { mock, restore } from '../../stub/mockfs'
describe('tags/include', function () {
let liquid: Liquid
before(function () {
beforeEach(function () {
liquid = new Liquid({
root: '/',
extname: '.html'
@@ -18,7 +17,7 @@ describe('tags/include', function () {
'/bar/foo.html': 'foo'
})
const html = await liquid.renderFile('/current.html')
return expect(html).to.equal('barfoobar')
return expect(html).toBe('barfoobar')
})
it('should support relative reference', async function () {
mock({
@@ -26,7 +25,7 @@ describe('tags/include', function () {
'/foo/coo/foo.html': 'foo'
})
const html = await liquid.renderFile('/foo/bar/current.html')
return expect(html).to.equal('barfoobar')
return expect(html).toBe('barfoobar')
})
it('should support template string', async function () {
mock({
@@ -34,7 +33,7 @@ describe('tags/include', function () {
'/bar/foo.html': 'foo'
})
const html = await liquid.renderFile('/current.html', { name: 'foo.html' })
return expect(html).to.equal('barfoobar')
return expect(html).toBe('barfoobar')
})
it('should allow escape in template string', async function () {
mock({
@@ -42,7 +41,7 @@ describe('tags/include', function () {
'/bar/foo.html': 'foo'
})
const html = await liquid.renderFile('/current.html', { name: 'foo' })
return expect(html).to.equal('barfoobar')
return expect(html).toBe('barfoobar')
})
it('should throw when not specified', function () {
@@ -50,8 +49,8 @@ describe('tags/include', function () {
'/parent.html': '{%include , %}'
})
return liquid.renderFile('/parent.html').catch(function (e) {
expect(e.name).to.equal('ParseError')
expect(e.message).to.match(/illegal argument ","/)
expect(e.name).toBe('ParseError')
expect(e.message).toMatch(/illegal argument ","/)
})
})
@@ -60,8 +59,8 @@ describe('tags/include', function () {
'/parent.html': '{%include not-exist%}'
})
return liquid.renderFile('/parent.html').catch(function (e) {
expect(e.name).to.equal('RenderError')
expect(e.message).to.match(/illegal filename "undefined"/)
expect(e.name).toBe('RenderError')
expect(e.message).toMatch(/illegal filename "undefined"/)
})
})
@@ -71,7 +70,7 @@ describe('tags/include', function () {
'/foo/relative.html': 'bar{% include "../bar/foo.html" %}bar'
})
const html = await liquid.renderFile('foo/relative.html')
return expect(html).to.equal('barfoobar')
return expect(html).toBe('barfoobar')
})
it('should support include: hash list', async function () {
@@ -80,7 +79,7 @@ describe('tags/include', function () {
'/user.html': '{{name}} : {{role}} : {{alias}}'
})
const html = await liquid.renderFile('hash.html')
return expect(html).to.equal('harttle : admin : harttle')
return expect(html).toBe('harttle : admin : harttle')
})
it('should support include: parent scope', async function () {
@@ -89,7 +88,7 @@ describe('tags/include', function () {
'/color.html': 'color:{{color}}, shape:{{shape}}'
})
const html = await liquid.renderFile('scope.html')
return expect(html).to.equal('color:yellow, shape:triangle')
return expect(html).toBe('color:yellow, shape:triangle')
})
it('should support include: with', async function () {
@@ -98,7 +97,7 @@ describe('tags/include', function () {
'/color.html': 'color:{{color}}, shape:{{shape}}'
})
const html = await liquid.renderFile('with.html')
return expect(html).to.equal('color:red, shape:rect')
return expect(html).toBe('color:red, shape:rect')
})
it('should ignore if with value not specified', async function () {
mock({
@@ -106,7 +105,7 @@ describe('tags/include', function () {
'/color.html': 'color:{{color}}, shape:{{shape}}'
})
const html = await liquid.renderFile('with.html')
return expect(html).to.equal('color:, shape:rect')
return expect(html).toBe('color:, shape:rect')
})
it('should treat with as a valid key', async function () {
mock({
@@ -114,7 +113,7 @@ describe('tags/include', function () {
'/color.html': 'with:{{with}}'
})
const html = await liquid.renderFile('with.html')
return expect(html).to.equal('with:foo')
return expect(html).toBe('with:foo')
})
it('should support include: with as Drop', async function () {
class ColorDrop extends Drop {
@@ -127,7 +126,7 @@ describe('tags/include', function () {
'/color.html': 'color:{{color}}'
})
const html = await liquid.renderFile('with.html', { color: new ColorDrop() })
expect(html).to.equal('color:red!')
expect(html).toBe('color:red!')
})
it('should support include: with passed as Drop', async function () {
class ColorDrop extends Drop {
@@ -141,7 +140,7 @@ describe('tags/include', function () {
'/color.html': '{{color | name}}'
})
const html = await liquid.renderFile('with.html', { color: new ColorDrop() })
expect(html).to.equal('ColorDrop')
expect(html).toBe('ColorDrop')
})
it('should support nested includes', async function () {
@@ -160,7 +159,7 @@ describe('tags/include', function () {
}
}
const html = await liquid.renderFile('personInfo.html', ctx)
return expect(html).to.equal('This is a person <p>Joe Shmoe<br/>City: Dallas</p>')
return expect(html).toBe('This is a person <p>Joe Shmoe<br/>City: Dallas</p>')
})
describe('static partial', function () {
@@ -171,7 +170,7 @@ describe('tags/include', function () {
})
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
const html = await staticLiquid.renderFile('parent.html')
return expect(html).to.equal('Xchild with redY')
return expect(html).toBe('Xchild with redY')
})
it('should support parent paths', async function () {
@@ -181,7 +180,7 @@ describe('tags/include', function () {
})
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
const html = await staticLiquid.renderFile('parent.html')
return expect(html).to.equal('XchildY')
return expect(html).toBe('XchildY')
})
it('should support subpaths', async function () {
@@ -191,7 +190,7 @@ describe('tags/include', function () {
})
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
const html = await staticLiquid.renderFile('parent.html')
return expect(html).to.equal('XchildY')
return expect(html).toBe('XchildY')
})
it('should support comma separated arguments', async function () {
@@ -201,7 +200,7 @@ describe('tags/include', function () {
})
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
const html = await staticLiquid.renderFile('parent.html')
return expect(html).to.equal('Xchild with redY')
return expect(html).toBe('Xchild with redY')
})
it('should support single liquid output', async function () {
@@ -211,7 +210,7 @@ describe('tags/include', function () {
})
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
const html = await staticLiquid.renderFile('parent.html', { child: 'child.html' })
return expect(html).to.equal('Xchild with redY')
return expect(html).toBe('Xchild with redY')
})
})
describe('sync support', function () {
@@ -221,7 +220,7 @@ describe('tags/include', function () {
'/bar/foo.html': 'foo'
})
const html = liquid.renderFileSync('/current.html')
return expect(html).to.equal('barfoobar')
return expect(html).toBe('barfoobar')
})
it('should support variable', function () {
mock({
@@ -229,7 +228,7 @@ describe('tags/include', function () {
'/bar/foo.html': 'foo'
})
const html = liquid.renderFileSync('/current.html', { name: '/bar/foo.html' })
return expect(html).to.equal('barfoobar')
return expect(html).toBe('barfoobar')
})
it('should support include: with', function () {
mock({
@@ -237,7 +236,7 @@ describe('tags/include', function () {
'/color.html': 'color:{{color}}, shape:{{shape}}'
})
const html = liquid.renderFileSync('with.html')
return expect(html).to.equal('color:red, shape:rect')
return expect(html).toBe('color:red, shape:rect')
})
it('should support filename with extension', function () {
mock({
@@ -246,12 +245,12 @@ describe('tags/include', function () {
})
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
const html = staticLiquid.renderFileSync('parent.html')
return expect(html).to.equal('Xchild with redY')
return expect(html).toBe('Xchild with redY')
})
})
describe('Jekyll include', function () {
before(function () {
beforeEach(function () {
liquid = new Liquid({
root: '/',
extname: '.html',
@@ -264,7 +263,7 @@ describe('tags/include', function () {
'/bar/foo.html': '{{include.content}}-{{content}}'
})
const html = liquid.renderFileSync('/current.html')
return expect(html).to.equal('FOO-')
return expect(html).toBe('FOO-')
})
it('should support multiple parameters', function () {
mock({
@@ -272,7 +271,7 @@ describe('tags/include', function () {
'/bar/foo.html': '<h2>{{include.header}}</h2>{{include.content}}'
})
const html = liquid.renderFileSync('/current.html')
return expect(html).to.equal('<h2>HEADER</h2>CONTENT')
return expect(html).toBe('<h2>HEADER</h2>CONTENT')
})
it('should support dynamicPartials=true', function () {
mock({
@@ -286,7 +285,7 @@ describe('tags/include', function () {
dynamicPartials: true
})
const html = liquid.renderFileSync('/current.html')
return expect(html).to.equal('FOO-')
return expect(html).toBe('FOO-')
})
})
})
@@ -1,8 +1,4 @@
import { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/increment', function () {
const liquid = new Liquid()
@@ -10,38 +6,38 @@ describe('tags/increment', function () {
it('should increment undefined variable', async function () {
const src = '{% increment one %}{% increment one %}{% increment one %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('012')
return expect(html).toBe('012')
})
it('should increment defined variable', async function () {
const src = '{% increment one %}{% increment one %}{% increment one %}'
const ctx = { one: 7 }
const html = await liquid.parseAndRender(src, ctx)
expect(html).to.equal('789')
expect(ctx.one).to.equal(10)
expect(html).toBe('789')
expect(ctx.one).toBe(10)
})
it('should be independent from assign', async function () {
const src = '{% assign var=10 %}{% increment var %}{% increment var %}{% increment var %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('012')
return expect(html).toBe('012')
})
it('should be independent from capture', async function () {
const src = '{% capture var %}10{% endcapture %}{% increment var %}{% increment var %}{% increment var %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('012')
return expect(html).toBe('012')
})
it('should not shading assign', async function () {
const src = '{% assign var=10 %}{% increment var %}{% increment var %}{% increment var %} {{var}}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('012 10')
return expect(html).toBe('012 10')
})
it('should not hide capture', async function () {
const src = '{% capture var %}10{% endcapture %}{% increment var %}{% increment var %}{% increment var %} {{var}}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('012 10')
return expect(html).toBe('012 10')
})
})
@@ -1,40 +1,36 @@
import { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/inline-comment', function () {
const liquid = new Liquid()
it('should ignore plain string', async function () {
const src = 'My name is {% # super %} Shopify.'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('My name is Shopify.')
return expect(html).toBe('My name is Shopify.')
})
it('should ignore output tokens', async function () {
const src = '{% #\n{{ foo}} \n %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('')
return expect(html).toBe('')
})
it('should support whitespace control', async function () {
const src = '{%- # some comment \n -%}\nfoo'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('foo')
return expect(html).toBe('foo')
})
it('should handle hash without trailing whitespace', async function () {
const src = '{% #some comment %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('')
return expect(html).toBe('')
})
it('should handle hash without leading whitespace', async function () {
const src = '{%#some comment %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('')
return expect(html).toBe('')
})
it('should handle empty comment', async function () {
const src = '{%#%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('')
return expect(html).toBe('')
})
it('should support multiple lines', async function () {
const src = [
@@ -44,7 +40,7 @@ describe('tags/inline-comment', function () {
'-%}'
].join('\n')
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('')
return expect(html).toBe('')
})
it('should enforce leading hashes', async function () {
const src = [
@@ -54,13 +50,13 @@ describe('tags/inline-comment', function () {
'-%}'
].join('\n')
return expect(liquid.parseAndRender(src))
.to.be.rejectedWith(/every line of an inline comment must start with a '#' character/)
.rejects.toThrow(/every line of an inline comment must start with a '#' character/)
})
describe('sync support', function () {
it('should ignore plain string', function () {
const src = 'My name is {% # super %} Shopify.'
const html = liquid.parseAndRenderSync(src)
return expect(html).to.equal('My name is Shopify.')
return expect(html).toBe('My name is Shopify.')
})
})
describe('liquid tag', function () {
@@ -78,7 +74,7 @@ describe('tags/inline-comment', function () {
'-%}'
].join('\n')
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('Hello goodbye')
return expect(html).toBe('Hello goodbye')
})
it('should handle lots of hashes', async function () {
const src = [
@@ -89,7 +85,7 @@ describe('tags/inline-comment', function () {
'-%}'
].join('\n')
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('')
return expect(html).toBe('')
})
})
})
@@ -1,13 +1,9 @@
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import { Liquid } from '../../../src/liquid'
import { mock, restore } from '../../stub/mockfs'
use(chaiAsPromised)
describe('tags/layout', function () {
let liquid: Liquid
before(function () {
beforeEach(function () {
liquid = new Liquid({
root: '/',
extname: '.html'
@@ -20,16 +16,15 @@ describe('tags/layout', function () {
'/parent.html': 'parent'
})
const src = '{% layout "parent" %}{%block%}A'
return expect(liquid.parseAndRender(src)).to
.be.rejectedWith(/tag {%block%} not closed/)
return expect(liquid.parseAndRender(src)).rejects.toThrow(/tag {%block%} not closed/)
})
it('should throw when filename not specified', function () {
mock({
'/parent.html': '{%layout%}'
})
return liquid.renderFile('/parent.html').catch(function (e) {
expect(e.name).to.equal('ParseError')
expect(e.message).to.match(/illegal argument ""/)
expect(e.name).toBe('ParseError')
expect(e.message).toMatch(/illegal argument ""/)
})
})
it('should throw when filename resolved to falsy', function () {
@@ -37,8 +32,8 @@ describe('tags/layout', function () {
'/parent.html': '{%layout foo%}'
})
return liquid.renderFile('/parent.html').catch(function (e) {
expect(e.name).to.equal('RenderError')
expect(e.message).to.contain('illegal filename "undefined"')
expect(e.name).toBe('RenderError')
expect(e.message).toContain('illegal filename "undefined"')
})
})
it('should handle layout none', async function () {
@@ -46,7 +41,7 @@ describe('tags/layout', function () {
'{%block a%}A{%endblock%}' +
'B'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('AB')
return expect(html).toBe('AB')
})
describe('anonymous block', function () {
it('should handle anonymous block', async function () {
@@ -55,7 +50,7 @@ describe('tags/layout', function () {
})
const src = '{% layout "parent.html" %}{%block%}A{%endblock%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('XAY')
return expect(html).toBe('XAY')
})
it('should handle top level contents as anonymous block', async function () {
mock({
@@ -63,7 +58,7 @@ describe('tags/layout', function () {
})
const src = '{% layout "parent.html" %}A'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('XAY')
return expect(html).toBe('XAY')
})
})
it('should handle named blocks', async function () {
@@ -74,7 +69,7 @@ describe('tags/layout', function () {
'{%block a%}A{%endblock%}' +
'{%block b%}B{%endblock%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('XAYBZ')
return expect(html).toBe('XAYBZ')
})
it('should support `options.layouts`', async () => {
mock({
@@ -83,7 +78,7 @@ describe('tags/layout', function () {
const src = '{% layout "parent.html" %}{%block a%}A{%endblock%}'
const liquid = new Liquid({ layouts: '/layouts' })
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('XAY')
return expect(html).toBe('XAY')
})
it('should use `layouts` if specified', async function () {
mock({
@@ -93,7 +88,7 @@ describe('tags/layout', function () {
})
const staticLiquid = new Liquid({ root: '/root', layouts: '/layouts', dynamicPartials: false })
const html = await staticLiquid.renderFile('main.html')
return expect(html).to.equal('LAYOUTS A')
return expect(html).toBe('LAYOUTS A')
})
it('should support block.super', async function () {
@@ -104,7 +99,7 @@ describe('tags/layout', function () {
'{%block css%}{{block.super}}<link href="extra.css" rel="stylesheet">{%endblock%}'
const html = await liquid.parseAndRender(src)
const output = '<link href="base.css" rel="stylesheet"><link href="extra.css" rel="stylesheet">'
return expect(html).to.equal(output)
return expect(html).toBe(output)
})
it('should render block.super to empty if no parent exists', async function () {
mock({
@@ -114,7 +109,7 @@ describe('tags/layout', function () {
'{%block css%}{{block.super}}<link href="extra.css" rel="stylesheet">{%endblock%}'
const html = await liquid.parseAndRender(src)
const output = '<link href="base.css" rel="stylesheet"><link href="extra.css" rel="stylesheet">'
return expect(html).to.equal(output)
return expect(html).toBe(output)
})
it('should support nested block.super', async function () {
mock({
@@ -124,7 +119,7 @@ describe('tags/layout', function () {
const src = '{% layout "parent.html" %}{%block css%}{{block.super}}<link href="extra.css" rel="stylesheet">{%endblock%}'
const html = await liquid.parseAndRender(src)
const output = '<link href="root.css" rel="stylesheet"><link href="parent.css" rel="stylesheet"><link href="extra.css" rel="stylesheet">'
return expect(html).to.equal(output)
return expect(html).toBe(output)
})
it('should support variable as layout name', async function () {
mock({
@@ -132,7 +127,7 @@ describe('tags/layout', function () {
})
const src = '{% layout parent %}{%block a%}A{%endblock%}'
const html = await liquid.parseAndRender(src, { parent: 'parent.html' })
return expect(html).to.equal('XAY')
return expect(html).toBe('XAY')
})
it('should support default block content', async function () {
mock({
@@ -140,7 +135,7 @@ describe('tags/layout', function () {
})
const src = '{% layout "parent.html" %}{%block a%}a{%endblock%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('XaYBZ')
return expect(html).toBe('XaYBZ')
})
it('should handle nested block', async function () {
mock({
@@ -149,7 +144,7 @@ describe('tags/layout', function () {
'/main.html': '{%layout "parent"%}{%block a%}A{%endblock%}'
})
const html = await liquid.renderFile('/main.html')
return expect(html).to.equal('XAY')
return expect(html).toBe('XAY')
})
it('should not bleed scope into `include` layout', async function () {
mock({
@@ -160,7 +155,7 @@ describe('tags/layout', function () {
'/included.html': '{%layout "parent"%}{%block a%}a{%endblock%}'
})
const html = await liquid.renderFile('main')
return expect(html).to.equal('XAYIXaYZJZ')
return expect(html).toBe('XAYIXaYZJZ')
})
it('should not bleed scope into `render` layout', async function () {
mock({
@@ -171,7 +166,7 @@ describe('tags/layout', function () {
'/included.html': '{%layout "parent"%}{%block a%}a{%endblock%}'
})
const html = await liquid.renderFile('main')
return expect(html).to.equal('XAYIXaYZJZ')
return expect(html).toBe('XAYIXaYZJZ')
})
it('should support hash list', async function () {
mock({
@@ -179,7 +174,7 @@ describe('tags/layout', function () {
'/main.html': '{% layout "parent.html" color:"black"%}{%block%}A{%endblock%}'
})
const html = await liquid.renderFile('/main.html')
return expect(html).to.equal('blackA')
return expect(html).toBe('blackA')
})
it('should support multiple hash', async function () {
mock({
@@ -187,7 +182,7 @@ describe('tags/layout', function () {
'/main.html': '{% layout "parent.html" color:"black", bg:"red"%}{%block%}A{%endblock%}'
})
const html = await liquid.renderFile('/main.html')
return expect(html).to.equal('blackredA')
return expect(html).toBe('blackredA')
})
it('should support relative reference', async function () {
@@ -197,7 +192,7 @@ describe('tags/layout', function () {
})
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
const html = await staticLiquid.renderFile('/foo/bar/main.html')
return expect(html).to.equal('blackA')
return expect(html).toBe('blackA')
})
it('should support relative root', async function () {
@@ -207,7 +202,7 @@ describe('tags/layout', function () {
})
const staticLiquid = new Liquid({ root: './foo', dynamicPartials: false })
const html = await staticLiquid.renderFile('bar/main.html')
return expect(html).to.equal('blackA')
return expect(html).toBe('blackA')
})
describe('static partial', function () {
@@ -218,7 +213,7 @@ describe('tags/layout', function () {
})
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
const html = await staticLiquid.renderFile('/main.html')
return expect(html).to.equal('blackA')
return expect(html).toBe('blackA')
})
it('should support parent paths', async function () {
@@ -228,7 +223,7 @@ describe('tags/layout', function () {
})
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
const html = await staticLiquid.renderFile('/main.html')
return expect(html).to.equal('blackA')
return expect(html).toBe('blackA')
})
it('should support none', async function () {
@@ -237,7 +232,7 @@ describe('tags/layout', function () {
})
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
const html = await staticLiquid.renderFile('/main.html')
return expect(html).to.equal('foo')
return expect(html).toBe('foo')
})
it('should support subpaths', async function () {
@@ -247,7 +242,7 @@ describe('tags/layout', function () {
})
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
const html = await staticLiquid.renderFile('/main.html')
return expect(html).to.equal('blackA')
return expect(html).toBe('blackA')
})
})
it('should support sync', function () {
@@ -257,6 +252,6 @@ describe('tags/layout', function () {
'/main.html': '{%layout "parent"%}{%block a%}A{%endblock%}'
})
const html = liquid.renderFileSync('/main.html')
return expect(html).to.equal('XAY')
return expect(html).toBe('XAY')
})
})
@@ -1,9 +1,5 @@
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import { Liquid } from '../../../src/liquid'
use(chaiAsPromised)
describe('tags/liquid', function () {
const liquid = new Liquid()
@@ -19,7 +15,7 @@ describe('tags/liquid', function () {
-%}
`
const html = await liquid.parseAndRender(src, { array: [1, 2, 3] })
return expect(html).to.equal('1#2#3')
return expect(html).toBe('1#2#3')
})
it('should support shorthand syntax with assignments and filters', async function () {
@@ -38,13 +34,13 @@ describe('tags/liquid', function () {
-%}
`
const html = await liquid.parseAndRender(src, { array: [1, 2, 3] })
return expect(html).to.equal('4#8#12#6')
return expect(html).toBe('4#8#12#6')
})
it('should handle empty tag', async function () {
const src = '{% liquid %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('')
return expect(html).toBe('')
})
it('should handle lines containing only whitespace', async function () {
@@ -56,7 +52,7 @@ describe('tags/liquid', function () {
echo 'goodbye'
%}`
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('hello goodbye')
return expect(html).toBe('hello goodbye')
})
it('should fail with carriage return terminated tags', async function () {
@@ -71,6 +67,6 @@ describe('tags/liquid', function () {
'-%}'
].join('\r')
return expect(liquid.parseAndRender(src))
.to.be.rejectedWith(/not closed/)
.rejects.toThrow(/not closed/)
})
})
@@ -1,30 +1,25 @@
import * as chai from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import { Liquid } from '../../../src/liquid'
const expect = chai.expect
chai.use(chaiAsPromised)
describe('tags/raw', function () {
const liquid = new Liquid()
it('should throw when not closed', async function () {
const p = liquid.parseAndRender('{% raw %}')
return expect(p).be.rejectedWith(/{% raw %} not closed/)
return expect(p).rejects.toThrow(/{% raw %} not closed/)
})
it('should output filters as it is', async function () {
const src = '{% raw %}{{ 5 | plus: 6 }}{% endraw %} is equal to 11.'
const dst = '{{ 5 | plus: 6 }} is equal to 11.'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('should preserve blank characters', async function () {
const src = '{% raw %}\n{{ foo}} \n{% endraw %}'
const dst = '\n{{ foo}} \n'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('should support sync', function () {
const html = liquid.parseAndRenderSync('{% raw %}{{foo}}{% endraw %}')
return expect(html).to.equal('{{foo}}')
return expect(html).toBe('{{foo}}')
})
})
@@ -1,13 +1,10 @@
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import { Liquid } from '../../../src/liquid'
import { Drop } from '../../../src/drop/drop'
import { mock, restore } from '../../stub/mockfs'
use(chaiAsPromised)
describe('tags/render', function () {
let liquid: Liquid
before(function () {
beforeEach(function () {
liquid = new Liquid({
root: '/',
extname: '.html'
@@ -20,7 +17,7 @@ describe('tags/render', function () {
'/bar/foo.html': 'foo'
})
const html = await liquid.renderFile('/current.html')
expect(html).to.equal('barfoobar')
expect(html).toBe('barfoobar')
})
it('should support render', async function () {
mock({
@@ -29,7 +26,7 @@ describe('tags/render', function () {
})
const liquid = new Liquid({ partials: '/partials', root: '/' })
const html = await liquid.renderFile('/current.html')
expect(html).to.equal('barfoobar')
expect(html).toBe('barfoobar')
})
it('should support template string', async function () {
mock({
@@ -37,7 +34,7 @@ describe('tags/render', function () {
'/bar/foo.html': 'foo'
})
const html = await liquid.renderFile('/current.html', { name: 'foo.html' })
expect(html).to.equal('barfoobar')
expect(html).toBe('barfoobar')
})
it('should throw when not specified', function () {
@@ -45,8 +42,8 @@ describe('tags/render', function () {
'/parent.html': '{%render%}'
})
return liquid.renderFile('/parent.html').catch(function (e) {
expect(e.name).to.equal('ParseError')
expect(e.message).to.match(/illegal argument ""/)
expect(e.name).toBe('ParseError')
expect(e.message).toMatch(/illegal argument ""/)
})
})
@@ -55,8 +52,8 @@ describe('tags/render', function () {
'/parent.html': '{%render not-exist%}'
})
return liquid.renderFile('/parent.html').catch(function (e) {
expect(e.name).to.equal('RenderError')
expect(e.message).to.match(/illegal filename "undefined"/)
expect(e.name).toBe('RenderError')
expect(e.message).toMatch(/illegal filename "undefined"/)
})
})
@@ -66,7 +63,7 @@ describe('tags/render', function () {
'/foo/relative.html': 'bar{% render "../bar/foo.html" %}bar'
})
const html = await liquid.renderFile('foo/relative.html')
expect(html).to.equal('barfoobar')
expect(html).toBe('barfoobar')
})
it('should support render: hash list', async function () {
@@ -75,7 +72,7 @@ describe('tags/render', function () {
'/user.html': '{{role}} : {{alias}}'
})
const html = await liquid.renderFile('hash.html')
expect(html).to.equal('admin : harttle')
expect(html).toBe('admin : harttle')
})
it('should not bleed into child template', async function () {
@@ -84,7 +81,7 @@ describe('tags/render', function () {
'/user.html': 'InChild: {{name}}'
})
const html = await liquid.renderFile('hash.html')
expect(html).to.equal('InParent: harttle InChild: ')
expect(html).toBe('InParent: harttle InChild: ')
})
it('should allow argument reassignment', async function () {
@@ -94,7 +91,7 @@ describe('tags/render', function () {
})
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
const html = await staticLiquid.renderFile('parent.html')
return expect(html).to.equal('green')
return expect(html).toBe('green')
})
it('should be able to access globals', async function () {
@@ -104,7 +101,7 @@ describe('tags/render', function () {
'/user.html': 'InChild: {{name}}'
})
const html = await liquid.renderFile('hash', { name: 'harttle' })
expect(html).to.equal('InParent: harttle InChild: Harttle')
expect(html).toBe('InParent: harttle InChild: Harttle')
})
it('should support with', async function () {
@@ -113,7 +110,7 @@ describe('tags/render', function () {
'/color.html': 'color:{{color}}, shape:{{shape}}'
})
const html = await liquid.renderFile('with.html')
expect(html).to.equal('color:red, shape:rect')
expect(html).toBe('color:red, shape:rect')
})
it('should treat as normal key/value if followed by ":"', async () => {
mock({
@@ -121,7 +118,7 @@ describe('tags/render', function () {
'/color.html': 'color:{{color}}, with:{{with}}'
})
const html = await liquid.renderFile('with.html')
expect(html).to.equal('color:, with:foo')
expect(html).toBe('color:, with:foo')
})
it('should treat as normal key if with value not specified', async () => {
mock({
@@ -129,7 +126,7 @@ describe('tags/render', function () {
'/color.html': 'color:{{color}}, with:{{with}}, shape:{{shape}}'
})
const html = await liquid.renderFile('with.html')
expect(html).to.equal('color:, with:true, shape:rect')
expect(html).toBe('color:, with:true, shape:rect')
})
it('should support with...as', async function () {
mock({
@@ -137,7 +134,7 @@ describe('tags/render', function () {
'/color.html': 'color:{{c}}'
})
const html = await liquid.renderFile('with.html', { color: 'red' })
expect(html).to.equal('color:red')
expect(html).toBe('color:red')
})
it('should support with...as and other parameters', async function () {
mock({
@@ -146,7 +143,7 @@ describe('tags/render', function () {
})
const scope = { color: 'red', shape: 'rect' }
const html = await liquid.renderFile('index.html', scope)
expect(html).to.equal('color:red, shape:rect')
expect(html).toBe('color:red, shape:rect')
})
it('should support for...as', async function () {
mock({
@@ -154,7 +151,7 @@ describe('tags/render', function () {
'/item.html': '{{forloop.index}}: {{color}}\n'
})
const html = await liquid.renderFile('index.html', { colors: ['red', 'green'] })
expect(html).to.equal('1: red\n2: green\n')
expect(html).toBe('1: red\n2: green\n')
})
it('should support for <iterable> as', async function () {
class MockIterable {
@@ -168,7 +165,7 @@ describe('tags/render', function () {
'/item.html': '{{forloop.index}}: {{color}}\n'
})
const html = await liquid.renderFile('index.html', { colors: new MockIterable() })
expect(html).to.equal('1: red\n2: green\n')
expect(html).toBe('1: red\n2: green\n')
})
it('should support for <non-array> as', async function () {
mock({
@@ -176,7 +173,7 @@ describe('tags/render', function () {
'/item.html': '{{forloop.index}}: {{color}}\n'
})
const html = await liquid.renderFile('index.html')
expect(html).to.equal('1: green\n')
expect(html).toBe('1: green\n')
})
it('should support for without as', async function () {
mock({
@@ -184,7 +181,7 @@ describe('tags/render', function () {
'/item.html': '{{forloop.index}}: {{color}}\n'
})
const html = await liquid.renderFile('index.html', { colors: ['red', 'green'] })
expect(html).to.equal('1: \n2: \n')
expect(html).toBe('1: \n2: \n')
})
it('should support for...as with other parameters', async function () {
mock({
@@ -192,7 +189,7 @@ describe('tags/render', function () {
'/item.html': '{{forloop.index}}{{sep}}{{color}}{{tail}}'
})
const html = await liquid.renderFile('index.html', { colors: ['red', 'green'] })
expect(html).to.equal('1. red.\n2. green.\n')
expect(html).toBe('1. red.\n2. green.\n')
})
it('should support for...as with other parameters (comma separated)', async function () {
mock({
@@ -200,7 +197,7 @@ describe('tags/render', function () {
'/item.html': '{{forloop.index}}{{sep}}{{color}}{{tail}}'
})
const html = await liquid.renderFile('index.html', { colors: ['red', 'green'] })
expect(html).to.equal('1. red.\n2. green.\n')
expect(html).toBe('1. red.\n2. green.\n')
})
it('should support render: with as Drop', async function () {
class ColorDrop extends Drop {
@@ -213,7 +210,7 @@ describe('tags/render', function () {
'/color.html': 'color:{{color}}'
})
const html = await liquid.renderFile('with.html', { color: new ColorDrop() })
expect(html).to.equal('color:red!')
expect(html).toBe('color:red!')
})
it('should support render: with passed as Drop', async function () {
class ColorDrop extends Drop {
@@ -227,7 +224,7 @@ describe('tags/render', function () {
'/color.html': '{{color | name}}'
})
const html = await liquid.renderFile('with.html', { color: new ColorDrop() })
expect(html).to.equal('ColorDrop')
expect(html).toBe('ColorDrop')
})
it('should support nested renders', async function () {
@@ -246,7 +243,7 @@ describe('tags/render', function () {
}
}
const html = await liquid.renderFile('personInfo.html', ctx)
expect(html).to.equal('This is a person <p>Joe Shmoe<br/>City: Dallas</p>')
expect(html).toBe('This is a person <p>Joe Shmoe<br/>City: Dallas</p>')
})
it('should support relative reference', async function () {
mock({
@@ -255,7 +252,7 @@ describe('tags/render', function () {
})
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/foo' })
const html = await staticLiquid.renderFile('coo/parent.html')
expect(html).to.equal('Xchild with redY')
expect(html).toBe('Xchild with redY')
})
it('should disable relative reference if specified', () => {
mock({
@@ -263,7 +260,7 @@ describe('tags/render', function () {
'/foo/bar/child.html': 'child with {{color}}'
})
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/foo', relativeReference: false })
return expect(staticLiquid.renderFile('coo/parent.html')).to.be.rejectedWith(/Failed to lookup/)
return expect(staticLiquid.renderFile('coo/parent.html')).rejects.toThrow(/Failed to lookup/)
})
it('should throw not found if relative reference out of root', () => {
mock({
@@ -271,7 +268,7 @@ describe('tags/render', function () {
'/bar/child.html': 'child with {{color}}'
})
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/foo', partials: '/foo' })
return expect(staticLiquid.renderFile('parent.html')).to.be.rejectedWith(/Failed to lookup "..\/bar\/child.html"/)
return expect(staticLiquid.renderFile('parent.html')).rejects.toThrow(/Failed to lookup "..\/bar\/child.html"/)
})
describe('static partial', function () {
@@ -285,7 +282,7 @@ describe('tags/render', function () {
'/child.html': 'child with {{color}}'
})
const html = await staticLiquid.renderFile('parent.html')
expect(html).to.equal('Xchild with redY')
expect(html).toBe('Xchild with redY')
})
it('should support parent paths', async function () {
@@ -294,7 +291,7 @@ describe('tags/render', function () {
'/foo/child.html': 'child'
})
const html = await staticLiquid.renderFile('parent.html')
expect(html).to.equal('XchildY')
expect(html).toBe('XchildY')
})
it('should support subpaths', async function () {
@@ -303,7 +300,7 @@ describe('tags/render', function () {
'/foo/child.html': 'child'
})
const html = await staticLiquid.renderFile('parent.html')
expect(html).to.equal('XchildY')
expect(html).toBe('XchildY')
})
it('should support comma separated arguments', async function () {
@@ -312,7 +309,7 @@ describe('tags/render', function () {
'/child.html': 'child with {{color}}'
})
const html = await staticLiquid.renderFile('parent.html')
expect(html).to.equal('Xchild with redY')
expect(html).toBe('Xchild with redY')
})
it('should support template string', async function () {
@@ -321,7 +318,7 @@ describe('tags/render', function () {
'/bar/foo.html': 'foo'
})
const html = await staticLiquid.renderFile('/current.html', { name: 'foo.html' })
expect(html).to.equal('barfoobar')
expect(html).toBe('barfoobar')
})
it('should support filters in template string', async function () {
@@ -330,7 +327,7 @@ describe('tags/render', function () {
'/bar/foo.html': 'foo'
})
const html = await staticLiquid.renderFile('/current.html', { name: 'foo' })
expect(html).to.equal('barfoobar')
expect(html).toBe('barfoobar')
})
})
describe('sync support', function () {
@@ -340,7 +337,7 @@ describe('tags/render', function () {
'/bar/foo.html': 'foo'
})
const html = liquid.renderFileSync('/current.html')
expect(html).to.equal('barfoobar')
expect(html).toBe('barfoobar')
})
it('should support value string', function () {
mock({
@@ -348,7 +345,7 @@ describe('tags/render', function () {
'/bar/foo.html': 'foo'
})
const html = liquid.renderFileSync('/current.html', { name: '/bar/foo.html' })
expect(html).to.equal('barfoobar')
expect(html).toBe('barfoobar')
})
it('should support template string', function () {
mock({
@@ -356,7 +353,7 @@ describe('tags/render', function () {
'/bar/foo.html': 'foo'
})
const html = liquid.renderFileSync('/current.html', { name: '/foo.html' })
expect(html).to.equal('barfoobar')
expect(html).toBe('barfoobar')
})
it('should support with', function () {
mock({
@@ -364,7 +361,7 @@ describe('tags/render', function () {
'/color.html': 'color:{{color}}, shape:{{shape}}'
})
const html = liquid.renderFileSync('with.html')
expect(html).to.equal('color:red, shape:rect')
expect(html).toBe('color:red, shape:rect')
})
it('should support filename with extension', function () {
mock({
@@ -373,7 +370,7 @@ describe('tags/render', function () {
})
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
const html = staticLiquid.renderFileSync('parent.html')
expect(html).to.equal('Xchild with redY')
expect(html).toBe('Xchild with redY')
})
})
})
@@ -1,9 +1,5 @@
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import { Liquid } from '../../../src/liquid'
use(chaiAsPromised)
describe('tags/tablerow', function () {
const liquid = new Liquid()
@@ -11,7 +7,7 @@ describe('tags/tablerow', function () {
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 html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('should support promises', async function () {
@@ -21,7 +17,7 @@ describe('tags/tablerow', function () {
}
const dst = '<tr class="row1"><td class="col1">1</td><td class="col2">2</td><td class="col3">3</td></tr>'
const html = await liquid.parseAndRender(src, ctx)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('should support iterables', async function () {
@@ -38,7 +34,7 @@ describe('tags/tablerow', function () {
}
const dst = '<tr class="row1"><td class="col1">1</td><td class="col2">2</td><td class="col3">3</td></tr>'
const html = await liquid.parseAndRender(src, ctx)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('should support cols', async function () {
@@ -50,40 +46,40 @@ describe('tags/tablerow', function () {
'<tr class="row1"><td class="col1">a</td><td class="col2">b</td></tr>' +
'<tr class="row2"><td class="col1">c</td></tr>'
const html = await liquid.parseAndRender(src, ctx)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('should support cols set to 0', async function () {
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 html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('should support empty tablerow', async function () {
const src = '{% tablerow i in (1..0) cols:2 %}{{ i }}{% endtablerow %}'
const dst = ''
const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('should support empty array', async function () {
const src = '{% tablerow i in alpha.z cols:2 %}{{ i }}{% endtablerow %}'
const dst = ''
const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('should throw when tablerow not closed', function () {
const src = '{% tablerow i in (1..0) cols:2 %}{{ i }}'
return expect(liquid.parseAndRender(src))
.to.be.rejectedWith(/tag .* not closed/)
.rejects.toThrow(/tag .* not closed/)
})
it('should throw when x in y not found', function () {
const src = '{% tablerow i (1..3) %}{{ i }}'
return expect(liquid.parseAndRender(src))
.to.be.rejectedWith('illegal tag: {% tablerow i (1..3) %}, line:1, col:1')
.rejects.toThrow('illegal tag: {% tablerow i (1..3) %}, line:1, col:1')
})
it('should support tablerow with range', async function () {
@@ -93,7 +89,7 @@ describe('tags/tablerow', function () {
'<tr class="row2"><td class="col1">3</td><td class="col2">4</td></tr>' +
'<tr class="row3"><td class="col1">5</td></tr>'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('should support tablerow with limit', async function () {
@@ -102,45 +98,45 @@ describe('tags/tablerow', function () {
'<tr class="row1"><td class="col1">1</td><td class="col2">2</td></tr>' +
'<tr class="row2"><td class="col1">3</td></tr>'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('should support index0, index, rindex0, rindex', async function () {
const src = '{% tablerow i in (1..3)%}{{tablerowloop.index0}}{{tablerowloop.index}}{{tablerowloop.rindex0}}{{tablerowloop.rindex}}{% endtablerow %}'
const dst = '<tr class="row1"><td class="col1">0123</td><td class="col2">1212</td><td class="col3">2301</td></tr>'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('should support first, last, length', async function () {
const src = '{% tablerow i in (1..3)%}{{tablerowloop.first}} {{tablerowloop.last}} {{tablerowloop.length}}{% endtablerow %}'
const dst = '<tr class="row1"><td class="col1">true false 3</td><td class="col2">false false 3</td><td class="col3">false true 3</td></tr>'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('should support col, row, col0, col_first, col_last', async function () {
const src = '{% tablerow i in (1..3)%}{{tablerowloop.col}} {{tablerowloop.col0}} {{tablerowloop.col_first}} {{tablerowloop.col_last}}{% endtablerow %}'
const dst = '<tr class="row1"><td class="col1">1 0 true false</td><td class="col2">2 1 false false</td><td class="col3">3 2 false true</td></tr>'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
describe('offset', function () {
it('should support tablerow with offset', async function () {
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 html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('index should also start at 1', async function () {
const src = '{% tablerow i in (1..4) cols:2 offset:2 %}{{tablerowloop.index}}{% endtablerow %}'
const dst = '<tr class="row1"><td class="col1">1</td><td class="col2">2</td></tr>'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
it('col should also start at 1', async function () {
const src = '{% tablerow i in (1..4) cols:2 offset:3 %}{{tablerowloop.col}}{% endtablerow %}'
const dst = '<tr class="row1"><td class="col1">1</td></tr>'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
})
describe('sync support', function () {
@@ -148,13 +144,13 @@ describe('tags/tablerow', function () {
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 html = liquid.parseAndRenderSync(src)
expect(html).to.equal(dst)
expect(html).toBe(dst)
})
it('should support empty tablerow', function () {
const src = '{% tablerow i in "" cols:2 %}{{ i }}{% endtablerow %}'
const dst = ''
const html = liquid.parseAndRenderSync(src)
return expect(html).to.equal(dst)
return expect(html).toBe(dst)
})
})
})
@@ -1,43 +1,39 @@
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import { Liquid } from '../../../src/liquid'
use(chaiAsPromised)
describe('tags/unless', function () {
let liquid: Liquid
before(() => { liquid = new Liquid() })
beforeEach(() => { liquid = new Liquid() })
it('should render else when predicate yields true', async function () {
// 0 is truthy
const src = '{% unless 0 %}yes{%else%}no{%endunless%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('no')
return expect(html).toBe('no')
})
it('should support elsif', async function () {
const src = '{% unless true %}1{%elsif true%}2{%else%}3{%endunless%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('2')
return expect(html).toBe('2')
})
it('should render unless when predicate yields false', async function () {
const src = '{% unless false %}yes{%else%}no{%endunless%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('yes')
return expect(html).toBe('yes')
})
it('should reject when tag not closed', function () {
const src = '{% unless 1 > 2 %}yes'
return expect(liquid.parseAndRender(src))
.to.be.rejectedWith(/tag {% unless 1 > 2 %} not closed/)
.rejects.toThrow(/tag {% unless 1 > 2 %} not closed/)
})
it('should render unless when predicate yields false and else undefined', async function () {
const src = '{% unless 1 > 2 %}yes{%endunless%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('yes')
return expect(html).toBe('yes')
})
it('should render "" when predicate yields false and else undefined', async function () {
const src = '{% unless 1 < 2 %}yes{%endunless%}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('')
return expect(html).toBe('')
})
it('should output unless contents in order', async function () {
@@ -46,7 +42,7 @@ describe('tags/unless', function () {
{% unless false %}Inside {{ location }}{% endunless %}
After {{ location }}`
const html = await liquid.parseAndRender(src, { location: 'wonderland' })
expect(html).to.equal(`
expect(html).toBe(`
Before wonderland
Inside wonderland
After wonderland`)
@@ -56,12 +52,12 @@ describe('tags/unless', function () {
it('should render else when predicate yields true', function () {
const src = '{% unless 0 %}yes{%else%}no{%endunless%}'
const html = liquid.parseAndRenderSync(src)
expect(html).to.equal('no')
expect(html).toBe('no')
})
it('should render unless when predicate yields false', function () {
const src = '{% unless false %}yes{%else%}no{%endunless%}'
const html = liquid.parseAndRenderSync(src)
expect(html).to.equal('yes')
expect(html).toBe('yes')
})
})
})
@@ -1,11 +1,7 @@
import { expect, use } from 'chai'
import { RenderError } from '../../../src/util/error'
import { Liquid } from '../../../src/liquid'
import * as path from 'path'
import { mock, restore } from '../../stub/mockfs'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
let engine = new Liquid()
const strictEngine = new Liquid({
@@ -18,9 +14,10 @@ describe('error', function () {
describe('TokenizationError', function () {
it('should throw TokenizationError when tag illegal', async function () {
const err = await expect(engine.parseAndRender('{% . a %}', {})).be.rejected
expect(err.name).to.equal('TokenizationError')
expect(err.message).to.contain('illegal tag syntax')
await expect(engine.parseAndRender('{% . a %}', {})).rejects.toMatchObject({
name: 'TokenizationError',
message: expect.stringContaining('illegal tag syntax')
})
})
it('should contain template content in err.message', async function () {
const html = ['1st', '2nd', 'X{% . a %} Y', '4th']
@@ -31,33 +28,41 @@ describe('error', function () {
' 4| 4th',
'TokenizationError'
]
const err = await expect(engine.parseAndRender(html.join('\n'))).be.rejected
expect(err.message).to.equal('illegal tag syntax, line:3, col:2')
expect(err.stack).to.contain(message.join('\n'))
expect(err.name).to.equal('TokenizationError')
await expect(engine.parseAndRender(html.join('\n'))).rejects.toMatchObject({
message: 'illegal tag syntax, line:3, col:2',
stack: expect.stringContaining(message.join('\n')),
name: 'TokenizationError'
})
})
it('should contain the whole template content in err.token.input', async function () {
const html = 'bar\nfoo{% . a %}\nfoo'
const err = await expect(engine.parseAndRender(html)).be.rejected
expect(err.token.input).to.equal(html)
await expect(engine.parseAndRender(html)).rejects.toMatchObject({
token: expect.objectContaining({
input: html
})
})
})
it('should contain stack in err.stack', async function () {
const err = await expect(engine.parseAndRender('{% . a %}')).be.rejected
expect(err.message).to.contain('illegal tag syntax')
expect(err.stack).to.contain('at Liquid.parse')
await expect(engine.parseAndRender('{% . a %}')).rejects.toMatchObject({
message: expect.stringContaining('illegal tag syntax'),
stack: expect.stringContaining('at Liquid.parse')
})
})
describe('captureStackTrace compatibility', function () {
it('should be empty when captureStackTrace undefined', async function () {
const err = await expect(engine.parseAndRender('{% . a %}')).be.rejected
expect(err.stack).to.contain('illegal tag syntax')
expect(err.stack).to.not.contain('at Object.parse')
await expect(engine.parseAndRender('{% . a %}')).rejects.toMatchObject({
stack: expect.stringContaining('illegal tag syntax')
})
await expect(engine.parseAndRender('{% . a %}')).rejects.toMatchObject({
stack: expect.not.stringContaining('at Object.parse')
})
})
})
it('should throw error with [line, col] if tag unmatched', async function () {
const err = await expect(engine.parseAndRender('1\n2\nfoo{% assign a = 4 }\n4')).be.rejected
console.log(err.stack)
expect(err.name).to.equal('TokenizationError')
expect(err.message).to.equal('tag "{% assign a =..." not closed, line:3, col:4')
await expect(engine.parseAndRender('1\n2\nfoo{% assign a = 4 }\n4')).rejects.toMatchObject({
name: 'TokenizationError',
message: 'tag "{% assign a =..." not closed, line:3, col:4'
})
})
})
@@ -82,30 +87,34 @@ describe('error', function () {
})
it('should throw RenderError when tag throws', async function () {
const src = '{%throwingTag%}'
const err = await expect(engine.parseAndRender(src)).be.rejected
expect(err.name).to.equal('RenderError')
expect(err.message).to.contain('intended render error')
await expect(engine.parseAndRender(src)).rejects.toMatchObject({
name: 'RenderError',
message: expect.stringContaining('intended render error')
})
})
it('should throw RenderError when tag rejects', async function () {
const src = '{%rejectingTag%}'
const err = await expect(engine.parseAndRender(src)).be.rejected
expect(err.name).to.equal('RenderError')
expect(err.message).to.contain('intended render reject')
await expect(engine.parseAndRender(src)).rejects.toMatchObject({
name: 'RenderError',
message: expect.stringContaining('intended render reject')
})
})
it('should throw RenderError when filter throws', async function () {
const src = '{{1|throwingFilter}}'
const err = await expect(engine.parseAndRender(src)).be.rejected
expect(err.name).to.equal('RenderError')
expect(err.message).to.contain('throwed by filter')
await expect(engine.parseAndRender(src)).rejects.toMatchObject({
name: 'RenderError',
message: expect.stringContaining('throwed by filter')
})
})
it('should not throw when variable undefined by default', async function () {
const html = await engine.parseAndRender('X{{a}}Y')
return expect(html).to.equal('XY')
return expect(html).toBe('XY')
})
it('should throw RenderError when variable not defined', async function () {
const err = await expect(strictEngine.parseAndRender('{{a}}')).be.rejected
expect(err).to.have.property('name', 'RenderError')
expect(err.message).to.contain('undefined variable: a')
await expect(strictEngine.parseAndRender('{{a}}')).rejects.toMatchObject({
name: 'RenderError',
message: expect.stringContaining('undefined variable: a')
})
})
it('should contain template context in err.stack', async function () {
const html = ['1st', '2nd', '3rd', 'X{%throwingTag%} Y', '5th', '6th', '7th']
@@ -118,10 +127,11 @@ describe('error', function () {
' 7| 7th',
'RenderError'
]
const err = await expect(engine.parseAndRender(html.join('\n'))).be.rejected
expect(err.message).to.equal('intended render error, line:4, col:2')
expect(err.stack).to.contain(message.join('\n'))
expect(err.name).to.equal('RenderError')
await expect(engine.parseAndRender(html.join('\n'))).rejects.toMatchObject({
name: 'RenderError',
message: 'intended render error, line:4, col:2',
stack: expect.stringContaining(message.join('\n'))
})
})
it('should contain original error info for {% layout %}', async function () {
mock({
@@ -145,10 +155,11 @@ describe('error', function () {
' 7| 7th',
'RenderError'
]
const err = await expect(engine.parseAndRender(html)).be.rejected
expect(err.message).to.equal(`intended render error, file:${path.resolve('/throwing-tag.html')}, line:4, col:2`)
expect(err.stack).to.contain(message.join('\n'))
expect(err.name).to.equal('RenderError')
await expect(engine.parseAndRender(html)).rejects.toMatchObject({
name: 'RenderError',
message: `intended render error, file:${path.resolve('/throwing-tag.html')}, line:4, col:2`,
stack: expect.stringContaining(message.join('\n'))
})
})
it('should contain original error info for {% include %}', async function () {
const origin = ['1st', '2nd', '3rd', 'X{%throwingTag%} Y', '5th', '6th', '7th']
@@ -165,15 +176,17 @@ describe('error', function () {
' 7| 7th',
'RenderError'
]
const err = await expect(engine.parseAndRender(html)).be.rejected
expect(err.message).to.equal(`intended render error, file:${path.resolve('/throwing-tag.html')}, line:4, col:2`)
expect(err.stack).to.contain(message.join('\n'))
expect(err.name).to.equal('RenderError')
await expect(engine.parseAndRender(html)).rejects.toMatchObject({
name: 'RenderError',
message: `intended render error, file:${path.resolve('/throwing-tag.html')}, line:4, col:2`,
stack: expect.stringContaining(message.join('\n'))
})
})
it('should contain stack in err.stack', async function () {
const err = await expect(engine.parseAndRender('{%rejectingTag%}')).be.rejected
expect(err.message).to.contain('intended render reject')
expect(err.stack).to.match(/at .*:\d+:\d+/)
await expect(engine.parseAndRender('{%rejectingTag%}')).rejects.toMatchObject({
message: expect.stringContaining('intended render reject'),
stack: expect.stringMatching(/at .*:\d+:\d+/)
})
})
})
@@ -188,30 +201,35 @@ describe('error', function () {
})
})
it('should throw ParseError when filter not defined', async function () {
const err = await expect(strictEngine.parseAndRender('{{1 | a}}')).be.rejected
expect(err).to.have.property('name', 'ParseError')
expect(err.message).to.contain('undefined filter: a')
await expect(strictEngine.parseAndRender('{{1 | a}}')).rejects.toMatchObject({
name: 'ParseError',
message: expect.stringContaining('undefined filter: a')
})
})
it('should throw ParseError when tag not closed', async function () {
const err = await expect(engine.parseAndRender('{% if %}')).be.rejected
expect(err.name).to.equal('ParseError')
expect(err.message).to.contain('tag {% if %} not closed')
await expect(engine.parseAndRender('{% if %}')).rejects.toMatchObject({
name: 'ParseError',
message: expect.stringContaining('tag {% if %} not closed')
})
})
it('should throw ParseError when tag parse throws', async function () {
const err = await expect(engine.parseAndRender('{%throwsOnParse%}')).be.rejected
expect(err.name).to.equal('ParseError')
expect(err.message).to.contain('intended parse error')
await expect(engine.parseAndRender('{%throwsOnParse%}')).rejects.toMatchObject({
name: 'ParseError',
message: expect.stringContaining('intended parse error')
})
})
it('should throw ParseError when tag not found', async function () {
const src = '{%if true%}\naaa{%endif%}\n{% -a %}\n3'
const err = await expect(engine.parseAndRender(src)).be.rejected
expect(err.name).to.equal('ParseError')
expect(err.message).to.contain('tag "-a" not found')
await expect(engine.parseAndRender(src)).rejects.toMatchObject({
name: 'ParseError',
message: expect.stringContaining('tag "-a" not found')
})
})
it('should throw ParseError when tag not exist', async function () {
const err = await expect(engine.parseAndRender('{% a %}')).be.rejected
expect(err.name).to.equal('ParseError')
expect(err.message).to.contain('tag "a" not found')
await expect(engine.parseAndRender('{% a %}')).rejects.toMatchObject({
name: 'ParseError',
message: expect.stringContaining('tag "a" not found')
})
})
it('should contain template context in err.stack', async function () {
@@ -225,10 +243,11 @@ describe('error', function () {
' 7| 7th',
'ParseError: tag "a" not found'
]
const err = await expect(engine.parseAndRender(html.join('\n'))).be.rejected
expect(err.message).to.equal('tag "a" not found, line:4, col:2')
expect(err.stack).to.contain(message.join('\n'))
expect(err.name).to.equal('ParseError')
await expect(engine.parseAndRender(html.join('\n'))).rejects.toMatchObject({
name: 'ParseError',
message: 'tag "a" not found, line:4, col:2',
stack: expect.stringContaining(message.join('\n'))
})
})
it('should handle err.message when context not enough', async function () {
@@ -240,15 +259,19 @@ describe('error', function () {
' 4| 4th',
'ParseError: tag "a" not found'
]
const err = await expect(engine.parseAndRender(html.join('\n'))).be.rejected
expect(err.message).to.equal('tag "a" not found, line:2, col:2')
expect(err.stack).to.contain(message.join('\n'))
await expect(engine.parseAndRender(html.join('\n'))).rejects.toMatchObject({
message: 'tag "a" not found, line:2, col:2',
stack: expect.stringContaining(message.join('\n'))
})
})
it('should contain stack in err.stack', async function () {
const err = await expect(engine.parseAndRender('{% -a %}')).be.rejected
expect(err.stack).to.contain('ParseError: tag "-a" not found')
expect(err.stack).to.match(/at .*:\d+:\d+\)/)
await expect(engine.parseAndRender('{% -a %}')).rejects.toMatchObject({
stack: expect.stringContaining('ParseError: tag "-a" not found')
})
await expect(engine.parseAndRender('{% -a %}')).rejects.toMatchObject({
stack: expect.stringMatching(/at .*:\d+:\d+\)/)
})
})
})
describe('sync support', function () {
@@ -265,8 +288,8 @@ describe('error', function () {
})
it('should throw RenderError when tag throws', function () {
const src = '{%throwingTag%}'
expect(() => engine.parseAndRenderSync(src))
.to.throw(RenderError, /intended render error/)
expect(() => engine.parseAndRenderSync(src)).toThrow(RenderError)
expect(() => engine.parseAndRenderSync(src)).toThrow(/intended render error/)
})
it('should contain original error info for {% include %}', function () {
mock({
@@ -286,9 +309,9 @@ describe('error', function () {
engine.parseAndRenderSync(html)
throw new Error('expected throw')
} catch (err) {
expect(err.message).to.equal(`intended render error, file:${path.resolve('/throwing-tag.html')}, line:4, col:2`)
expect(err.stack).to.contain(message.join('\n'))
expect(err.name).to.equal('RenderError')
expect(err).toHaveProperty('name', 'RenderError')
expect(err).toHaveProperty('message', `intended render error, file:${path.resolve('/throwing-tag.html')}, line:4, col:2`)
expect(err).toHaveProperty('stack', expect.stringContaining(message.join('\n')))
}
})
})
+1 -1
View File
@@ -1,5 +1,5 @@
import { isString, forOwn } from '../../src/util/underscore'
import * as fs from '../../src/fs/node'
import * as fs from '../../src/fs/fs-impl'
import { resolve } from 'path'
interface FileDescriptor {
+2 -3
View File
@@ -1,6 +1,5 @@
import { Liquid } from '../../src/liquid'
import { LiquidOptions } from '../../src/liquid-options'
import { expect } from 'chai'
export const liquid = new Liquid()
@@ -15,6 +14,6 @@ export async function test (src: string, ctx: object | string, expected?: string
}
const engine = opts ? new Liquid(opts) : liquid
const result = await engine.parseAndRender(src, ctx as object)
if (expected instanceof RegExp) return expect(result).to.match(expected)
return expect(result).to.equal(expected)
if (expected instanceof RegExp) return expect(result).toMatch(expected)
return expect(result).toBe(expected)
}
@@ -1,8 +0,0 @@
import { expect } from 'chai'
import { StreamedEmitter } from '../../../src/emitters/streamed-emitter-browser'
describe('emitters/streamed-emitter-browser', () => {
it('should throw when try to constructing', () => {
expect(() => new StreamedEmitter()).to.throw(/streaming not supported/)
})
})
-29
View File
@@ -1,29 +0,0 @@
import { expect } from 'chai'
import { matchOperator } from '../../../src/parser/match-operator'
import { defaultOperators } from '../../../src'
import { createTrie } from '../../../src/util/operator-trie'
describe('parser/matchOperator()', function () {
const trie = createTrie(defaultOperators)
it('should match contains', () => {
expect(matchOperator('contains', 0, trie)).to.equal(8)
})
it('should match comparision', () => {
expect(matchOperator('>', 0, trie)).to.equal(1)
expect(matchOperator('>=', 0, trie)).to.equal(2)
expect(matchOperator('<', 0, trie)).to.equal(1)
expect(matchOperator('<=', 0, trie)).to.equal(2)
})
it('should match binary logic', () => {
expect(matchOperator('and', 0, trie)).to.equal(3)
expect(matchOperator('or', 0, trie)).to.equal(2)
})
it('should not match if word not terminate', () => {
expect(matchOperator('true1', 0, trie)).to.equal(-1)
expect(matchOperator('containsa', 0, trie)).to.equal(-1)
})
it('should match if word boundary found', () => {
expect(matchOperator('>=1', 0, trie)).to.equal(2)
expect(matchOperator('contains b', 0, trie)).to.equal(8)
})
})
-35
View File
@@ -1,35 +0,0 @@
import { expect } from 'chai'
import { parseStringLiteral } from '../../../src/parser/parse-string-literal'
describe('parseStringLiteral()', function () {
it('should parse octal escape', () => {
expect(parseStringLiteral(String.raw`"\1010"`)).to.equal('A0')
expect(parseStringLiteral(String.raw`"\12"`)).to.equal('\n')
expect(parseStringLiteral(String.raw`"\01"`)).to.equal('\u0001')
expect(parseStringLiteral(String.raw`"\0"`)).to.equal('\0')
})
it('should skip invalid octal escape', () => {
expect(parseStringLiteral(String.raw`"\9"`)).to.equal('9')
})
it('should parse \\n, \\t, \\r', () => {
expect(parseStringLiteral(String.raw`"fo\no"`)).to.equal('fo\no')
expect(parseStringLiteral(String.raw`'fo\to'`)).to.equal('fo\to')
expect(parseStringLiteral(String.raw`'fo\ro'`)).to.equal('fo\ro')
})
it('should parse unicode(hex) escape', () => {
expect(parseStringLiteral('"\\u003C"')).to.equal('<')
expect(parseStringLiteral('"\\u003cZ"')).to.equal('<Z')
expect(parseStringLiteral('"\\u41"')).to.equal('A')
})
it('should skip invalid unicode(hex) escape', () => {
expect(parseStringLiteral('"\\u41Z"')).to.equal('AZ')
expect(parseStringLiteral('"\\uZ"')).to.equal('\0Z')
})
it('should parse quote escape', () => {
expect(parseStringLiteral(String.raw`"fo\'o"`)).to.equal("fo'o")
expect(parseStringLiteral(String.raw`'fo\"o'`)).to.equal('fo"o')
})
it('should parse slash escape', () => {
expect(parseStringLiteral(String.raw`'fo\\o'`)).to.equal('fo\\o')
})
})
-555
View File
@@ -1,555 +0,0 @@
import { expect } from 'chai'
import { IdentifierToken } from '../../../src/tokens/identifier-token'
import { NumberToken } from '../../../src/tokens/number-token'
import { PropertyAccessToken } from '../../../src/tokens/property-access-token'
import { RangeToken } from '../../../src/tokens/range-token'
import { OperatorToken } from '../../../src/tokens/operator-token'
import { Tokenizer } from '../../../src/parser/tokenizer'
import { TagToken } from '../../../src/tokens/tag-token'
import { QuotedToken } from '../../../src/tokens/quoted-token'
import { OutputToken } from '../../../src/tokens/output-token'
import { HTMLToken } from '../../../src/tokens/html-token'
import { LiquidTagToken } from '../../../src/tokens/liquid-tag-token'
describe('Tokenizer', function () {
it('should read quoted', () => {
expect(new Tokenizer('"foo" ff').readQuoted()!.getText()).to.equal('"foo"')
expect(new Tokenizer(' "foo"ff').readQuoted()!.getText()).to.equal('"foo"')
})
it('should read value', () => {
expect(new Tokenizer('a[ b][ "c d" ]').readValueOrThrow().getText()).to.equal('a[ b][ "c d" ]')
expect(new Tokenizer('a.b[c[d.e]]').readValueOrThrow().getText()).to.equal('a.b[c[d.e]]')
})
it('should read identifier', () => {
expect(new Tokenizer('foo bar').readIdentifier()).to.haveOwnProperty('content', 'foo')
expect(new Tokenizer('foo bar').readWord()).to.haveOwnProperty('content', 'foo')
})
it('should read number value', () => {
const token: NumberToken = new Tokenizer('2.33.2').readValueOrThrow() as any
expect(token).to.be.instanceOf(NumberToken)
expect(token.whole.getText()).to.equal('2')
expect(token.decimal!.getText()).to.equal('33')
expect(token.getText()).to.equal('2.33')
})
it('should read quoted value', () => {
const value = new Tokenizer('"foo"a').readValue()
expect(value).to.be.instanceOf(QuotedToken)
expect(value!.getText()).to.equal('"foo"')
})
it('should read property access value', () => {
expect(new Tokenizer('a[b]["c d"]').readValueOrThrow().getText()).to.equal('a[b]["c d"]')
})
it('should read quoted property access value', () => {
const value = new Tokenizer('["a prop"]').readValue()
expect(value).to.be.instanceOf(PropertyAccessToken)
expect((value as PropertyAccessToken).variable.getText()).to.equal('"a prop"')
})
it('should throw for broken quoted property access', () => {
const tokenizer = new Tokenizer('[5]')
expect(() => tokenizer.readValueOrThrow()).to.throw()
})
it('should throw for incomplete quoted property access', () => {
const tokenizer = new Tokenizer('["a prop"')
expect(() => tokenizer.readValueOrThrow()).to.throw()
})
it('should read hash', () => {
const hash1 = new Tokenizer('foo: 3').readHash()
expect(hash1!.name.content).to.equal('foo')
expect(hash1!.value!.getText()).to.equal('3')
const hash2 = new Tokenizer(', foo: a[ "bar"]').readHash()
expect(hash2!.name.content).to.equal('foo')
expect(hash2!.value!.getText()).to.equal('a[ "bar"]')
})
it('should read multiple hashs', () => {
const hashes = new Tokenizer(', limit: 3 reverse offset:off').readHashes()
expect(hashes).to.have.lengthOf(3)
const [limit, reverse, offset] = hashes
expect(limit.name.content).to.equal('limit')
expect(limit.value!.getText()).to.equal('3')
expect(reverse.name.content).to.equal('reverse')
expect(reverse.value).to.be.undefined
expect(offset.name.content).to.equal('offset')
expect(offset.value!.getText()).to.equal('off')
})
it('should read hash value with property access', () => {
const hashes = new Tokenizer('cols: 2, rows: data["rows"]').readHashes()
expect(hashes).to.have.lengthOf(2)
const [cols, rols] = hashes
expect(cols.name.content).to.equal('cols')
expect(cols.value!.getText()).to.equal('2')
expect(rols.name.content).to.equal('rows')
expect(rols.value!.getText()).to.equal('data["rows"]')
})
describe('#readTopLevelTokens()', () => {
it('should read HTML token', function () {
const html = '<html><body><p>Lorem Ipsum</p></body></html>'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(1)
expect(tokens[0]).instanceOf(HTMLToken)
expect((tokens[0] as HTMLToken).getContent()).to.equal(html)
})
it('should read tag token', function () {
const html = '<p>{% for p in a[1]%}</p>'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(3)
const tag = tokens[1] as TagToken
expect(tag).instanceOf(TagToken)
expect(tag.name).to.equal('for')
expect(tag.args).to.equal('p in a[1]')
})
it('should allow unclosed tag inside {% raw %}', function () {
const html = '{%raw%} {%if%} {%else {%endraw%}'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(3)
expect(tokens[0]).to.haveOwnProperty('name', 'raw')
expect((tokens[1] as any).getContent()).to.equal(' {%if%} {%else ')
})
it('should allow unclosed endraw tag inside {% raw %}', function () {
const html = '{%raw%} {%endraw {%raw%} {%endraw%}'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(3)
expect(tokens[0]).to.haveOwnProperty('name', 'raw')
expect((tokens[1] as any).getContent()).to.equal(' {%endraw {%raw%} ')
})
it('should throw when {% raw %} not closed', function () {
const html = '{%raw%} {%endraw {%raw%}'
const tokenizer = new Tokenizer(html)
expect(() => tokenizer.readTopLevelTokens()).to.throw('raw "{%raw%} {%end..." not closed, line:1, col:8')
})
it('should read output token', function () {
const html = '<p>{{foo | date: "%Y-%m-%d"}}</p>'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(3)
const output = tokens[1] as OutputToken
expect(output).instanceOf(OutputToken)
expect(output.content).to.equal('foo | date: "%Y-%m-%d"')
})
it('should handle consecutive value and tags', function () {
const html = '{{foo}}{{bar}}{%foo%}{%bar%}'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(4)
const o1 = tokens[0] as OutputToken
const o2 = tokens[1] as OutputToken
const t1 = tokens[2] as TagToken
const t2 = tokens[3] as TagToken
expect(o1).instanceOf(OutputToken)
expect(o2).instanceOf(OutputToken)
expect(t1).instanceOf(TagToken)
expect(t2).instanceOf(TagToken)
expect(o1.content).to.equal('foo')
expect(o2.content).to.equal('bar')
expect(t1.name).to.equal('foo')
expect(t1.args).to.equal('')
expect(t2.name).to.equal('bar')
expect(t2.args).to.equal('')
})
it('should keep white spaces and newlines', function () {
const html = '{%foo%}\n{%bar %} \n {%alice%}'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(5)
expect(tokens[1]).instanceOf(HTMLToken)
expect(tokens[1].getText()).to.equal('\n')
expect(tokens[3]).instanceOf(HTMLToken)
expect(tokens[3].getText()).to.equal(' \n ')
})
it('should handle multiple lines tag', function () {
const html = '{%foo\na:a\nb:1.23\n%}'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(1)
expect(tokens[0]).instanceOf(TagToken)
expect((tokens[0] as TagToken).args).to.equal('a:a\nb:1.23')
expect(tokens[0].getText()).to.equal('{%foo\na:a\nb:1.23\n%}')
})
it('should handle multiple lines value', function () {
const html = '{{foo\n|date:\n"%Y-%m-%d"\n}}'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(1)
expect(tokens[0]).instanceOf(OutputToken)
expect(tokens[0].getText()).to.equal('{{foo\n|date:\n"%Y-%m-%d"\n}}')
})
it('should handle complex object property access', function () {
const html = '{{ obj["my:property with anything"] }}'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(1)
const output = tokens[0] as OutputToken
expect(output).instanceOf(OutputToken)
expect(output.content).to.equal('obj["my:property with anything"]')
})
it('should throw if tag not closed', function () {
const html = '{% assign foo = bar {{foo}}'
const tokenizer = new Tokenizer(html)
expect(() => tokenizer.readTopLevelTokens()).to.throw(/tag "{% assign foo..." not closed/)
})
it('should throw if output not closed', function () {
const tokenizer = new Tokenizer('{{name}')
expect(() => tokenizer.readTopLevelTokens()).to.throw(/output "{{name}" not closed/)
})
})
describe('#readTagToken()', () => {
it('should skip quoted delimiters', function () {
const html = '{% assign a = "%} {% }} {{" %}'
const tokenizer = new Tokenizer(html)
const token = tokenizer.readTagToken()
expect(token).instanceOf(TagToken)
expect(token.name).to.equal('assign')
expect(token.args).to.equal('a = "%} {% }} {{"')
})
})
describe('#readOutputToken()', () => {
it('should skip quoted delimiters', function () {
const html = '{{ "%} {%" | append: "}} {{" }}'
const tokenizer = new Tokenizer(html)
const token = tokenizer.readOutputToken()
expect(token).instanceOf(OutputToken)
expect(token.content).to.equal('"%} {%" | append: "}} {{"')
})
})
describe('#readRange()', () => {
it('should read `(1..3)`', () => {
const range = new Tokenizer('(1..3)').readRange()
expect(range).to.be.instanceOf(RangeToken)
expect(range!.getText()).to.deep.equal('(1..3)')
const { lhs, rhs } = range!
expect(lhs).to.be.instanceOf(NumberToken)
expect(lhs.getText()).to.equal('1')
expect(rhs).to.be.instanceOf(NumberToken)
expect(rhs.getText()).to.equal('3')
})
it('should throw for `(..3)`', () => {
expect(() => new Tokenizer('(..3)').readRange()).to.throw('unexpected token "..3)", value expected')
})
it('should read `(a.b..c["..d"])`', () => {
const range = new Tokenizer('(a.b..c["..d"])').readRange()
expect(range).to.be.instanceOf(RangeToken)
expect(range!.getText()).to.deep.equal('(a.b..c["..d"])')
})
})
describe('#readFilter()', () => {
it('should read a simple filter', function () {
const tokenizer = new Tokenizer('| plus')
const token = tokenizer.readFilter()
expect(token).to.have.property('name', 'plus')
expect(token).to.have.property('args').to.deep.equal([])
})
it('should read a filter with argument', function () {
const tokenizer = new Tokenizer(' | plus: 1')
const token = tokenizer.readFilter()
expect(token).to.have.property('name', 'plus')
expect(token!.args).to.have.lengthOf(1)
const one: NumberToken = token!.args[0] as any
expect(one).to.be.instanceOf(NumberToken)
expect(one.getText()).to.equal('1')
})
it('should read a filter with colon but no argument', function () {
const tokenizer = new Tokenizer('| plus:')
const token = tokenizer.readFilter()
expect(token).to.have.property('name', 'plus')
expect(token).to.have.property('args').to.deep.equal([])
})
it('should read null if name not found', function () {
const tokenizer = new Tokenizer('|')
const token = tokenizer.readFilter()
expect(token).to.be.null
})
it('should read a filter with k/v argument', function () {
const tokenizer = new Tokenizer(' | plus: a:1')
const token = tokenizer.readFilter()
expect(token).to.have.property('name', 'plus')
expect(token!.args).to.have.lengthOf(1)
const [k, v]: [string, NumberToken] = token!.args[0] as any
expect(k).to.equal('a')
expect(v).to.be.instanceOf(NumberToken)
expect(v.getText()).to.equal('1')
})
it('should read a filter with "arr[0]" argument', function () {
const tokenizer = new Tokenizer('| plus: arr[0]')
const token = tokenizer.readFilter()
expect(token).to.have.property('name', 'plus')
expect(token!.args).to.have.lengthOf(1)
const pa: PropertyAccessToken = token!.args[0] as any
expect(token!.args[0]).to.be.instanceOf(PropertyAccessToken)
expect((pa.variable as any).content).to.equal('arr')
expect(pa.props).to.have.lengthOf(1)
expect(pa.props[0]).to.be.instanceOf(NumberToken)
expect(pa.props[0].getText()).to.equal('0')
})
it('should read a filter with obj.foo argument', function () {
const tokenizer = new Tokenizer('| plus: obj.foo')
const token = tokenizer.readFilter()
expect(token).to.have.property('name', 'plus')
expect(token!.args).to.have.lengthOf(1)
const pa: PropertyAccessToken = token!.args[0] as any
expect(token!.args[0]).to.be.instanceOf(PropertyAccessToken)
expect((pa.variable as any).content).to.equal('obj')
expect(pa.props).to.have.lengthOf(1)
expect(pa.props[0]).to.be.instanceOf(IdentifierToken)
expect(pa.props[0].getText()).to.equal('foo')
})
it('should read a filter with obj["foo"] argument', function () {
const tokenizer = new Tokenizer('| plus: obj["good luck"]')
const token = tokenizer.readFilter()
expect(token).to.have.property('name', 'plus')
expect(token!.args).to.have.lengthOf(1)
const pa: PropertyAccessToken = token!.args[0] as any
expect(token!.args[0]).to.be.instanceOf(PropertyAccessToken)
expect(pa.getText()).to.equal('obj["good luck"]')
expect((pa.variable as any).content).to.equal('obj')
expect(pa.props[0].getText()).to.equal('"good luck"')
})
})
describe('#readFilters()', () => {
it('should read simple filters', function () {
const tokenizer = new Tokenizer('| plus: 3 | capitalize')
const tokens = tokenizer.readFilters()
expect(tokens).to.have.lengthOf(2)
expect(tokens[0]).to.have.property('name', 'plus')
expect(tokens[0].args).to.have.lengthOf(1)
expect(tokens[0].args[0]).to.be.instanceOf(NumberToken)
expect((tokens[0].args[0] as any).getText()).to.equal('3')
expect(tokens[1]).to.have.property('name', 'capitalize')
expect(tokens[1].args).to.have.lengthOf(0)
})
it('should read filters', function () {
const tokenizer = new Tokenizer('| plus: a:3 | capitalize | append: foo[a.b["c d"]]')
const tokens = tokenizer.readFilters()
expect(tokens).to.have.lengthOf(3)
expect(tokens[0]).to.have.property('name', 'plus')
expect(tokens[0].args).to.have.lengthOf(1)
const [k, v]: [string, NumberToken] = tokens[0].args[0] as any
expect(k).to.equal('a')
expect(v).to.be.instanceOf(NumberToken)
expect(v.getText()).to.equal('3')
expect(tokens[1]).to.have.property('name', 'capitalize')
expect(tokens[1].args).to.have.lengthOf(0)
expect(tokens[2]).to.have.property('name', 'append')
expect(tokens[2].args).to.have.lengthOf(1)
expect(tokens[2].args[0]).to.be.instanceOf(PropertyAccessToken)
expect((tokens[2].args[0] as any).getText()).to.equal('foo[a.b["c d"]]')
expect((tokens[2].args[0] as any).props[0].getText()).to.equal('a.b["c d"]')
})
})
describe('#readExpression()', () => {
it('should read expression `a `', () => {
const exp = [...new Tokenizer('a ').readExpressionTokens()]
expect(exp).to.have.lengthOf(1)
expect(exp[0]).to.be.instanceOf(PropertyAccessToken)
expect(exp[0].getText()).to.deep.equal('a')
})
it('should read expression `a[][b]`', () => {
const exp = [...new Tokenizer('a[][b]').readExpressionTokens()]
expect(exp).to.have.lengthOf(1)
const pa = exp[0] as PropertyAccessToken
expect(pa).to.be.instanceOf(PropertyAccessToken)
expect((pa.variable as any).content).to.deep.equal('a')
expect(pa.props).to.have.lengthOf(2)
const [p1, p2] = pa.props
expect(p1).to.be.instanceOf(IdentifierToken)
expect(p1.getText()).to.equal('')
expect(p2).to.be.instanceOf(PropertyAccessToken)
expect(p2.getText()).to.equal('b')
})
it('should read expression `a.`', () => {
const exp = [...new Tokenizer('a.').readExpressionTokens()]
expect(exp).to.have.lengthOf(1)
const pa = exp[0] as PropertyAccessToken
expect(pa).to.be.instanceOf(PropertyAccessToken)
expect((pa.variable as any).content).to.deep.equal('a')
expect(pa.props).to.have.lengthOf(0)
})
it('should read expression `a ==`', () => {
const exp = [...new Tokenizer('a ==').readExpressionTokens()]
expect(exp).to.have.lengthOf(2)
expect(exp[0]).to.be.instanceOf(PropertyAccessToken)
expect(exp[0].getText()).to.deep.equal('a')
expect(exp[1]).to.be.instanceOf(OperatorToken)
expect(exp[1].getText()).to.deep.equal('==')
})
it('should read expression `a==b`', () => {
const exp = new Tokenizer('a==b').readExpressionTokens()
const [a, equals, b] = exp
expect(a).to.be.instanceOf(PropertyAccessToken)
expect(a.getText()).to.deep.equal('a')
expect(equals).to.be.instanceOf(OperatorToken)
expect(equals.getText()).to.equal('==')
expect(b).to.be.instanceOf(PropertyAccessToken)
expect(b.getText()).to.deep.equal('b')
})
it('should read expression `^`', () => {
const exp = new Tokenizer('^').readExpressionTokens()
expect([...exp]).to.deep.equal([])
})
it('should read expression `a == b`', () => {
const exp = new Tokenizer('a == b').readExpressionTokens()
const [a, equals, b] = exp
expect(a).to.be.instanceOf(PropertyAccessToken)
expect(a.getText()).to.deep.equal('a')
expect(equals).to.be.instanceOf(OperatorToken)
expect(equals.getText()).to.equal('==')
expect(b).to.be.instanceOf(PropertyAccessToken)
expect(b.getText()).to.deep.equal('b')
})
it('should read expression `(1..3) contains 3`', () => {
const exp = new Tokenizer('(1..3) contains 3').readExpressionTokens()
const [range, contains, rhs] = exp
expect(range).to.be.instanceOf(RangeToken)
expect(range.getText()).to.deep.equal('(1..3)')
expect(contains).to.be.instanceOf(OperatorToken)
expect(contains.getText()).to.equal('contains')
expect(rhs).to.be.instanceOf(NumberToken)
expect(rhs.getText()).to.deep.equal('3')
})
it('should read expression `a[b] == c`', () => {
const exp = new Tokenizer('a[b] == c').readExpressionTokens()
const [lhs, contains, rhs] = exp
expect(lhs).to.be.instanceOf(PropertyAccessToken)
expect(lhs.getText()).to.deep.equal('a[b]')
expect(contains).to.be.instanceOf(OperatorToken)
expect(contains.getText()).to.equal('==')
expect(rhs).to.be.instanceOf(PropertyAccessToken)
expect(rhs.getText()).to.deep.equal('c')
})
it('should read expression `c[a["b"]] >= c`', () => {
const exp = new Tokenizer('c[a["b"]] >= c').readExpressionTokens()
const [lhs, op, rhs] = exp
expect(lhs).to.be.instanceOf(PropertyAccessToken)
expect(lhs.getText()).to.deep.equal('c[a["b"]]')
expect(op).to.be.instanceOf(OperatorToken)
expect(op.getText()).to.equal('>=')
expect(rhs).to.be.instanceOf(PropertyAccessToken)
expect(rhs.getText()).to.deep.equal('c')
})
it('should read expression `"][" == var`', () => {
const exp = new Tokenizer('"][" == var').readExpressionTokens()
const [lhs, equals, rhs] = exp
expect(lhs).to.be.instanceOf(QuotedToken)
expect(lhs.getText()).to.deep.equal('"]["')
expect(equals).to.be.instanceOf(OperatorToken)
expect(equals.getText()).to.equal('==')
expect(rhs).to.be.instanceOf(PropertyAccessToken)
expect(rhs.getText()).to.deep.equal('var')
})
it('should read expression `"\\\'" == "\\""`', () => {
const exp = new Tokenizer('"\\\'" == "\\""').readExpressionTokens()
const [lhs, equals, rhs] = exp
expect(lhs).to.be.instanceOf(QuotedToken)
expect(lhs.getText()).to.deep.equal('"\\\'"')
expect(equals).to.be.instanceOf(OperatorToken)
expect(equals.getText()).to.equal('==')
expect(rhs).to.be.instanceOf(QuotedToken)
expect(rhs.getText()).to.deep.equal('"\\""')
})
})
describe('#readLiquidTagTokens', () => {
it('should read newline terminated tokens', () => {
const tokenizer = new Tokenizer('echo \'hello\'')
const tokens = tokenizer.readLiquidTagTokens()
expect(tokens.length).to.equal(1)
const tag = tokens[0]
expect(tag).instanceOf(LiquidTagToken)
expect(tag.name).to.equal('echo')
expect(tag.args).to.equal('\'hello\'')
})
it('should gracefully handle empty lines', () => {
const tokenizer = new Tokenizer(`
echo 'hello'
decrement foo
`)
const tokens = tokenizer.readLiquidTagTokens()
expect(tokens.length).to.equal(2)
})
it('should throw if line does not start with an identifier', () => {
const tokenizer = new Tokenizer('!')
expect(() => tokenizer.readLiquidTagTokens()).to.throw(/illegal liquid tag syntax/)
})
})
describe('#read inline comment tags', () => {
it('should allow hash characters in tag names', () => {
const tokenizer = new Tokenizer('{% # some comment %}')
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(1)
const tag = tokens[0] as TagToken
expect(tag).instanceOf(TagToken)
expect(tag.name).to.equal('#')
expect(tag.args).to.equal('some comment')
})
it('should handle leading whitespace', () => {
const tokenizer = new Tokenizer('{%\n # some comment %}')
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(1)
const tag = tokens[0] as TagToken
expect(tag).instanceOf(TagToken)
expect(tag.name).to.equal('#')
expect(tag.args).to.equal('some comment')
})
it('should handle no trailing whitespace', () => {
const tokenizer = new Tokenizer('{%\n #some comment %}')
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(1)
const tag = tokens[0] as TagToken
expect(tag).instanceOf(TagToken)
expect(tag.name).to.equal('#')
expect(tag.args).to.equal('some comment')
})
})
})
-95
View File
@@ -1,95 +0,0 @@
import { isTruthy, isFalsy } from '../../../src/render/boolean'
import { Context } from '../../../src/context/context'
import { expect } from 'chai'
describe('boolean Shopify', async function () {
describe('.isTruthy()', async function () {
const ctx = {
opts: {
jsTruthy: false
}
} as unknown as Context
//
// Spec: https://shopify.github.io/liquid/basics/truthy-and-falsy/
it('true is truthy', function () {
expect(isTruthy(true, ctx)).to.be.true
})
it('false is falsy', function () {
expect(isTruthy(false, ctx)).to.be.false
})
it('null is falsy', function () {
expect(isTruthy(null, ctx)).to.be.false
})
it('"foo" is truthy', function () {
expect(isTruthy('foo', ctx)).to.be.true
})
it('"" is truthy', function () {
expect(isTruthy('', ctx)).to.be.true
})
it('0 is truthy', function () {
expect(isTruthy(0, ctx)).to.be.true
})
it('1 is truthy', function () {
expect(isTruthy(1, ctx)).to.be.true
})
it('1.1 is truthy', function () {
expect(isTruthy(1.1, ctx)).to.be.true
})
it('[1] is truthy', function () {
expect(isTruthy([1], ctx)).to.be.true
})
it('[] is truthy', function () {
expect(isTruthy([], ctx)).to.be.true
})
})
})
describe('boolean jsTruthy', async function () {
const ctx = {
opts: {
jsTruthy: true
}
} as unknown as Context
describe('.isFalsy()', async function () {
it('null is always falsy', function () {
expect(isFalsy(null, ctx)).to.be.true
})
})
describe('.isTruthy()', async function () {
it('true is truthy', function () {
expect(isTruthy(true, ctx)).to.be.true
})
it('false is falsy', function () {
expect(isTruthy(false, ctx)).to.be.false
})
it('null is always falsy', function () {
expect(isTruthy(null, ctx)).to.be.false
})
it('null is always falsy', function () {
expect(isTruthy(null, ctx)).to.be.false
})
it('"foo" is truthy', function () {
expect(isTruthy('foo', ctx)).to.be.true
})
it('"" is falsy', function () {
expect(isTruthy('', ctx)).to.be.false
})
it('0 is falsy', function () {
expect(isTruthy(0, ctx)).to.be.false
})
it('1 is truthy', function () {
expect(isTruthy(1, ctx)).to.be.true
})
it('1.1 is truthy', function () {
expect(isTruthy(1.1, ctx)).to.be.true
})
it('[1] is truthy', function () {
expect(isTruthy([1], ctx)).to.be.true
})
it('[] is truthy', function () {
expect(isTruthy([], ctx)).to.be.true
})
})
})
-44
View File
@@ -1,44 +0,0 @@
import * as chai from 'chai'
import { Liquid } from '../../../src/liquid'
import { QuotedToken } from '../../../src/tokens/quoted-token'
import { toPromise } from '../../../src/util/async'
import * as sinonChai from 'sinon-chai'
import * as sinon from 'sinon'
import { Context } from '../../../src/context/context'
import { Value } from '../../../src/template/value'
chai.use(sinonChai)
const expect = chai.expect
describe('Value', function () {
const liquid = new Liquid()
describe('#constructor()', function () {
it('should parse filters in value content', function () {
const f = new Value('o | foo: a: "a"', liquid)
expect(f.filters[0].name).to.equal('foo')
expect(f.filters[0].args).to.have.lengthOf(1)
const [k, v] = f.filters[0].args[0] as any
expect(k).to.equal('a')
expect(v).to.be.instanceOf(QuotedToken)
expect((v as QuotedToken).getText()).to.equal('"a"')
})
})
describe('#value()', function () {
it('should call chained filters correctly', async function () {
const date = sinon.stub().returns('y')
const time = sinon.spy()
liquid.registerFilter('date', date)
liquid.registerFilter('time', time)
const tpl = new Value('foo.bar | date: "b" | time:2', liquid)
const scope = new Context({
foo: { bar: 'bar' }
})
await toPromise(tpl.value(scope, false))
expect(date).to.have.been.calledWith('bar', 'b')
expect(time).to.have.been.calledWith('y', 2)
})
})
})
-21
View File
@@ -1,21 +0,0 @@
import * as chai from 'chai'
import * as sinonChai from 'sinon-chai'
import { PropertyAccessToken } from '../../../src/tokens/property-access-token'
import { QuotedToken } from '../../../src/tokens/quoted-token'
import { IdentifierToken } from '../../../src/tokens/identifier-token'
chai.use(sinonChai)
const expect = chai.expect
describe('PropertyAccessToken', function () {
describe('#propertyName', function () {
it('should return correct value for IdentifierToken', function () {
const token = new PropertyAccessToken(new IdentifierToken('foo', 0, 3), [], 3)
expect(token.propertyName).to.equal('foo')
})
it('should return correct value for QuotedToken', function () {
const token = new PropertyAccessToken(new QuotedToken('"foo bar"', 0, 9), [], 9)
expect(token.propertyName).to.equal('foo bar')
})
})
})