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'
]
}
+6287 -24387
View File
File diff suppressed because it is too large Load Diff
+10 -16
View File
@@ -16,8 +16,8 @@
"scripts": { "scripts": {
"lint": "eslint \"**/*.mjs\" \"**/*.ts\" .", "lint": "eslint \"**/*.mjs\" \"**/*.ts\" .",
"check": "npm run build && npm test && npm run lint && npm run perf:diff", "check": "npm run build && npm test && npm run lint && npm run perf:diff",
"test": "nyc mocha \"test/**/*.ts\"", "test": "jest --coverage",
"test:e2e": "mocha \"test/e2e/**/*.ts\"", "test:e2e": "jest test/e2e",
"perf": "cd benchmark && npm ci && npm start", "perf": "cd benchmark && npm ci && npm start",
"perf:diff": "bin/perf-diff.sh", "perf:diff": "bin/perf-diff.sh",
"perf:engines": "cd benchmark && npm run engines", "perf:engines": "cd benchmark && npm run engines",
@@ -63,23 +63,18 @@
"@semantic-release/changelog": "^3.0.2", "@semantic-release/changelog": "^3.0.2",
"@semantic-release/commit-analyzer": "^6.1.0", "@semantic-release/commit-analyzer": "^6.1.0",
"@semantic-release/git": "^7.0.8", "@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", "@semantic-release/release-notes-generator": "^7.1.4",
"@types/benchmark": "^1.0.31", "@types/benchmark": "^1.0.31",
"@types/chai": "^4.1.7",
"@types/chai-as-promised": "^7.1.0",
"@types/express": "^4.17.2", "@types/express": "^4.17.2",
"@types/jest": "^29.4.0",
"@types/jsdom": "^12.2.2", "@types/jsdom": "^12.2.2",
"@types/mocha": "^5.2.6", "@types/sinon": "^10.0.13",
"@types/sinon": "^7.0.6",
"@types/sinon-chai": "^3.2.2",
"@types/supertest": "^2.0.7", "@types/supertest": "^2.0.7",
"@typescript-eslint/eslint-plugin": "^5.6.0", "@typescript-eslint/eslint-plugin": "^5.6.0",
"@typescript-eslint/parser": "^5.6.0", "@typescript-eslint/parser": "^5.6.0",
"all-contributors-cli": "^6.24.0", "all-contributors-cli": "^6.24.0",
"benchmark": "^2.1.4", "benchmark": "^2.1.4",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"coveralls": "^3.0.2", "coveralls": "^3.0.2",
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"eslint": "^7.32.0", "eslint": "^7.32.0",
@@ -92,22 +87,21 @@
"eslint-plugin-standard": "^4.0.0", "eslint-plugin-standard": "^4.0.0",
"express": "^4.16.4", "express": "^4.16.4",
"husky": "^4.2.5", "husky": "^4.2.5",
"jest": "^29.5.0",
"jsdom": "^16.5.0", "jsdom": "^16.5.0",
"mocha": "^9.0.1",
"nyc": "^15.1.0",
"regenerator-runtime": "^0.12.1", "regenerator-runtime": "^0.12.1",
"rollup": "^2.61.0", "rollup": "^2.61.0",
"rollup-plugin-replace": "^2.1.0", "rollup-plugin-replace": "^2.1.0",
"rollup-plugin-typescript2": "^0.31.1", "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", "rollup-plugin-version-injector": "^1.3.3",
"semantic-release": "^19.0.3", "semantic-release": "^19.0.3",
"sinon": "^7.5.0", "sinon": "^15.0.2",
"sinon-chai": "^3.3.0",
"supertest": "^3.4.2", "supertest": "^3.4.2",
"ts-jest": "^29.0.5",
"ts-node": "^8.0.2", "ts-node": "^8.0.2",
"tslib": "^2.3.1", "tslib": "^2.3.1",
"typedoc": "^0.19.2", "typedoc": "^0.23.26",
"typedoc-plugin-markdown": "^2.2.17", "typedoc-plugin-markdown": "^2.2.17",
"typescript": "^4.5.3" "typescript": "^4.5.3"
}, },
+3 -3
View File
@@ -42,17 +42,17 @@ const input = './src/index.ts'
const browserFS = { const browserFS = {
include: './src/liquid-options.ts', include: './src/liquid-options.ts',
delimiters: ['', ''], delimiters: ['', ''],
'./fs/node': './fs/browser' './fs/fs-impl': './build/fs-impl-browser'
} }
const browserStream = { const browserStream = {
include: './src/emitters/index.ts', include: './src/emitters/index.ts',
delimiters: ['', ''], delimiters: ['', ''],
'./streamed-emitter': './streamed-emitter-browser' './streamed-emitter': '../build/streamed-emitter-browser'
} }
const esmRequire = { const esmRequire = {
include: './src/fs/node.ts', include: './src/fs/node.ts',
delimiters: ['', ''], delimiters: ['', ''],
'./node-require': './node-require.mjs' './node-require': '../build/node-require.mjs'
} }
const nodeCjs = { 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 * as sinon from 'sinon'
import { expect, use } from 'chai' import { JSDOM } from 'jsdom'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('fs/browser', function () { describe('fs/browser', function () {
if (+(process.version.match(/^v(\d+)/) as RegExpMatchArray)[1] < 8) { if (+(process.version.match(/^v(\d+)/) as RegExpMatchArray)[1] < 8) {
console.info('jsdom not supported, skipping template-browser...') console.info('jsdom not supported, skipping template-browser...')
return return
} }
const JSDOM = require('jsdom').JSDOM
beforeEach(function () { beforeEach(function () {
const dom = new JSDOM(``, { const dom = new JSDOM(``, {
url: 'https://example.com/foo/bar/', url: 'https://example.com/foo/bar/',
@@ -24,51 +20,51 @@ describe('fs/browser', function () {
}) })
describe('#resolve()', function () { describe('#resolve()', function () {
it('should support relative root', 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 () { 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 () { 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 () { 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 () { 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 () { 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 () { 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 () { 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 () { 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()', () => { describe('#dirname()', () => {
it('should return dirname of file', async function () { it('should return dirname of file', async function () {
const val = fs.dirname('https://example.com/views/foo/bar') 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()', () => { describe('#exists()', () => {
it('should always return true', async function () { it('should always return true', async function () {
const val = await fs.exists('/foo/bar') const val = await fs.exists('/foo/bar')
expect(val).to.equal(true) expect(val).toBe(true)
}) })
}) })
describe('#existsSync()', () => { describe('#existsSync()', () => {
it('should always return true', function () { 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 () { it('should get corresponding text', async function () {
const html = await fs.readFile('https://example.com/views/hello.html') 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', () => { it('should throw 404', () => {
return expect(fs.readFile('https://example.com/not/exist.html')) 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 () { it('should throw error', function () {
const result = expect(fs.readFile('https://example.com/views/hello.html')) 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() server.requests[0].error()
return result return result
}) })
@@ -118,11 +114,11 @@ describe('fs/browser', function () {
}) })
it('should get corresponding text', function () { it('should get corresponding text', function () {
const html = fs.readFileSync('https://example.com/views/hello.html') 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', () => { it('should throw 404', () => {
return expect(() => fs.readFileSync('https://example.com/not/exist.html')) 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 { export class StreamedEmitter implements Emitter {
public buffer = ''; public buffer = '';
+26 -27
View File
@@ -1,68 +1,67 @@
import { expect } from 'chai' import { LRU } from './lru'
import { LRU } from '../../../src/cache/lru'
describe('LRU', () => { describe('LRU', () => {
it('should perform read()/write()', () => { it('should perform read()/write()', () => {
const lru = new LRU(2) const lru = new LRU(2)
expect(lru.limit).to.equal(2) expect(lru.limit).toEqual(2)
lru.write('foo', 'FOO') lru.write('foo', 'FOO')
lru.write('bar', 'BAR') lru.write('bar', 'BAR')
expect(lru.read('foo')).to.equal('FOO') expect(lru.read('foo')).toEqual('FOO')
expect(lru.read('bar')).to.equal('BAR') expect(lru.read('bar')).toEqual('BAR')
}) })
it('should perform clear()', () => { it('should perform clear()', () => {
const lru = new LRU(2) const lru = new LRU(2)
lru.write('foo', 'FOO') lru.write('foo', 'FOO')
lru.write('bar', 'BAR') lru.write('bar', 'BAR')
expect(lru.size).to.equal(2) expect(lru.size).toEqual(2)
lru.clear() lru.clear()
expect(lru.size).to.equal(0) expect(lru.size).toEqual(0)
expect(lru.read('foo')).to.be.undefined expect(lru.read('foo')).toBe(undefined)
}) })
it('should remove lrc item when full(limit=-1)', () => { it('should remove lrc item when full(limit=-1)', () => {
const lru = new LRU(-1) const lru = new LRU(-1)
lru.write('foo', 'FOO') lru.write('foo', 'FOO')
lru.write('bar', 'BAR') lru.write('bar', 'BAR')
expect(lru.size).to.equal(0) expect(lru.size).toEqual(0)
expect(lru.read('foo')).to.be.undefined expect(lru.read('foo')).toBe(undefined)
expect(lru.read('bar')).to.be.undefined expect(lru.read('bar')).toBe(undefined)
}) })
it('should remove lrc item when full(limit=0)', () => { it('should remove lrc item when full(limit=0)', () => {
const lru = new LRU(0) const lru = new LRU(0)
lru.write('foo', 'FOO') lru.write('foo', 'FOO')
lru.write('bar', 'BAR') lru.write('bar', 'BAR')
expect(lru.size).to.equal(0) expect(lru.size).toEqual(0)
expect(lru.read('foo')).to.be.undefined expect(lru.read('foo')).toBe(undefined)
expect(lru.read('bar')).to.be.undefined expect(lru.read('bar')).toBe(undefined)
}) })
it('should remove lrc item when full(limit=1)', () => { it('should remove lrc item when full(limit=1)', () => {
const lru = new LRU(1) const lru = new LRU(1)
lru.write('foo', 'FOO') lru.write('foo', 'FOO')
lru.write('bar', 'BAR') lru.write('bar', 'BAR')
expect(lru.size).to.equal(1) expect(lru.size).toEqual(1)
expect(lru.read('foo')).to.be.undefined expect(lru.read('foo')).toBe(undefined)
expect(lru.read('bar')).to.equal('BAR') expect(lru.read('bar')).toEqual('BAR')
}) })
it('should remove lrc item when full(limit=2)', () => { it('should remove lrc item when full(limit=2)', () => {
const lru = new LRU(2) const lru = new LRU(2)
expect(lru.size).to.equal(0) expect(lru.size).toEqual(0)
lru.write('foo', 'FOO') lru.write('foo', 'FOO')
expect(lru.size).to.equal(1) expect(lru.size).toEqual(1)
lru.write('bar', 'BAR') lru.write('bar', 'BAR')
expect(lru.size).to.equal(2) expect(lru.size).toEqual(2)
lru.write('coo', 'COO') lru.write('coo', 'COO')
expect(lru.size).to.equal(2) expect(lru.size).toEqual(2)
expect(lru.read('foo')).to.be.undefined expect(lru.read('foo')).toBe(undefined)
expect(lru.read('bar')).to.equal('BAR') expect(lru.read('bar')).toEqual('BAR')
expect(lru.read('coo')).to.equal('COO') expect(lru.read('coo')).toEqual('COO')
}) })
it('should overwrite item the with same key', () => { it('should overwrite item the with same key', () => {
const lru = new LRU(2) const lru = new LRU(2)
lru.write('foo', 'FOO') lru.write('foo', 'FOO')
expect(lru.size).to.equal(1) expect(lru.size).toEqual(1)
lru.write('foo', 'BAR') lru.write('foo', 'BAR')
expect(lru.size).to.equal(1) expect(lru.size).toEqual(1)
expect(lru.read('foo')).to.equal('BAR') expect(lru.read('foo')).toEqual('BAR')
}) })
}) })
@@ -1,8 +1,5 @@
import * as chai from 'chai' import { Context } from './context'
import { Context } from '../../../src/context/context' import { Scope } from './scope'
import { Scope } from '../../../src/context/scope'
const expect = chai.expect
describe('Context', function () { describe('Context', function () {
let ctx: any, scope: Scope let ctx: any, scope: Scope
@@ -30,67 +27,68 @@ describe('Context', function () {
describe('#get()', function () { describe('#get()', function () {
it('should get direct property', async 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 () { it('should read nested property', async function () {
expect(ctx.get(['obj', 'first'])).to.equal('f') expect(ctx.get(['obj', 'first'])).toEqual('f')
expect(ctx.get(['obj', 'last'])).to.equal('l') expect(ctx.get(['obj', 'last'])).toEqual('l')
expect(ctx.get(['obj', 'size'])).to.equal(2) expect(ctx.get(['obj', 'size'])).toEqual(2)
}) })
it('undefined property should yield undefined', async function () { it('undefined property should yield undefined', async function () {
expect(ctx.get(['notdefined'])).to.equal(undefined) expect(ctx.get(['notdefined'])).toEqual(undefined)
expect(ctx.get([false as any])).to.equal(undefined) expect(ctx.get([false as any])).toEqual(undefined)
}) })
it('should respect to toLiquid', async function () { it('should respect to toLiquid', async function () {
const scope = new Context({ foo: { const scope = new Context({ foo: {
toLiquid: () => ({ bar: 'BAR' }), toLiquid: () => ({ bar: 'BAR' }),
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 () { 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 () { 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 () { 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 () { 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 () { it('should return undefined if not have a size', async function () {
expect(ctx.get(['one', 'size'])).to.equal(undefined) expect(ctx.get(['one', 'size'])).toBeUndefined()
expect(ctx.get(['non-exist', 'size'])).to.equal(undefined) expect(ctx.get(['non-exist', 'size'])).toBeUndefined()
}) })
it('should read .first of array', async function () { 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 () { 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 () { 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 () { 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 () { 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 () { 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 () { describe('#getFromScope()', function () {
it('should support string', () => { 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 let ctx: Context
beforeEach(function () { beforeEach(function () {
ctx = new Context(ctx, { ctx = new Context(ctx, {
@@ -98,26 +96,26 @@ describe('Context', function () {
} as any) } as any)
}) })
it('should throw when variable not defined', function () { 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 () { it('should throw when deep variable not exist', async function () {
ctx.push({ foo: 'FOO' }) 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 () { it('should throw when itself not defined', async function () {
ctx.push({ foo: 'FOO' }) 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 () { it('should find variable in parent scope', async function () {
ctx.push({ 'foo': 'foo' }) ctx.push({ 'foo': 'foo' })
ctx.push({ ctx.push({
'bar': 'bar' '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 let ctx: Context
beforeEach(function () { beforeEach(function () {
ctx = new Context(ctx, { ctx = new Context(ctx, {
@@ -126,56 +124,56 @@ describe('Context', function () {
}) })
it('should return undefined for prototype object property', function () { it('should return undefined for prototype object property', function () {
ctx.push({ foo: Object.create({ bar: 'BAR' }) }) 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 () { it('should use prototype when ownPropertyOnly=false', function () {
ctx = new Context({ foo: Object.create({ bar: 'BAR' }) }, { ownPropertyOnly: false } as any) 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 () { it('renderOptions.ownPropertyOnly should override options.ownPropertyOnly', function () {
ctx = new Context({ foo: Object.create({ bar: 'BAR' }) }, { ownPropertyOnly: false } as any, { ownPropertyOnly: true }) 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 () { it('should return undefined for Array.prototype.reduce', function () {
ctx.push({ foo: [] }) 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 () { it('should return undefined for function prototype property', function () {
function Foo () {} function Foo () {}
Foo.prototype.bar = 'BAR' Foo.prototype.bar = 'BAR'
ctx.push({ foo: new (Foo as any)() }) 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 () { it('should allow function constructor properties', function () {
function Foo (this: any) { this.bar = 'BAR' } function Foo (this: any) { this.bar = 'BAR' }
ctx.push({ foo: new (Foo as any)() }) 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 () { it('should return undefined for class method', function () {
class Foo { bar () {} } class Foo { bar () {} }
ctx.push({ foo: new Foo() }) 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 () { it('should allow class property', function () {
class Foo { bar = 'BAR' } class Foo { bar = 'BAR' }
ctx.push({ foo: new Foo() }) 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 () { it('should allow Array.prototype.length', function () {
ctx.push({ foo: [1, 2] }) 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 () { it('should allow size to access Array.prototype.length', function () {
ctx.push({ foo: [1, 2] }) 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 () { it('should allow size to access Set.prototype.size', function () {
ctx.push({ foo: new Set([1, 2]) }) 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 () { it('should allow size to access Object key count', function () {
ctx.push({ foo: { bar: 'BAR', coo: 'COO' } }) 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 () { it('should throw when property is hidden and strictVariables is true', function () {
ctx = new Context(ctx, { ctx = new Context(ctx, {
@@ -183,13 +181,13 @@ describe('Context', function () {
strictVariables: true strictVariables: true
} as any) } as any)
ctx.push({ foo: Object.create({ bar: 'BAR' }) }) 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 () { describe('.getAll()', function () {
it('should get all properties when arguments empty', async 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({ ctx.push({
foo: 'foo' foo: 'foo'
}) })
expect(ctx.get(['foo'])).to.equal('foo') expect(ctx.getSync(['foo'])).toEqual('foo')
expect(ctx.get(['bar'])).to.equal('bar') expect(ctx.getSync(['bar'])).toEqual('bar')
}) })
it('should hide deep properties by push', async function () { it('should hide deep properties by push', async function () {
ctx.push({ bar: { bar: 'bar' } }) ctx.push({ bar: { bar: 'bar' } })
ctx.push({ bar: { foo: 'foo' } }) ctx.push({ bar: { foo: 'foo' } })
expect(ctx.get(['bar', 'foo'])).to.equal('foo') expect(ctx.getSync(['bar', 'foo'])).toEqual('foo')
expect(ctx.get(['bar', 'bar'])).to.equal(undefined) expect(ctx.getSync(['bar', 'bar'])).toEqual(undefined)
}) })
}) })
describe('.pop()', function () { describe('.pop()', function () {
@@ -215,7 +213,7 @@ describe('Context', function () {
foo: 'foo' foo: 'foo'
}) })
ctx.pop() 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), {}) .reduce((ctx, val) => __assign(ctx, val), {})
} }
/** /**
* @deprecated use `_get()` instead * @deprecated use `_get()` or `getSync()` instead
*/ */
public get (paths: PropertyKey[]): unknown { public get (paths: PropertyKey[]): unknown {
return this.getSync(paths)
}
public getSync (paths: PropertyKey[]): unknown {
return toValueSync(this._get(paths)) return toValueSync(this._get(paths))
} }
public * _get (paths: PropertyKey[]): IterableIterator<unknown> { 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 * as path from 'path'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised) describe('fs-impl', function () {
describe('fs/node', function () {
describe('.resolve()', function () { describe('.resolve()', function () {
it('should resolve based on root', async function () { it('should resolve based on root', async function () {
const filepath = fs.resolve('/foo', 'bar.html', '.liquid') const filepath = fs.resolve('/foo', 'bar.html', '.liquid')
const expected = path.resolve('/foo/bar.html') 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 () { it('should add extension if it has no extension', async function () {
const filepath = fs.resolve('/foo', 'bar', '.liquid') const filepath = fs.resolve('/foo', 'bar', '.liquid')
const expected = path.resolve('/foo/bar.liquid') const expected = path.resolve('/foo/bar.liquid')
return expect(filepath).to.equal(expected) return expect(filepath).toBe(expected)
}) })
}) })
describe('.existsSync', () => { describe('.existsSync', () => {
it('should resolve as false if not exists', () => { 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', () => { it('should resolve as true if exists', () => {
expect(fs.existsSync(__filename)).to.be.true expect(fs.existsSync(__filename)).toBeTruthy()
}) })
}) })
describe('.exists', () => { describe('.exists', () => {
it('should resolve as false if not exists', async () => { it('should resolve as false if not exists', async () => {
const result = await fs.exists('/foo/bar') const result = await fs.exists('/foo/bar')
expect(result).to.be.false expect(result).toBeFalsy()
}) })
it('should resolve as true if exists', async () => { it('should resolve as true if exists', async () => {
const result = await fs.exists(__filename) const result = await fs.exists(__filename)
expect(result).to.be.true expect(result).toBeTruthy()
}) })
}) })
describe('.readFileSync', function () { describe('.readFileSync', function () {
it('should throw when not exist', 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 () { it('should read content if exists', function () {
const content = fs.readFileSync(__filename) 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 () { describe('.readFile', function () {
it('should throw when not exist', 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 () { it('should read content if exists', async function () {
const content = await fs.readFile(__filename) 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 './fs-impl'
import * as fs from '../../../src/fs/node' import { Loader } from './loader'
import * as chaiAsPromised from 'chai-as-promised'
import { Loader } from '../../../src/fs/loader'
use(chaiAsPromised)
describe('fs/loader', function () { describe('fs/loader', function () {
describe('.candidates()', function () { describe('.candidates()', function () {
it('should resolve relatively', async function () { it('should resolve relatively', async function () {
const loader = new Loader({ relativeReference: true, fs, extname: '' } as any) const loader = new Loader({ relativeReference: true, fs, extname: '' } as any)
const candidates = [...loader.candidates('./foo/bar', ['/root', '/root/foo'], '/root/current', true)] 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 () { it('should not include out of root candidates', async function () {
const loader = new Loader({ relativeReference: true, fs, extname: '' } as any) const loader = new Loader({ relativeReference: true, fs, extname: '' } as any)
const candidates = [...loader.candidates('../foo/bar', ['/root'], '/root/current', true)] 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 () { it('should treat root as a terminated path', async function () {
const loader = new Loader({ relativeReference: true, fs, extname: '' } as any) const loader = new Loader({ relativeReference: true, fs, extname: '' } as any)
const candidates = [...loader.candidates('../root-dir/bar', ['/root'], '/root/current', true)] 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 () { it('should default `.contains()` to () => true', async function () {
const customFs = { const customFs = {
@@ -29,7 +25,7 @@ describe('fs/loader', function () {
} }
const loader = new Loader({ relativeReference: true, fs: customFs, extname: '' } as any) const loader = new Loader({ relativeReference: true, fs: customFs, extname: '' } as any)
const candidates = [...loader.candidates('../foo/bar', ['/root'], '/root/current', true)] 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 { normalize } from './liquid-options'
import { expect } from 'chai'
describe('liquid-options', () => { describe('liquid-options', () => {
describe('.normalize()', () => { describe('.normalize()', () => {
it('should set cache to undefined if specified to falsy', () => { it('should set cache to undefined if specified to falsy', () => {
const options = normalize({ cache: false }) 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 { assert, isArray, isString, isFunction } from './util'
import { LRU, LiquidCache } from './cache' import { LRU, LiquidCache } from './cache'
import { FS, LookupType } from './fs' import { FS, LookupType } from './fs'
import * as fs from './fs/node' import * as fs from './fs/fs-impl'
import { defaultOperators, Operators } from './render' import { defaultOperators, Operators } from './render'
import { json } from './filters/misc' import { json } from './filters/misc'
import { escape } from './filters/html' import { escape } from './filters/html'
@@ -180,8 +180,8 @@ export function normalize (options: LiquidOptions): NormalizedFullOptions {
options.cache = cache options.cache = cache
} }
options = { ...defaultOptions, ...(options.jekyllInclude ? { dynamicPartials: false } : {}), ...options } options = { ...defaultOptions, ...(options.jekyllInclude ? { dynamicPartials: false } : {}), ...options }
if (!options.fs!.dirname && options.relativeReference) { if ((!options.fs!.dirname || !options.fs!.sep) && 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`') console.warn('[LiquidJS] `fs.dirname` and `fs.sep` are required for relativeReference, set relativeReference to `false` to suppress this warning')
options.relativeReference = false options.relativeReference = false
} }
options.root = normalizeDirectoryList(options.root) 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 './parse-stream'
import { ParseStream } from '../../../src/parser' import { Token } from '../tokens'
import { Token } from '../../../src/tokens'
const expect = chai.expect
describe('parseStream', () => { describe('parseStream', () => {
it('should trigger "token" event', () => { it('should trigger "token" event', () => {
@@ -10,6 +7,6 @@ describe('parseStream', () => {
const ps = new ParseStream([token], (token) => ({ token } as any)) const ps = new ParseStream([token], (token) => ({ token } as any))
let got let got
ps.on('token', token => { got = token }).start() 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 () { readWord () {
console.warn('Tokenizer#readWord() will be removed, use #readIdentifier instead')
return this.readIdentifier() 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 { Tokenizer } from '../parser'
import { expect } from 'chai' import { Drop } from '../drop'
import { Drop } from '../../../src/drop/drop' import { Context } from '../context'
import { Context } from '../../../src/context/context' import { toPromise, toValueSync } from '../util'
import { toPromise, toValueSync } from '../../../src/util/async'
describe('Expression', function () { describe('Expression', function () {
const ctx = new Context({}) const ctx = new Context({})
@@ -12,26 +11,26 @@ describe('Expression', function () {
toPromise(create('foo').evaluate(undefined!, false)) toPromise(create('foo').evaluate(undefined!, false))
.then(() => done(new Error('should not resolved'))) .then(() => done(new Error('should not resolved')))
.catch(err => { .catch(err => {
expect(err.message).to.match(/context not defined/) expect(err.message).toMatch(/context not defined/)
done() done()
}) })
}) })
describe('single value', function () { describe('single value', function () {
it('should eval literal', async function () { it('should eval literal', async function () {
expect(await toPromise(create('2.4').evaluate(ctx, false))).to.equal(2.4) expect(await toPromise(create('2.4').evaluate(ctx, false))).toBe(2.4)
expect(await toPromise(create('"foo"').evaluate(ctx, false))).to.equal('foo') expect(await toPromise(create('"foo"').evaluate(ctx, false))).toBe('foo')
expect(await toPromise(create('false').evaluate(ctx, false))).to.equal(false) expect(await toPromise(create('false').evaluate(ctx, false))).toBe(false)
}) })
it('should eval range expression', async function () { it('should eval range expression', async function () {
const ctx = new Context({ two: 2 }) 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('(2..4)').evaluate(ctx, false))).toEqual([2, 3, 4])
expect(await toPromise(create('(two..4)').evaluate(ctx, false))).to.deep.equal([2, 3, 4]) expect(await toPromise(create('(two..4)').evaluate(ctx, false))).toEqual([2, 3, 4])
}) })
it('should eval literal', async function () { it('should eval literal', async function () {
expect(await toPromise(create('2.4').evaluate(ctx, false))).to.equal(2.4) expect(await toPromise(create('2.4').evaluate(ctx, false))).toBe(2.4)
expect(await toPromise(create('"foo"').evaluate(ctx, false))).to.equal('foo') expect(await toPromise(create('"foo"').evaluate(ctx, false))).toBe('foo')
expect(await toPromise(create('false').evaluate(ctx, false))).to.equal(false) expect(await toPromise(create('false').evaluate(ctx, false))).toBe(false)
}) })
it('should eval property access', async function () { it('should eval property access', async function () {
@@ -40,18 +39,18 @@ describe('Expression', function () {
coo: 'bar', coo: 'bar',
doo: { foo: 'bar', bar: { foo: '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))).toBe('BAR')
expect(await toPromise(create('foo["bar"]').evaluate(ctx, false))).to.equal('BAR') expect(await toPromise(create('foo["bar"]').evaluate(ctx, false))).toBe('BAR')
expect(await toPromise(create('foo[coo]').evaluate(ctx, false))).to.equal('BAR') expect(await toPromise(create('foo[coo]').evaluate(ctx, false))).toBe('BAR')
expect(await toPromise(create('foo[doo.foo]').evaluate(ctx, false))).to.equal('BAR') expect(await toPromise(create('foo[doo.foo]').evaluate(ctx, false))).toBe('BAR')
expect(await toPromise(create('foo[doo["foo"]]').evaluate(ctx, false))).to.equal('BAR') expect(await toPromise(create('foo[doo["foo"]]').evaluate(ctx, false))).toBe('BAR')
expect(await toPromise(create('doo[coo].foo').evaluate(ctx, false))).to.equal('bar') expect(await toPromise(create('doo[coo].foo').evaluate(ctx, false))).toBe('bar')
}) })
}) })
describe('simple expression', function () { describe('simple expression', function () {
it('should return false for "1==2"', async () => { 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 () => { it('should apply deep equal for arrays', async () => {
const ctx = new Context({ const ctx = new Context({
@@ -59,133 +58,133 @@ describe('Expression', function () {
arr2: [1, 2], arr2: [1, 2],
arr3: [1, 2, 3] arr3: [1, 2, 3]
}) })
expect(await toPromise(create('arr1==arr2').evaluate(ctx, false))).to.equal(true) expect(await toPromise(create('arr1==arr2').evaluate(ctx, false))).toBe(true)
expect(await toPromise(create('arr1==arr3').evaluate(ctx, false))).to.equal(false) expect(await toPromise(create('arr1==arr3').evaluate(ctx, false))).toBe(false)
}) })
it('should return true for "1<2"', async () => { 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 () => { 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 () => { 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 () => { 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 () => { it('should return true for "one <= two"', async () => {
const ctx = new Context({ one: 1, two: 2 }) 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 () => { it('should return false for "x contains "x""', async () => {
const ctx = new Context({ x: 'XXX' }) 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 () => { it('should return true for "x contains "X""', async () => {
const ctx = new Context({ x: 'XXX' }) 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 () => { it('should return false for "1 contains "x""', async () => {
const ctx = new Context({ x: 'XXX' }) 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 () => { it('should return false for "y contains "x""', async () => {
const ctx = new Context({ x: 'XXX' }) 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 () => { it('should return false for "z contains "x""', async () => {
const ctx = new Context({ x: 'XXX' }) 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 () => { it('should return true for "(1..5) contains 3"', async () => {
const ctx = new Context({ x: 'XXX' }) 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 () => { it('should return false for "(1..5) contains 6"', async () => {
const ctx = new Context({ x: 'XXX' }) 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 () => { 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 () { it('should allow space in quoted value', async function () {
const ctx = new Context({ space: ' ' }) 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', () => { describe('escape', () => {
it('should escape quote', async function () { it('should escape quote', async function () {
const ctx = new Context({ quote: '"' }) 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 () { it('should escape square bracket', async function () {
const ctx = new Context({ obj: { ']': 'bracket' } }) 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 () { describe('complex expression', function () {
it('should support value or value', async 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 () { 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 () { 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 () => { it('should support Drops for "x contains "x""', async () => {
class TemplateDrop extends Drop { class TemplateDrop extends Drop {
valueOf () { return 'X' } valueOf () { return 'X' }
} }
const ctx = new Context({ x: 'XXX', X: new TemplateDrop() }) 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 () { it('should support value and !=', async function () {
const ctx = new Context({ empty: '' }) 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 () { 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 () { 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 or false and false').evaluate(ctx, false))).toBe(true)
expect(await toPromise(create('true and false and false or true').evaluate(ctx, false))).to.equal(false) expect(await toPromise(create('true and false and false or true').evaluate(ctx, false))).toBe(false)
}) })
it('should recognize property access', async function () { it('should recognize property access', async function () {
const ctx = new Context({ obj: { foo: true } }) 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 () { it('should allow nested property access', async function () {
const ctx = new Context({ obj: { foo: 'FOO' }, keys: { "what's this": 'foo' } }) 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 () { 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 () { 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 or not 1 > 2').evaluate(ctx))).toBe(true)
expect(await toPromise(create('not 1 < 2 and not 1 > 2').evaluate(ctx))).to.equal(false) expect(await toPromise(create('not 1 < 2 and not 1 > 2').evaluate(ctx))).toBe(false)
}) })
}) })
describe('sync', function () { describe('sync', function () {
it('should eval literal', 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"', () => { 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 () { it('should escape quote', function () {
const ctx = new Context({ quote: '"' }) 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 () { it('should allow nested property access', function () {
const ctx = new Context({ obj: { foo: 'FOO' }, keys: { "what's this": 'foo' } }) 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 '../context'
import { Context } from '../../../src/context/context' import { HTMLToken, TagToken } from '../tokens'
import { HTMLToken, TagToken } from '../../../src/tokens' import { Render } from './render'
import { Render } from '../../../src/render/render' import { Tag, HTML } from '../template'
import { HTML } from '../../../src/template/html' import { SimpleEmitter } from '../emitters'
import { SimpleEmitter } from '../../../src/emitters/simple-emitter' import { toPromise } from '../util'
import { toPromise } from '../../../src/util/async'
import { Tag } from '../../../src/template/tag'
describe('render', function () { describe('render', function () {
let render: Render let render: Render
before(function () { beforeEach(function () {
render = new Render() render = new Render()
}) })
@@ -18,7 +16,7 @@ describe('render', function () {
const scope = new Context() const scope = new Context()
const token = { getContent: () => '<p>' } as HTMLToken const token = { getContent: () => '<p>' } as HTMLToken
const html = await toPromise(render.renderTemplates([new HTML(token)], scope, new SimpleEmitter())) 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 result += data
}) })
stream.on('end', () => { stream.on('end', () => {
expect(result).to.equal('<p></p>') expect(result).toBe('<p></p>')
done() done()
}) })
}) })
@@ -59,7 +57,7 @@ describe('render', function () {
result += data result += data
}) })
stream.on('end', () => { stream.on('end', () => {
expect(result).to.equal('<p>async tag</p>') expect(result).toBe('<p>async tag</p>')
done() done()
}) })
}) })
@@ -1,58 +1,55 @@
import * as chai from 'chai' import { Context } from '../context'
import * as sinon from 'sinon' import { toPromise } from '../util'
import * as sinonChai from 'sinon-chai' import { IdentifierToken, NumberToken, QuotedToken } from '../tokens'
import { Context } from '../../../../src/context' import { Filter } from './filter'
import { toPromise } from '../../../../src/util'
import { IdentifierToken, NumberToken, QuotedToken } from '../../../../src/tokens'
import { Filter } from '../../../../src/template'
chai.use(sinonChai)
const expect = chai.expect
describe('filter', function () { describe('filter', function () {
const ctx = new Context() const ctx = new Context({ thirty: 30 })
const liquid = {} as any const liquid = { testVersion: '1.0' } as any
it('should not change input if filter not registered', async function () { it('should not change input if filter not registered', async function () {
const filter = new Filter('foo', undefined as any, [], liquid) 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 () { 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 thirty = new NumberToken(new IdentifierToken('30', 0, 2), undefined)
const filter = new Filter('foo', spy, [thirty], liquid) const filter = new Filter('foo', spy, [thirty], liquid)
await toPromise(filter.render('foo', ctx)) 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 () { it('should call filter impl with correct this', async function () {
const spy = sinon.spy() const spy = jest.fn(function * (valStr, diff): Generator<string> {
const thirty = new NumberToken(new IdentifierToken('33', 0, 2), undefined) const val = yield this.context._get([valStr])
await toPromise(new Filter('foo', spy, [thirty], liquid).render('foo', ctx)) return `${this.liquid.testVersion}: ${val + diff}`
expect(spy).to.have.been.calledOn(sinon.match.has('context', ctx)) })
expect(spy).to.have.been.calledOn(sinon.match.has('liquid', liquid)) 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 () { 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 () { it('should render filters with argument', async function () {
const two = new NumberToken(new IdentifierToken('2', 0, 1), undefined) 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 () { it('should render filters with multiple arguments', async function () {
const two = new NumberToken(new IdentifierToken('2', 0, 1), undefined) const two = new NumberToken(new IdentifierToken('2', 0, 1), undefined)
const c = new QuotedToken('"c"', 0, 3) 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 () { it('should pass Objects/Drops as it is', async function () {
class Foo {} 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 () { it('should support key value pairs', async function () {
const two = new NumberToken(new IdentifierToken('2', 0, 1), undefined) 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 '../util'
import { toPromise } from '../../../src/util/async' import { Hash } from './hash'
import { Hash } from '../../../src/template/hash' import { Context } from '../context'
import { Context } from '../../../src/context/context'
const expect = chai.expect
describe('Hash', function () { describe('Hash', function () {
it('should parse "reverse"', async function () { it('should parse "reverse"', async function () {
const hash = await toPromise(new Hash('reverse').render(new Context({ foo: 3 }))) const hash = await toPromise(new Hash('reverse').render(new Context({ foo: 3 })))
expect(hash).to.haveOwnProperty('reverse') expect(hash).toHaveProperty('reverse')
expect(hash.reverse).to.be.true expect(hash.reverse).toBeTruthy()
}) })
it('should parse "num:foo"', async function () { it('should parse "num:foo"', async function () {
const hash = await toPromise(new Hash('num:foo').render(new Context({ foo: 3 }))) 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 () { it('should parse "num:3"', async function () {
const hash = await toPromise(new Hash('num:3').render(new Context())) 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 () { it('should parse "num: arr[0]"', async function () {
const hash = await toPromise(new Hash('num:3').render(new Context({ arr: [3] }))) 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 () { it('should parse "num: 2.3"', async function () {
const hash = await toPromise(new Hash('num:2.3').render(new Context())) 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 () { it('should parse "num:bar.coo"', async function () {
const pending = new Hash('num:bar.coo').render(new Context({ bar: { coo: 3 } })) const pending = new Hash('num:bar.coo').render(new Context({ bar: { coo: 3 } }))
const hash = await toPromise(pending) 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 () { 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 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)) 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, num1: 2.3,
reverse: true, reverse: true,
num2: 3, num2: 3,
@@ -1,11 +1,8 @@
import * as chai from 'chai' import { toPromise } from '../util'
import { toPromise } from '../../../src/util/async' import { Context } from '../context'
import { Context } from '../../../src/context/context' import { Output } from '../template'
import { Output } from '../../../src/template/output' import { OutputToken } from '../tokens'
import { OutputToken } from '../../../src/tokens/output-token' import { defaultOptions } from '../liquid-options'
import { defaultOptions } from '../../../src/liquid-options'
const expect = chai.expect
describe('Output', function () { describe('Output', function () {
const emitter: any = { write: (html: string) => (emitter.html += html), html: '' } 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) const output = new Output({ content: 'foo' } as OutputToken, liquid)
await toPromise(output.render(scope, emitter)) 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 () { it('should skip function property', async function () {
const scope = new Context({ obj: { foo: 'foo', bar: (x: any) => x } }) const scope = new Context({ obj: { foo: 'foo', bar: (x: any) => x } })
const output = new Output({ content: 'obj' } as OutputToken, liquid) const output = new Output({ content: 'obj' } as OutputToken, liquid)
await toPromise(output.render(scope, emitter)) 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 () => { it('should respect to .toString()', async () => {
const scope = new Context({ obj: { toString: () => 'FOO' } }) const scope = new Context({ obj: { toString: () => 'FOO' } })
const output = new Output({ content: 'obj' } as OutputToken, liquid) const output = new Output({ content: 'obj' } as OutputToken, liquid)
await toPromise(output.render(scope, emitter)) 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 () => { it('should respect to .toString()', async () => {
const scope = new Context({ obj: { toString: () => 'FOO' } }) const scope = new Context({ obj: { toString: () => 'FOO' } })
const output = new Output({ content: 'obj' } as OutputToken, liquid) const output = new Output({ content: 'obj' } as OutputToken, liquid)
await toPromise(output.render(scope, emitter)) 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 = { const emitter: any = {
write: (html: any) => { write: (html: any) => {
if (emitter.keepOutputType && typeof html !== 'string') { if (emitter.keepOutputType && typeof html !== 'string') {
@@ -59,7 +56,7 @@ describe('Output', function () {
}, { ...defaultOptions, keepOutputType: true }) }, { ...defaultOptions, keepOutputType: true })
const output = new Output({ content: 'foo' } as OutputToken, liquid) const output = new Output({ content: 'foo' } as OutputToken, liquid)
await toPromise(output.render(scope, emitter)) 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 () => { it('should respect output variable boolean type', async () => {
const scope = new Context({ const scope = new Context({
@@ -67,7 +64,7 @@ describe('Output', function () {
}, { ...defaultOptions, keepOutputType: true }) }, { ...defaultOptions, keepOutputType: true })
const output = new Output({ content: 'foo' } as OutputToken, liquid) const output = new Output({ content: 'foo' } as OutputToken, liquid)
await toPromise(output.render(scope, emitter)) 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 () => { it('should respect output variable object type', async () => {
const scope = new Context({ const scope = new Context({
@@ -75,7 +72,7 @@ describe('Output', function () {
}, { ...defaultOptions, keepOutputType: true }) }, { ...defaultOptions, keepOutputType: true })
const output = new Output({ content: 'foo' } as OutputToken, liquid) const output = new Output({ content: 'foo' } as OutputToken, liquid)
await toPromise(output.render(scope, emitter)) 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 () => { it('should respect output variable string type', async () => {
const scope = new Context({ const scope = new Context({
@@ -83,7 +80,7 @@ describe('Output', function () {
}, { ...defaultOptions, keepOutputType: true }) }, { ...defaultOptions, keepOutputType: true })
const output = new Output({ content: 'foo' } as OutputToken, liquid) const output = new Output({ content: 'foo' } as OutputToken, liquid)
await toPromise(output.render(scope, emitter)) 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 './assert'
import { assert } from '../../../src/util/assert'
const expect = chai.expect
describe('assert', function () { describe('assert', function () {
it('should not throw if predicate is truthy', function () { it('should not throw if predicate is truthy', function () {
const fn = () => assert('foo', () => 'bar') const fn = () => assert('foo', () => 'bar')
expect(fn).to.not.throw() expect(fn).not.toThrow()
}) })
it('should not throw if predicate is truthy', function () { it('should not throw if predicate is truthy', function () {
const fn = () => assert('', () => 'bar') const fn = () => assert('', () => 'bar')
expect(fn).to.throw(/bar/) expect(fn).toThrow(/bar/)
}) })
it('should populate default message', function () { it('should populate default message', function () {
const fn = () => assert(false) 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 { toPromise, toValueSync } from './async'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('utils/async', () => { describe('utils/async', () => {
describe('#toPromise()', function () { describe('#toPromise()', function () {
@@ -11,21 +7,21 @@ describe('utils/async', () => {
return 'foo' return 'foo'
} }
const result = await toPromise(foo()) const result = await toPromise(foo())
expect(result).to.equal('foo') expect(result).toBe('foo')
}) })
it('should support iterable with single return statement', async () => { it('should support iterable with single return statement', async () => {
function * foo () { function * foo () {
return 'foo' return 'foo'
} }
const result = await toPromise(foo()) const result = await toPromise(foo())
expect(result).to.equal('foo') expect(result).toBe('foo')
}) })
it('should support promise', async () => { it('should support promise', async () => {
function foo () { function foo () {
return Promise.resolve('foo') return Promise.resolve('foo')
} }
const result = await toPromise(foo()) const result = await toPromise(foo())
expect(result).to.equal('foo') expect(result).toBe('foo')
}) })
it('should resolve dependency', async () => { it('should resolve dependency', async () => {
function * foo (): Generator<Generator<string>> { function * foo (): Generator<Generator<string>> {
@@ -35,14 +31,14 @@ describe('utils/async', () => {
return 'bar' return 'bar'
} }
const result = await toPromise(foo()) const result = await toPromise(foo())
expect(result).to.equal('bar') expect(result).toBe('bar')
}) })
it('should support promise dependency', async () => { it('should support promise dependency', async () => {
function * foo (): Generator<Promise<string>> { function * foo (): Generator<Promise<string>> {
return yield Promise.resolve('foo') return yield Promise.resolve('foo')
} }
const result = await toPromise(foo()) const result = await toPromise(foo())
expect(result).to.equal('foo') expect(result).toBe('foo')
}) })
it('should reject Promise if dependency throws syncly', done => { it('should reject Promise if dependency throws syncly', done => {
function * foo (): Generator<Generator<never>> { function * foo (): Generator<Generator<never>> {
@@ -52,7 +48,7 @@ describe('utils/async', () => {
throw new Error('bar') throw new Error('bar')
} }
toPromise(foo()).catch(err => { toPromise(foo()).catch(err => {
expect(err.message).to.equal('bar') expect(err.message).toBe('bar')
done() done()
return 0 as any return 0 as any
}) })
@@ -72,7 +68,7 @@ describe('utils/async', () => {
throw new Error('bar') throw new Error('bar')
} }
const ret = await toPromise(foo()) const ret = await toPromise(foo())
expect(ret).to.equal('barfoo') expect(ret).toBe('barfoo')
}) })
}) })
describe('#toValueSync()', function () { describe('#toValueSync()', function () {
@@ -83,7 +79,7 @@ describe('utils/async', () => {
function * bar (): Generator<never> { function * bar (): Generator<never> {
throw new Error('bar') throw new Error('bar')
} }
expect(() => toValueSync(foo())).to.throw('bar') expect(() => toValueSync(foo())).toThrow('bar')
}) })
it('should resume yield after catch', () => { it('should resume yield after catch', () => {
function * foo (): Generator<unknown, never, never> { function * foo (): Generator<unknown, never, never> {
@@ -95,7 +91,7 @@ describe('utils/async', () => {
function * bar (): Generator<never> { function * bar (): Generator<never> {
throw new Error('bar') throw new Error('bar')
} }
expect(toValueSync(foo())).to.equal('foo') expect(toValueSync(foo())).toBe('foo')
}) })
it('should resume return after catch', () => { it('should resume return after catch', () => {
function * foo (): Generator<Generator<never>, string> { function * foo (): Generator<Generator<never>, string> {
@@ -107,10 +103,10 @@ describe('utils/async', () => {
function * bar (): Generator<never> { function * bar (): Generator<never> {
throw new Error('bar') throw new Error('bar')
} }
expect(toValueSync(foo())).to.equal('foo') expect(toValueSync(foo())).toBe('foo')
}) })
it('should return non iterator value as it is', () => { 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 './strftime'
import { strftime as t } from '../../../src/util/strftime' import { DateWithTimezone } from '../../test/stub/date-with-timezone'
import { DateWithTimezone } from '../../stub/date-with-timezone'
const expect = chai.expect
describe('util/strftime', function () { describe('util/strftime', function () {
const now = new Date('2016-01-04 13:15:23') const now = new Date('2016-01-04 13:15:23')
@@ -9,112 +7,112 @@ describe('util/strftime', function () {
describe('Date (Year, Month, Day)', () => { describe('Date (Year, Month, Day)', () => {
it('should format %C as century', function () { 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 () { 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 () { 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 () { 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 () { describe('%j', function () {
it('should format %j as day of year', 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 () { it('should take count of leap years', function () {
const date = new Date('2001 03 01') 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 () { it('should take count of leap years', function () {
const date = new Date('2000 03 01') 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 () { it('should format %q as date suffix', function () {
const st = new Date('2016-03-01 03:05:03') const st = new Date('2016-03-01 03:05:03')
const nd = new Date('2016-03-02 03:05:03') const nd = new Date('2016-03-02 03:05:03')
const rd = new Date('2016-03-03 03:05:03') const rd = new Date('2016-03-03 03:05:03')
expect(t(st, '%q')).to.equal('st') expect(t(st, '%q')).toBe('st')
expect(t(nd, '%q')).to.equal('nd') expect(t(nd, '%q')).toBe('nd')
expect(t(rd, '%q')).to.equal('rd') expect(t(rd, '%q')).toBe('rd')
expect(t(now, '%q')).to.equal('th') expect(t(now, '%q')).toBe('th')
}) })
}) })
describe('Time (Hour, Minute, Second, Subsecond)', function () { describe('Time (Hour, Minute, Second, Subsecond)', function () {
it('should format %I as 0 padded hour12', 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 () { it('should format %I as 12 for 00:00', function () {
const date = new Date('2016-01-01 00:00:00') 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 () { 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 () { 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 () { it('should format %l as 12 for 00:00', function () {
const date = new Date('2016-01-01 00:00:00') 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 () { 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 () { it('should format %N as fractional seconds digits', function () {
const time = new Date('2019-12-15 01:21:00.129') const time = new Date('2019-12-15 01:21:00.129')
expect(t(time, '%N')).to.equal('129000000') expect(t(time, '%N')).toBe('129000000')
expect(t(time, '%2N')).to.equal('12') expect(t(time, '%2N')).toBe('12')
expect(t(time, '%10N')).to.equal('1290000000') expect(t(time, '%10N')).toBe('1290000000')
expect(t(time, '%0N')).to.equal('129000000') expect(t(time, '%0N')).toBe('129000000')
}) })
it('should format %p as upper cased am/pm', function () { it('should format %p as upper cased am/pm', function () {
expect(t(now, '%p')).to.equal('PM') expect(t(now, '%p')).toBe('PM')
expect(t(then, '%p')).to.equal('AM') expect(t(then, '%p')).toBe('AM')
}) })
it('should format %P as lower cased am/pm', function () { it('should format %P as lower cased am/pm', function () {
expect(t(now, '%P')).to.equal('pm') expect(t(now, '%P')).toBe('pm')
expect(t(now, '%^8P')).to.equal(' PM') expect(t(now, '%^8P')).toBe(' PM')
expect(t(then, '%P')).to.equal('am') expect(t(then, '%P')).toBe('am')
}) })
}) })
describe('Weekday', function () { describe('Weekday', function () {
it('should format %A as Monday', function () { it('should format %A as Monday', function () {
expect(t(now, '%A')).to.equal('Monday') expect(t(now, '%A')).toBe('Monday')
expect(t(now, '%^A')).to.equal('MONDAY') expect(t(now, '%^A')).toBe('MONDAY')
expect(t(now, '%#A')).to.equal('MONDAY') expect(t(now, '%#A')).toBe('MONDAY')
}) })
it('should format %a as Mon', function () { it('should format %a as Mon', function () {
expect(t(now, '%a')).to.equal('Mon') expect(t(now, '%a')).toBe('Mon')
expect(t(now, '%^a')).to.equal('MON') expect(t(now, '%^a')).toBe('MON')
}) })
it('should format %u as day of week(1-7)', function () { it('should format %u as day of week(1-7)', function () {
expect(t(now, '%u')).to.be.equal('1') expect(t(now, '%u')).toBe('1')
expect(t(then, '%u')).to.be.equal('7') expect(t(then, '%u')).toBe('7')
}) })
it('should format %w as day of week(0-7)', function () { it('should format %w as day of week(0-7)', function () {
expect(t(now, '%w')).to.be.equal('1') expect(t(now, '%w')).toBe('1')
expect(t(then, '%w')).to.be.equal('0') expect(t(then, '%w')).toBe('0')
}) })
}) })
describe('Seconds since the Unix Epoch', () => { describe('Seconds since the Unix Epoch', () => {
it('should format %s as UNIX seconds', function () { 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', () => { describe('Week number', () => {
it('should format %U as week of year, starts with 0', function () { 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 () { 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 () { it('should format %z as time zone', function () {
// suppose we're in +8:00 // suppose we're in +8:00
const now = new DateWithTimezone('2016-01-04 13:15:23', -480) 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 () { it('should format %z as negative time zone', function () {
// suppose we're in -8:00 // suppose we're in -8:00
const date = new DateWithTimezone('2016-01-04T13:15:23.000Z', 480) 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', () => { describe('combination', () => {
it('should format %x as local date string', function () { 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 () { 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 () { 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 () { 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', () => { describe('literal strings', () => {
it('should escape %% as %', function () { it('should escape %% as %', function () {
expect(t(now, '%%')).to.equal('%') expect(t(now, '%%')).toBe('%')
}) })
it('should escape %n as \\n', function () { 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 () { 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 () { it('should retain un-recognized formaters', function () {
expect(t(now, '%o')).to.equal('%o') expect(t(now, '%o')).toBe('%o')
}) })
}) })
describe('width field', () => { describe('width field', () => {
it('should support 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', () => { it('should ignore invalid width', () => {
expect(t(then, '%1Y')).to.equal('2016') expect(t(then, '%1Y')).toBe('2016')
expect(t(then, '%1H')).to.equal('3') expect(t(then, '%1H')).toBe('3')
}) })
it('should have higher priority than H', () => { it('should have higher priority than H', () => {
expect(t(then, '%0H')).to.equal('03') expect(t(then, '%0H')).toBe('03')
}) })
}) })
describe('modifier field', () => { describe('modifier field', () => {
it('should ignore E modifier', () => { it('should ignore E modifier', () => {
expect(t(now, '%EY')).to.equal('2016') expect(t(now, '%EY')).toBe('2016')
}) })
it('should ignore O modifier', () => { 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', () => { it('should support modifier with width field', () => {
expect(t(now, '%8EY')).to.equal('00002016') expect(t(now, '%8EY')).toBe('00002016')
}) })
}) })
describe('flags field', () => { describe('flags field', () => {
it('should support - flag', () => { it('should support - flag', () => {
expect(t(now, '%-m')).to.equal('1') expect(t(now, '%-m')).toBe('1')
}) })
it('should support _ flag', () => { it('should support _ flag', () => {
expect(t(now, '%_m')).to.equal(' 1') expect(t(now, '%_m')).toBe(' 1')
}) })
it('should support 0 flag', () => { it('should support 0 flag', () => {
expect(t(now, '%0m')).to.equal('01') expect(t(now, '%0m')).toBe('01')
}) })
it('should support ^ flag', () => { it('should support ^ flag', () => {
expect(t(now, '%^B')).to.equal('JANUARY') expect(t(now, '%^B')).toBe('JANUARY')
}) })
it('should respect to specific conversion', () => { it('should respect to specific conversion', () => {
expect(t(now, '%^P')).to.equal('PM') expect(t(now, '%^P')).toBe('PM')
expect(t(now, '%P')).to.equal('pm') expect(t(now, '%P')).toBe('pm')
}) })
it('should support # flag', () => { it('should support # flag', () => {
expect(t(now, '%#B')).to.equal('JANUARY') expect(t(now, '%#B')).toBe('JANUARY')
expect(t(now, '%#P')).to.equal('PM') expect(t(now, '%#P')).toBe('PM')
}) })
it('should support : flag', () => { it('should support : flag', () => {
// suppose we're in +8:00 // suppose we're in +8:00
const date = new DateWithTimezone('2016-01-04T13:15:23.000Z', -480) const date = new DateWithTimezone('2016-01-04T13:15:23.000Z', -480)
expect(t(date, '%:z')).to.equal('+08:00') expect(t(date, '%:z')).toBe('+08:00')
expect(t(date, '%z')).to.equal('+0800') expect(t(date, '%z')).toBe('+0800')
}) })
it('should support multiple flags', () => { 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 { TimezoneDate } from './timezone-date'
import { expect } from 'chai'
describe('TimezoneDate', () => { describe('TimezoneDate', () => {
it('should respect timezone set to 00:00', () => { it('should respect timezone set to 00:00', () => {
const date = new TimezoneDate('2021-10-06T14:26:00.000+08:00', 0) const date = new TimezoneDate('2021-10-06T14:26:00.000+08:00', 0)
expect(date.getTimezoneOffset()).to.equal(0) expect(date.getTimezoneOffset()).toBe(0)
expect(date.getHours()).to.equal(6) expect(date.getHours()).toBe(6)
expect(date.getMinutes()).to.equal(26) expect(date.getMinutes()).toBe(26)
}) })
it('should respect timezone set to -06:00', () => { it('should respect timezone set to -06:00', () => {
const date = new TimezoneDate('2021-10-06T14:26:00.000+08:00', -360) const date = new TimezoneDate('2021-10-06T14:26:00.000+08:00', -360)
expect(date.getTimezoneOffset()).to.equal(-360) expect(date.getTimezoneOffset()).toBe(-360)
expect(date.getMinutes()).to.equal(26) expect(date.getMinutes()).toBe(26)
}) })
it('should support Date as argument', () => { it('should support Date as argument', () => {
const date = new TimezoneDate(new Date('2021-10-06T14:26:00.000+08:00'), 0) 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()', () => { it('should support .getMilliseconds()', () => {
const date = new TimezoneDate('2021-10-06T14:26:00.001+00:00', 0) 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()', () => { it('should support .getDay()', () => {
const date = new TimezoneDate('2021-12-07T00:00:00.001+08:00', -480) 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()', () => { it('should support .toLocaleTimeString()', () => {
const date = new TimezoneDate('2021-10-06T00:00:00.001+00:00', -480) 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.toLocaleTimeString('en-US')).toBe('8:00:00 AM')
expect(() => date.toLocaleDateString()).to.not.throw() expect(() => date.toLocaleDateString()).not.toThrow()
}) })
it('should support .toLocaleDateString()', () => { it('should support .toLocaleDateString()', () => {
const date = new TimezoneDate('2021-10-06T22:00:00.001+00:00', -480) 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('en-US')).toBe('10/7/2021')
expect(() => date.toLocaleDateString()).to.not.throw() expect(() => date.toLocaleDateString()).not.toThrow()
}) })
}) })
@@ -1,128 +1,122 @@
import * as chai from 'chai' import * as _ from './underscore'
import * as sinonChai from 'sinon-chai'
import * as sinon from 'sinon'
import * as _ from '../../../src/util/underscore'
const expect = chai.expect
chai.use(sinonChai)
describe('util/underscore', function () { describe('util/underscore', function () {
describe('.isString()', function () { describe('.isString()', function () {
it('should return true for literal string', 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 () { 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 () { it('should return false for 123 ', function () {
expect(_.isString(123)).to.be.false expect(_.isString(123)).toBeFalsy()
}) })
}) })
describe('.isNumber()', function () { describe('.isNumber()', function () {
it('should return false for "foo"', 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 () { it('should return true for 0', function () {
expect(_.isNumber(0)).to.be.true expect(_.isNumber(0)).toBeTruthy()
}) })
}) })
describe('.stringify()', function () { describe('.stringify()', function () {
it('should return "" for null', function () { it('should return "" for null', function () {
expect(_.stringify(null)).to.equal('') expect(_.stringify(null)).toBe('')
}) })
it('should return "" for undefined', function () { it('should return "" for undefined', function () {
expect(_.stringify(undefined)).to.equal('') expect(_.stringify(undefined)).toBe('')
}) })
it('should return regex string for RegExp', function () { it('should return regex string for RegExp', function () {
const reg = /foo/g 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 () { it('should return locale string for date', function () {
const date = new Date('2018-10-01T14:51:00.000Z') const date = new Date('2018-10-01T14:51:00.000Z')
// Mon Oct 01 2018 22:51:00 GMT+0800 (CST) // 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 () { describe('.forOwn()', function () {
it('should iterate all properties', function () { it('should iterate all properties', function () {
const spy = sinon.spy() const spy = jest.fn()
const obj = { const obj = {
foo: 'bar' foo: 'bar'
} }
_.forOwn(obj, spy) _.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 () { it('should default to empty object', function () {
const spy = sinon.spy() const spy = jest.fn()
_.forOwn(undefined, spy) _.forOwn(undefined, spy)
expect(spy).to.have.not.been.called expect(spy).not.toHaveBeenCalled()
}) })
it('should not iterate over properties on prototype', function () { it('should not iterate over properties on prototype', function () {
const spy = sinon.spy() const spy = jest.fn()
const obj = Object.create({ const obj = Object.create({
bar: 'foo' bar: 'foo'
}) })
obj.foo = 'bar' obj.foo = 'bar'
_.forOwn(obj, spy) _.forOwn(obj, spy)
expect(spy).to.have.been.calledOnce expect(spy).toHaveBeenCalledTimes(1)
expect(spy).to.have.been.calledWith('bar', 'foo', obj) expect(spy).toHaveBeenCalledWith('bar', 'foo', obj)
}) })
it('should break when returned false', function () { it('should break when returned false', function () {
const spy = sinon.stub().returns(false) const spy = jest.fn(() => false)
_.forOwn({ _.forOwn({
'foo': 'foo', 'foo': 'foo',
'bar': 'foo' 'bar': 'foo'
}, spy) }, spy)
expect(spy).to.have.been.calledOnce expect(spy).toHaveBeenCalledTimes(1)
}) })
}) })
describe('.range()', function () { describe('.range()', function () {
it('should return a range of integers', 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 () { describe('.isObject()', function () {
it('should return true for function', 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 () { it('should return true for plain object', function () {
expect(_.isObject({})).to.be.true expect(_.isObject({})).toBeTruthy()
}) })
it('should return false for null', function () { it('should return false for null', function () {
expect(_.isObject(null)).to.be.false expect(_.isObject(null)).toBeFalsy()
}) })
it('should return false for number', function () { it('should return false for number', function () {
expect(_.isObject(2)).to.be.false expect(_.isObject(2)).toBeFalsy()
}) })
}) })
describe('.padEnd()', function () { describe('.padEnd()', function () {
it('should default ch to " "', () => { it('should default ch to " "', () => {
expect(_.padEnd('foo', 5)).to.equal('foo ') expect(_.padEnd('foo', 5)).toBe('foo ')
}) })
}) })
describe('.changeCase()', function () { describe('.changeCase()', function () {
it('should to upper case if there is one lowercase', () => { 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', () => { it('should to lower case if all upper case', () => {
expect(_.changeCase('FOOA')).to.equal('fooa') expect(_.changeCase('FOOA')).toBe('fooa')
}) })
}) })
describe('.caseInsensitiveCompare()', function () { describe('.caseInsensitiveCompare()', function () {
it('should "foo" > "bar"', () => { it('should "foo" > "bar"', () => {
expect(_.caseInsensitiveCompare('foo', 'bar')).to.equal(1) expect(_.caseInsensitiveCompare('foo', 'bar')).toBe(1)
}) })
it('should "foo" < null', () => { it('should "foo" < null', () => {
expect(_.caseInsensitiveCompare('foo', null)).to.equal(-1) expect(_.caseInsensitiveCompare('foo', null)).toBe(-1)
}) })
it('should null > "foo"', () => { it('should null > "foo"', () => {
expect(_.caseInsensitiveCompare(null, 'foo')).to.equal(1) expect(_.caseInsensitiveCompare(null, 'foo')).toBe(1)
}) })
it('should -1 < 0', () => { it('should -1 < 0', () => {
expect(_.caseInsensitiveCompare(-1, 0)).to.equal(-1) expect(_.caseInsensitiveCompare(-1, 0)).toBe(-1)
}) })
it('should 1 > 0', () => { 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 { Liquid } from '../../src/liquid'
import { Drop } from '../../src/drop/drop' import { Drop } from '../../src/drop/drop'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
class SettingsDrop extends Drop { class SettingsDrop extends Drop {
private foo = 'FOO' private foo = 'FOO'
@@ -24,7 +20,7 @@ describe('drop', function () {
it('should support liquidMethodMissing', async function () { it('should support liquidMethodMissing', async function () {
const src = `{{settings.foo}},{{settings.bar}},{{settings.coo}}` const src = `{{settings.foo}},{{settings.bar}},{{settings.coo}}`
const html = await engine.parseAndRender(src, { settings }) 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 () { describe('BlandDrop', function () {
@@ -35,7 +31,7 @@ describe('drop', function () {
{% endunless %}` {% endunless %}`
var ctx = { settings: { fpHeading: '' } } var ctx = { settings: { fpHeading: '' } }
const html = await engine.parseAndRender(src, ctx) 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 '../..' import { Liquid } from '../..'
describe('#evalValueSync()', function () { describe('#evalValueSync()', function () {
@@ -6,6 +5,6 @@ describe('#evalValueSync()', function () {
beforeEach(() => { engine = new Liquid() }) beforeEach(() => { engine = new Liquid() })
it('should eval value syncly', async function () { 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 '../..' import { Liquid } from '../..'
describe('#evalValue()', function () { describe('#evalValue()', function () {
@@ -7,16 +6,16 @@ describe('#evalValue()', function () {
it('should support boolean', async function () { it('should support boolean', async function () {
const val = await engine.evalValue('true') const val = await engine.evalValue('true')
expect(val).to.equal(true) expect(val).toBe(true)
}) })
it('should support binary expression with Context', async function () { it('should support binary expression with Context', async function () {
const val = await engine.evalValue('a > b', { a: 1, b: 2 }) 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 () { it('should inherit Liquid options', async function () {
const val = await engine.evalValue('foo') 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 request from 'supertest'
import * as express from 'express' import * as express from 'express'
import { resolve } from 'path' import { resolve } from 'path'
@@ -47,8 +46,8 @@ describe('express()', function () {
const ctx = {} const ctx = {}
engine.express().call(view, file, ctx, function (err: any) { engine.express().call(view, file, ctx, function (err: any) {
try { try {
expect(err.code).to.equal('ENOENT') expect(err.code).toBe('ENOENT')
expect(err.message).to.match(/Failed to lookup/) expect(err.message).toMatch(/Failed to lookup/)
done() done()
} catch (e) { } catch (e) {
done(e) done(e)
+67 -71
View File
@@ -1,18 +1,11 @@
import { TopLevelToken, TagToken, Tokenizer, Context, Liquid, Drop, toValueSync } from '../..' 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 const LiquidUMD = require('../../dist/liquid.browser.umd.js').Liquid
use(chaiAsPromised)
use(sinonChai)
describe('Issues', function () { describe('Issues', function () {
it('#221 unicode blanks are not properly treated', async () => { it('#221 unicode blanks are not properly treated', async () => {
const engine = new Liquid({ strictVariables: true, strictFilters: true }) const engine = new Liquid({ strictVariables: true, strictFilters: true })
const html = engine.parseAndRenderSync('{{huh | truncate: 11}}', { huh: 'fdsafdsafdsafdsaaaaa' }) 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 () => { it('#252 "Not valid identifier" error for a quotes-containing identifier', async () => {
const template = `{% capture "form_classes" -%} const template = `{% capture "form_classes" -%}
@@ -20,12 +13,12 @@ describe('Issues', function () {
{%- endcapture %}{{form_classes}}` {%- endcapture %}{{form_classes}}`
const engine = new Liquid() const engine = new Liquid()
const html = await engine.parseAndRender(template) 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 () => { it('#259 complex property access with braces is not supported', async () => {
const engine = new Liquid() const engine = new Liquid()
const html = engine.parseAndRenderSync('{{ ["complex key"] }}', { 'complex key': 'foo' }) 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 () => { it('#243 Potential for ReDoS through string replace function', async () => {
const engine = new Liquid() const engine = new Liquid()
@@ -38,13 +31,13 @@ describe('Issues', function () {
const html = engine.parseAndRenderSync(template, parameters) const html = engine.parseAndRenderSync(template, parameters)
// should stringify the regexp rather than execute it // 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', () => { it('#263 raw/endraw block not ignoring {% characters', () => {
const template = `{% raw %}This is a code snippet showing how {% breaks the raw block.{% endraw %}` const template = `{% raw %}This is a code snippet showing how {% breaks the raw block.{% endraw %}`
const engine = new Liquid() const engine = new Liquid()
const html = engine.parseAndRenderSync(template) 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', () => { it('#268 elsif is not supported for unless', () => {
const template = `{%- unless condition1 -%} const template = `{%- unless condition1 -%}
@@ -56,7 +49,7 @@ describe('Issues', function () {
{% endunless %}` {% endunless %}`
const engine = new Liquid() const engine = new Liquid()
const html = engine.parseAndRenderSync(template, { condition1: true, condition2: true }) 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', () => { it('#277 Passing liquid in FilterImpl', () => {
const engine = new Liquid() const engine = new Liquid()
@@ -67,12 +60,12 @@ describe('Issues', function () {
`{{ subtemplate | render: "foo" }}`, `{{ subtemplate | render: "foo" }}`,
{ subtemplate: encodeURIComponent('hello {{ name }}') } { subtemplate: encodeURIComponent('hello {{ name }}') }
) )
expect(html).to.equal('hello foo') expect(html).toBe('hello foo')
}) })
it('#288 Unexpected behavior when string literals contain }}', async () => { it('#288 Unexpected behavior when string literals contain }}', async () => {
const engine = new Liquid() const engine = new Liquid()
const html = await engine.parseAndRender(`{{ '{{' }}{{ '}}' }}`) const html = await engine.parseAndRender(`{{ '{{' }}{{ '}}' }}`)
expect(html).to.equal('{{}}') expect(html).toBe('{{}}')
}) })
it('#222 Support function calls', async () => { it('#222 Support function calls', async () => {
const engine = new Liquid() const engine = new Liquid()
@@ -80,7 +73,7 @@ describe('Issues', function () {
`{{ obj.property }}`, `{{ obj.property }}`,
{ obj: { property: () => 'BAR' } } { obj: { property: () => 'BAR' } }
) )
expect(html).to.equal('BAR') expect(html).toBe('BAR')
}) })
it('#313 lenientIf not working as expected in umd', async () => { it('#313 lenientIf not working as expected in umd', async () => {
const engine = new LiquidUMD({ const engine = new LiquidUMD({
@@ -88,7 +81,7 @@ describe('Issues', function () {
lenientIf: true lenientIf: true
}) })
const html = await engine.parseAndRender(`{{ name | default: "default name" }}`) 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 () => { it('#321 comparison for empty/nil', async () => {
const engine = new Liquid() const engine = new Liquid()
@@ -96,30 +89,31 @@ describe('Issues', function () {
'{% if empty == nil %}true{%else%}false{%endif%}' + '{% if empty == nil %}true{%else%}false{%endif%}' +
'{% if nil == empty %}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 () => { it('#320 newline_to_br filter should output <br /> instead of <br/>', async () => {
const engine = new Liquid() const engine = new Liquid()
const html = await engine.parseAndRender( const html = await engine.parseAndRender(
`{{ 'a \n b \n c' | newline_to_br | split: '<br />' }}` `{{ '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 () => { it('#342 New lines in logical operator', async () => {
const engine = new Liquid() 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 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) 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 () => { it('#401 Timezone Offset Issue', async () => {
const engine = new Liquid({ timezoneOffset: -600 }) const engine = new Liquid({ timezoneOffset: -600 })
const tpl = engine.parse('{{ date | date: "%Y-%m-%d %H:%M %p %z" }}') 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' }) 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 () => { it('#412 Pass root as it is to `resolve`', async () => {
const engine = new Liquid({ const engine = new Liquid({
root: '/tmp', root: '/tmp',
relativeReference: false,
fs: { fs: {
readFileSync: (file: string) => file, readFileSync: (file: string) => file,
async readFile (file: string) { return 'foo' }, async readFile (file: string) { return 'foo' },
@@ -130,15 +124,16 @@ describe('Issues', function () {
}) })
const tpl = engine.parse('{% include "foo.liquid" %}') const tpl = engine.parse('{% include "foo.liquid" %}')
const html = await engine.renderSync(tpl) 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 () => { it('#416 Templates imported by {% render %} not cached for concurrent async render', async () => {
const readFile = sinon.spy(() => Promise.resolve('HELLO')) const readFile = jest.fn(() => Promise.resolve('HELLO'))
const exists = sinon.spy(() => 'HELLO') const exists = jest.fn(() => 'HELLO')
const engine = new Liquid({ const engine = new Liquid({
cache: true, cache: true,
extname: '.liquid', extname: '.liquid',
root: '~', root: '~',
relativeReference: false,
fs: { fs: {
exists, exists,
resolve: (root: string, file: string, ext: string) => root + '#' + file + ext, 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( await Promise.all(Array(5).fill(0).map(
x => engine.parseAndRender("{% render 'template' %}") x => engine.parseAndRender("{% render 'template' %}")
)) ))
expect(exists).to.be.calledOnce expect(exists).toHaveBeenCalledTimes(1)
expect(readFile).to.be.calledOnce 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({ const engine = new Liquid({
timezoneOffset: 0, timezoneOffset: 0,
preserveTimezones: true preserveTimezones: true
}) })
const tpl = engine.parse('Welcome to {{ now | date: "%Y-%m-%d" }}!') 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') 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 () => { it('#433 Support Jekyll-like includes', async () => {
const engine = new Liquid({ const engine = new Liquid({
dynamicPartials: false, dynamicPartials: false,
relativeReference: false,
root: '/tmp', root: '/tmp',
fs: { fs: {
readFileSync: (file: string) => file, readFileSync: (file: string) => file,
@@ -175,7 +171,7 @@ describe('Issues', function () {
}) })
const tpl = engine.parse('{% include prefix/{{ my_variable | append: "-bar" }}/suffix %}') const tpl = engine.parse('{% include prefix/{{ my_variable | append: "-bar" }}/suffix %}')
const html = await engine.render(tpl, { my_variable: 'foo' }) 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', () => { it('#428 Implement liquid/echo tags', () => {
const template = `{%- liquid const template = `{%- liquid
@@ -192,17 +188,17 @@ describe('Issues', function () {
-%}` -%}`
const engine = new Liquid() const engine = new Liquid()
const html = engine.parseAndRenderSync(template, { array: [1, 2, 3] }) 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 () => { it('#454 leaking JS prototype getter functions in evaluation', async () => {
const engine = new Liquid({ ownPropertyOnly: true }) const engine = new Liquid({ ownPropertyOnly: true })
const html = engine.parseAndRenderSync('{{foo | size}}-{{bar.coo}}', { foo: 'foo', bar: Object.create({ coo: 'COO' }) }) 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', () => { it('#465 Liquidjs divided_by not compatible with Ruby/Shopify Liquid', () => {
const engine = new Liquid({ ownPropertyOnly: true }) const engine = new Liquid({ ownPropertyOnly: true })
const html = engine.parseAndRenderSync('{{ 5 | divided_by: 3, 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 () => { it('#479 url_encode throws on undefined value', async () => {
const engine = new Liquid({ const engine = new Liquid({
@@ -210,7 +206,7 @@ describe('Issues', function () {
}) })
const tpl = engine.parse('{{ v | url_encode }}') const tpl = engine.parse('{{ v | url_encode }}')
const html = await engine.render(tpl, { v: undefined }) const html = await engine.render(tpl, { v: undefined })
expect(html).to.equal('') expect(html).toBe('')
}) })
it('#481 filters that should not throw', async () => { it('#481 filters that should not throw', async () => {
const engine = new Liquid() const engine = new Liquid()
@@ -225,17 +221,17 @@ describe('Issues', function () {
{{ foo | concat | json }} {{ foo | concat | json }}
`) `)
const html = await engine.render(tpl, { foo: undefined }) 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 () => { it('#481 concat should always return an array', async () => {
const engine = new Liquid() const engine = new Liquid()
const html = await engine.parseAndRender(`{{ foo | concat | json }}`) 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 () => { it('#486 Access array items from the right with negative indexes', async () => {
const engine = new Liquid() const engine = new Liquid()
const html = await engine.parseAndRender(`{% assign a = "x,y,z" | split: ',' -%}{{ a[-1] }} {{ a[-3] }} {{ a[-8] }}`) 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 () => { it('#492 contains operator does not support Drop', async () => {
class TemplateDrop extends Drop { class TemplateDrop extends Drop {
@@ -244,44 +240,44 @@ describe('Issues', function () {
const engine = new Liquid() const engine = new Liquid()
const ctx = { template: new TemplateDrop() } const ctx = { template: new TemplateDrop() }
const html = await engine.parseAndRender(`{% if template contains "product" %}contains{%endif%}`, ctx) 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 () => { it('#513 should support large number of templates [async]', async () => {
const engine = new Liquid() const engine = new Liquid()
const html = await engine.parseAndRender(`{% for i in (1..10000) %}{{ i }}{% endfor %}`) 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]', () => { it('#513 should support large number of templates [sync]', () => {
const engine = new Liquid() const engine = new Liquid()
const html = engine.parseAndRenderSync(`{% for i in (1..10000) %}{{ i }}{% endfor %}`) 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', () => { it('#519 should throw parse error for invalid assign expression', () => {
const engine = new Liquid() 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', () => { it('#527 export Liquid Expression', () => {
const tokenizer = new Tokenizer('a > b') const tokenizer = new Tokenizer('a > b')
const expression = tokenizer.readExpression() const expression = tokenizer.readExpression()
const result = toValueSync(expression.evaluate(new Context({ a: 1, b: 2 }))) 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 () => { it('#527 export Liquid Expression (evalValue)', async () => {
const liquid = new Liquid() const liquid = new Liquid()
const result = await liquid.evalValue('a > b', { a: 1, b: 2 }) 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 () => { it('#527 export Liquid Expression (evalValueSync)', async () => {
const liquid = new Liquid() const liquid = new Liquid()
const result = liquid.evalValueSync('a > b', { a: 1, b: 2 }) 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 () => { it('#276 Promise support in expressions', async () => {
const liquid = new Liquid() const liquid = new Liquid()
const tpl = '{%if name == "alice" %}true{%endif%}' const tpl = '{%if name == "alice" %}true{%endif%}'
const ctx = { name: Promise.resolve('alice') } const ctx = { name: Promise.resolve('alice') }
const html = await liquid.parseAndRender(tpl, ctx) 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 () => { it('#533 Nested Promise support for scope object', async () => {
const liquid = new Liquid() 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('a == 1', context)).toBe(true)
expect(await liquid.evalValue('b == 1', context)).to.equal(true) expect(await liquid.evalValue('b == 1', context)).toBe(true)
expect(await liquid.evalValue('c == 1', context)).to.equal(true) expect(await liquid.evalValue('c == 1', context)).toBe(true)
expect(await liquid.evalValue('d.d == 1', context)).to.equal(true) expect(await liquid.evalValue('d.d == 1', context)).toBe(true)
expect(await liquid.evalValue('e.e == 1', context)).to.equal(true) expect(await liquid.evalValue('e.e == 1', context)).toBe(true)
expect(await liquid.evalValue('f.f == 1', context)).to.equal(true) expect(await liquid.evalValue('f.f == 1', context)).toBe(true)
expect(await liquid.evalValue('g.g == 1', context)).to.equal(true) expect(await liquid.evalValue('g.g == 1', context)).toBe(true)
expect(await liquid.evalValue('h.h == 1', context)).to.equal(true) expect(await liquid.evalValue('h.h == 1', context)).toBe(true)
expect(await liquid.evalValue('i.i == 1', context)).to.equal(true) expect(await liquid.evalValue('i.i == 1', context)).toBe(true)
expect(await liquid.evalValue('j.j == 1', context)).to.equal(true) expect(await liquid.evalValue('j.j == 1', context)).toBe(true)
expect(await liquid.parseAndRender('{{a}}', context)).to.equal('1') expect(await liquid.parseAndRender('{{a}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{b}}', context)).to.equal('1') expect(await liquid.parseAndRender('{{b}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{c}}', context)).to.equal('1') expect(await liquid.parseAndRender('{{c}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{d.d}}', context)).to.equal('1') expect(await liquid.parseAndRender('{{d.d}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{e.e}}', context)).to.equal('1') expect(await liquid.parseAndRender('{{e.e}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{f.f}}', context)).to.equal('1') expect(await liquid.parseAndRender('{{f.f}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{g.g}}', context)).to.equal('1') expect(await liquid.parseAndRender('{{g.g}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{h.h}}', context)).to.equal('1') expect(await liquid.parseAndRender('{{h.h}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{i.i}}', context)).to.equal('1') expect(await liquid.parseAndRender('{{i.i}}', context)).toBe('1')
expect(await liquid.parseAndRender('{{j.j}}', context)).to.equal('1') expect(await liquid.parseAndRender('{{j.j}}', context)).toBe('1')
}) })
it('#559 Case/When should evaluate multiple When statements', async () => { it('#559 Case/When should evaluate multiple When statements', async () => {
const liquid = new Liquid() const liquid = new Liquid()
@@ -344,7 +340,7 @@ describe('Issues', function () {
{% endcase %} {% endcase %}
` `
const html = await liquid.parseAndRender(tpl) 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 () => { it('#570 tag registration compatible to v9', async () => {
const liquid = new Liquid() const liquid = new Liquid()
@@ -360,7 +356,7 @@ describe('Issues', function () {
const tpl = '{% metadata_file foo %}' const tpl = '{% metadata_file foo %}'
const ctx = { foo: 'FOO' } const ctx = { foo: 'FOO' }
const html = await liquid.parseAndRender(tpl, ctx) 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 () => { it('#573 date filter should return parsed input when no format is provided', async () => {
const liquid = new Liquid() const liquid = new Liquid()
@@ -376,7 +372,7 @@ describe('Issues', function () {
const tpl = `{{ 'now' | date }}` const tpl = `{{ 'now' | date }}`
const html = await liquid.parseAndRender(tpl) const html = await liquid.parseAndRender(tpl)
// sample: Thursday, February 2, 2023 at 6:25 pm +0000 // 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 () => { it('#575 Add support for Not operator', async () => {
const liquid = new Liquid() const liquid = new Liquid()
@@ -388,7 +384,7 @@ describe('Issues', function () {
{% endif %}` {% endif %}`
const ctx = { link: 'https://example.com', button: false } const ctx = { link: 'https://example.com', button: false }
const html = await liquid.parseAndRender(tpl, ctx) 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 () => { it('#70 strip multiline content of <style>', async () => {
const str = ` const str = `
@@ -398,7 +394,7 @@ describe('Issues', function () {
const engine = new Liquid() const engine = new Liquid()
const template = '{{ str | strip_html }}' const template = '{{ str | strip_html }}'
const html = await engine.parseAndRender(template, { str }) const html = await engine.parseAndRender(template, { str })
expect(html).to.match(/^\s*$/) expect(html).toMatch(/^\s*$/)
}) })
it('#589 Arrays should compare values', async () => { it('#589 Arrays should compare values', async () => {
const engine = new Liquid() const engine = new Liquid()
@@ -408,6 +404,6 @@ describe('Issues', function () {
{% if people1 == people2 %}true{%else%}false{% endif %} {% if people1 == people2 %}true{%else%}false{% endif %}
` `
const html = await engine.parseAndRender(template) const html = await engine.parseAndRender(template)
expect(html).to.contain('true') expect(html).toContain('true')
}) })
}) })
@@ -1,8 +1,4 @@
import { Liquid } from '../..' import { Liquid } from '../..'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('.parseAndRender()', function () { describe('.parseAndRender()', function () {
var engine: Liquid, strictEngine: Liquid var engine: Liquid, strictEngine: Liquid
@@ -15,51 +11,50 @@ describe('.parseAndRender()', function () {
it('should stringify array ', async function () { it('should stringify array ', async function () {
var ctx = { arr: [-2, 'a'] } var ctx = { arr: [-2, 'a'] }
const html = await engine.parseAndRender('{{arr}}', ctx) 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 () { it('should render undefined as empty', async function () {
const html = await engine.parseAndRender('foo{{zzz}}bar', {}) 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 () { it('should render as null when filter undefined', async function () {
const html = await engine.parseAndRender('{{"foo" | filter1}}', {}) 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 () { it('should throw upon undefined filter when strictFilters set', function () {
return expect(strictEngine.parseAndRender('{{"foo" | filter1}}', {})).to return expect(strictEngine.parseAndRender('{{"foo" | filter1}}', {})).rejects.toThrow(/undefined filter: filter1/)
.be.rejectedWith(/undefined filter: filter1/)
}) })
it('should parse html', function () { it('should parse html', function () {
expect(function () { expect(function () {
engine.parse('{{obj}}') engine.parse('{{obj}}')
}).to.not.throw() }).not.toThrow()
expect(function () { expect(function () {
engine.parse('<html><head>{{obj}}</head></html>') engine.parse('<html><head>{{obj}}</head></html>')
}).to.not.throw() }).not.toThrow()
}) })
it('template should be able to be rendered multiple times', async function () { it('template should be able to be rendered multiple times', async function () {
const ctx = { obj: [1, 2] } const ctx = { obj: [1, 2] }
const template = engine.parse('{{obj}}') const template = engine.parse('{{obj}}')
const result = await engine.render(template, ctx) const result = await engine.render(template, ctx)
expect(result).to.equal('12') expect(result).toBe('12')
const result2 = await engine.render(template, ctx) const result2 = await engine.render(template, ctx)
expect(result2).to.equal('12') expect(result2).toBe('12')
}) })
it('should support the "join" filter', async function () { it('should support the "join" filter', async function () {
var ctx = { names: ['alice', 'bob'] } var ctx = { names: ['alice', 'bob'] }
var template = engine.parse('<p>{{names | join: ","}}</p>') var template = engine.parse('<p>{{names | join: ","}}</p>')
const html = await engine.render(template, ctx) 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 () { it('should support the "first" filter', async function () {
var src = '{% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}' + var src = '{% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}' +
'{{ my_array | first }}' '{{ my_array | first }}'
const html = await engine.parseAndRender(src) 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 () { it('should support nil(null, undefined) literal', async function () {
const src = '{% if notexist == nil %}true{% endif %}' const src = '{% if notexist == nil %}true{% endif %}'
const html = await engine.parseAndRender(src) const html = await engine.parseAndRender(src)
expect(html).to.equal('true') expect(html).toBe('true')
}) })
}) })
@@ -1,10 +1,6 @@
import { Liquid } from '../..' import { Liquid } from '../..'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import { resolve } from 'path' import { resolve } from 'path'
use(chaiAsPromised)
describe('#renderFile()', function () { describe('#renderFile()', function () {
const root = resolve(__dirname, '../stub/root') const root = resolve(__dirname, '../stub/root')
const views = resolve(__dirname, '../stub/views') const views = resolve(__dirname, '../stub/views')
@@ -17,16 +13,16 @@ describe('#renderFile()', function () {
}) })
it('should render file', async function () { it('should render file', async function () {
const html = await engine.renderFile(resolve(root, 'foo.html'), {}) 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 () { it('should find files without extname', async function () {
var engine = new Liquid({ root }) var engine = new Liquid({ root })
const html = await engine.renderFile(resolve(root, 'bar'), {}) 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 () { it('should accept relative path', async function () {
const html = await engine.renderFile('foo.html') 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 () { it('should traverse root array', async function () {
engine = new Liquid({ engine = new Liquid({
@@ -34,27 +30,26 @@ describe('#renderFile()', function () {
extname: '.html' extname: '.html'
}) })
const html = await engine.renderFile('foo.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 () { it('should default root to cwd', async function () {
engine = new Liquid() engine = new Liquid()
const html = await engine.renderFile('package.json') 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 () { it('should render file with context', async function () {
const html = await engine.renderFile(resolve(views, 'name.html'), { name: 'harttle' }) 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 () { it('should use default extname', async function () {
const html = await engine.renderFile(resolve(root, 'foo')) 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 () { it('should throw with lookup list when file not exist', function () {
engine = new Liquid({ engine = new Liquid({
root: ['/boo', '/root/'], root: ['/boo', '/root/'],
extname: '.html' extname: '.html'
}) })
return expect(engine.renderFile('/not/exist.html')).to return expect(engine.renderFile('/not/exist.html')).rejects.toThrow(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
.be.rejectedWith(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
}) })
}) })
@@ -1,10 +1,6 @@
import { expect, use } from 'chai'
import { resolve } from 'path' import { resolve } from 'path'
import * as chaiAsPromised from 'chai-as-promised'
import { drainStream } from '../stub/stream' import { drainStream } from '../stub/stream'
use(chaiAsPromised)
describe('.renderToNodeStream()', function () { describe('.renderToNodeStream()', function () {
it('should render to stream in Node.js', done => { it('should render to stream in Node.js', done => {
const cjs = require('../../dist/liquid.node.cjs') const cjs = require('../../dist/liquid.node.cjs')
@@ -15,7 +11,7 @@ describe('.renderToNodeStream()', function () {
stream.on('data', (data: string) => { html += data }) stream.on('data', (data: string) => { html += data })
stream.on('end', () => { stream.on('end', () => {
try { try {
expect(html).to.equal('foo') expect(html).toBe('foo')
done() done()
} catch (err) { } catch (err) {
done(err) done(err)
@@ -26,7 +22,7 @@ describe('.renderToNodeStream()', function () {
const cjs = require('../../dist/liquid.browser.umd') const cjs = require('../../dist/liquid.browser.umd')
const engine = new cjs.Liquid() const engine = new cjs.Liquid()
const render = () => engine.renderToNodeStream('foo') 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/') root: resolve(__dirname, '../stub/root/')
}) })
const stream = await engine.renderFileToNodeStream('foo.html') 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 * as sinon from 'sinon'
import { expect, use } from 'chai'
import { JSDOM } from 'jsdom' import { JSDOM } from 'jsdom'
import * as chaiAsPromised from 'chai-as-promised' import type { Liquid } from '../..'
const LiquidUMD = require('../../dist/liquid.browser.umd.js').Liquid
use(chaiAsPromised)
describe('xhr', () => { describe('xhr', () => {
if (+(process.version.match(/^v(\d+)/) as RegExpMatchArray)[1] < 8) { 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).XMLHttpRequest = sinon.FakeXMLHttpRequest;
(global as any).document = dom.window.document (global as any).document = dom.window.document
engine = new Liquid({ engine = new LiquidUMD({
root: 'https://example.com/views/', root: 'https://example.com/views/',
extname: '.html' extname: '.html'
}) })
@@ -37,21 +34,21 @@ describe('xhr', () => {
describe('#renderFile()', () => { describe('#renderFile()', () => {
it('should support without extname', async () => { it('should support without extname', async () => {
const html = await engine.renderFile('hello', { name: 'alice1' }) 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 () => { it('should support with extname', async () => {
const html = await engine.renderFile('hello.html', { name: 'alice2' }) 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 () => { it('should support with absolute path', async () => {
server.respondWith('GET', 'https://example.com/foo.html', server.respondWith('GET', 'https://example.com/foo.html',
[200, { 'Content-Type': 'text/plain' }, 'foo']) [200, { 'Content-Type': 'text/plain' }, 'foo'])
const html = await engine.renderFile('/foo.html') const html = await engine.renderFile('/foo.html')
return expect(html).to.equal('foo') return expect(html).toBe('foo')
}) })
it('should support with url', async () => { it('should support with url', async () => {
const html = await engine.renderFile('https://example.com/views/hello.html', { name: 'alice4' }) 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 () => { it('should support include', async () => {
server.respondWith('GET', 'https://example.com/views/hello.html', server.respondWith('GET', 'https://example.com/views/hello.html',
@@ -59,15 +56,15 @@ describe('xhr', () => {
server.respondWith('GET', 'https://example.com/views/name.html', server.respondWith('GET', 'https://example.com/views/name.html',
[200, { 'Content-Type': 'text/plain' }, '{{name}}']) [200, { 'Content-Type': 'text/plain' }, '{{name}}'])
const html = await engine.renderFile('hello.html', { name: 'alice5' }) 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', () => { it('should throw 404', () => {
return expect(engine.renderFile('/not/exist.html')) return expect(engine.renderFile('/not/exist.html'))
.to.be.rejectedWith('Not Found') .rejects.toThrow('Not Found')
}) })
it('should throw error', function () { it('should throw error', function () {
const result = expect(engine.renderFile('hello.html')) 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) { (global as any).XMLHttpRequest.onCreate = function (request: sinon.SinonFakeXMLHttpRequest) {
setTimeout(() => request.error()) setTimeout(() => request.error())
} }
@@ -76,53 +73,53 @@ describe('xhr', () => {
}) })
describe('#renderFile() with root specified', () => { describe('#renderFile() with root specified', () => {
it('should support undefined root', async () => { it('should support undefined root', async () => {
engine = new Liquid({ engine = new LiquidUMD({
extname: '.html' extname: '.html'
}) })
server.respondWith('GET', 'https://example.com/foo/hello.html', server.respondWith('GET', 'https://example.com/foo/hello.html',
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}']) [200, { 'Content-Type': 'text/plain' }, 'hello {{name}}'])
const html = await engine.renderFile('hello.html', { name: 'alice5' }) 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 () => { it('should support empty root', async () => {
engine = new Liquid({ engine = new LiquidUMD({
root: '', root: '',
extname: '.html' extname: '.html'
}) })
server.respondWith('https://example.com/foo/hello.html', server.respondWith('https://example.com/foo/hello.html',
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}']) [200, { 'Content-Type': 'text/plain' }, 'hello {{name}}'])
const html = await engine.renderFile('hello.html', { name: 'alice5' }) 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 () => { it('should support with relative path', async () => {
engine = new Liquid({ engine = new LiquidUMD({
root: './views/', root: './views/',
extname: '.html' extname: '.html'
}) })
server.respondWith('GET', 'https://example.com/foo/views/hello.html', server.respondWith('GET', 'https://example.com/foo/views/hello.html',
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}']) [200, { 'Content-Type': 'text/plain' }, 'hello {{name}}'])
const html = await engine.renderFile('hello.html', { name: 'alice5' }) 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 () => { it('should support with absolute path', async () => {
engine = new Liquid({ engine = new LiquidUMD({
root: '/views/', root: '/views/',
extname: '.html' extname: '.html'
}) })
server.respondWith('GET', 'https://example.com/views/hello.html', server.respondWith('GET', 'https://example.com/views/hello.html',
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}']) [200, { 'Content-Type': 'text/plain' }, 'hello {{name}}'])
const html = await engine.renderFile('hello.html', { name: 'alice5' }) 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 () => { it('should support with url', async () => {
engine = new Liquid({ engine = new LiquidUMD({
root: 'https://foo.com/bar/', root: 'https://foo.com/bar/',
extname: '.html' extname: '.html'
}) })
server.respondWith('GET', 'https://foo.com/bar/hello.html', server.respondWith('GET', 'https://foo.com/bar/hello.html',
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}']) [200, { 'Content-Type': 'text/plain' }, 'hello {{name}}'])
const html = await engine.renderFile('hello.html', { name: 'alice5' }) 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', () => { describe('cache options', () => {
@@ -131,15 +128,15 @@ describe('xhr', () => {
[200, { 'Content-Type': 'text/plain' }, 'foo1']) [200, { 'Content-Type': 'text/plain' }, 'foo1'])
return engine.renderFile('foo.html') return engine.renderFile('foo.html')
.then((html: string) => { .then((html: string) => {
expect(html).to.equal('foo1') expect(html).toBe('foo1')
server.respondWith('GET', 'https://example.com/views/foo.html', server.respondWith('GET', 'https://example.com/views/foo.html',
[200, { 'Content-Type': 'text/plain' }, 'foo2']) [200, { 'Content-Type': 'text/plain' }, 'foo2'])
return engine.renderFile('foo.html') 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', () => { it('should respect cache=true option', () => {
engine = new Liquid({ engine = new LiquidUMD({
root: '/views/', root: '/views/',
extname: '.html', extname: '.html',
cache: true cache: true
@@ -148,12 +145,12 @@ describe('xhr', () => {
[200, { 'Content-Type': 'text/plain' }, 'foo1']) [200, { 'Content-Type': 'text/plain' }, 'foo1'])
return engine.renderFile('foo.html') return engine.renderFile('foo.html')
.then((html: string) => { .then((html: string) => {
expect(html).to.equal('foo1') expect(html).toBe('foo1')
server.respondWith('GET', 'https://example.com/views/foo.html', server.respondWith('GET', 'https://example.com/views/foo.html',
[200, { 'Content-Type': 'text/plain' }, 'foo2']) [200, { 'Content-Type': 'text/plain' }, 'foo2'])
return engine.renderFile('foo.html') 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' import { Liquid } from '../../../src/liquid'
describe('drop/blank-drop', function () { describe('drop/blank-drop', function () {
let liquid: Liquid let liquid: Liquid
before(() => (liquid = new Liquid())) beforeEach(() => (liquid = new Liquid()))
it('render blank drop as blank string', async function () { it('render blank drop as blank string', async function () {
const html = await liquid.parseAndRender('{{blank}}') const html = await liquid.parseAndRender('{{blank}}')
expect(html).to.equal('') expect(html).toBe('')
}) })
it('blank equals nil', async function () { it('blank equals nil', async function () {
const src = '{%if blank == nil %}blank == nil{%else%}blank != nil{% endif %}' const src = '{%if blank == nil %}blank == nil{%else%}blank != nil{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('blank == nil') expect(html).toBe('blank == nil')
}) })
it('false is blank', async function () { it('false is blank', async function () {
const src = '{%if false == blank %}false == blank{%else%}false != blank{% endif %}' const src = '{%if false == blank %}false == blank{%else%}false != blank{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('false == blank') expect(html).toBe('false == blank')
}) })
it('"" is blank', async function () { it('"" is blank', async function () {
const src = '{%if "" == blank %}"" == blank{%else%}"" != blank{% endif %}' const src = '{%if "" == blank %}"" == blank{%else%}"" != blank{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('"" == blank') expect(html).toBe('"" == blank')
}) })
it('" " is blank', async function () { it('" " is blank', async function () {
const src = '{%if " " == blank %}" " == blank{%else%}" " != blank{% endif %}' const src = '{%if " " == blank %}" " == blank{%else%}" " != blank{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('" " == blank') expect(html).toBe('" " == blank')
}) })
it('{} is blank', async function () { it('{} is blank', async function () {
const src = '{%if obj == blank %}{} == blank{%else%}{} != blank{% endif %}' const src = '{%if obj == blank %}{} == blank{%else%}{} != blank{% endif %}'
const html = await liquid.parseAndRender(src, { obj: {} }) const html = await liquid.parseAndRender(src, { obj: {} })
expect(html).to.equal('{} == blank') expect(html).toBe('{} == blank')
}) })
it('{foo: 1} is not blank', async function () { it('{foo: 1} is not blank', async function () {
const src = '{%if obj == blank %}{foo: 1} == blank{%else%}{foo: 1} != blank{% endif %}' const src = '{%if obj == blank %}{foo: 1} == blank{%else%}{foo: 1} != blank{% endif %}'
const html = await liquid.parseAndRender(src, { obj: { foo: 1 } }) 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 () { it('[] is blank', async function () {
const src = '{%if arr == blank %}[] == blank{%else%}[] != blank{% endif %}' const src = '{%if arr == blank %}[] == blank{%else%}[] != blank{% endif %}'
const html = await liquid.parseAndRender(src, { arr: [] }) const html = await liquid.parseAndRender(src, { arr: [] })
expect(html).to.equal('[] == blank') expect(html).toBe('[] == blank')
}) })
it('[1] is not blank', async function () { it('[1] is not blank', async function () {
const src = '{%if arr == blank %}[1] == blank{%else%}[1] != blank{% endif %}' const src = '{%if arr == blank %}[1] == blank{%else%}[1] != blank{% endif %}'
const html = await liquid.parseAndRender(src, { arr: [1] }) 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' import { Liquid, Drop } from '../../../src'
describe('drop/drop', function () { describe('drop/drop', function () {
let liquid: Liquid let liquid: Liquid
before(() => (liquid = new Liquid())) beforeEach(() => (liquid = new Liquid()))
class CustomDrop extends Drop { class CustomDrop extends Drop {
private name = 'NAME' private name = 'NAME'
@@ -27,39 +26,39 @@ describe('drop/drop', function () {
} }
it('should call corresponding method when output', async function () { it('should call corresponding method when output', async function () {
const html = await liquid.parseAndRender(`{{obj.getName}}`, { obj: new CustomDrop() }) 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 () { 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() }) 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 () { it('should read corresponding property', async function () {
const html = await liquid.parseAndRender(`{{obj.name}}`, { obj: new CustomDrop() }) 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 () { it('should output empty string if not exist', async function () {
const html = await liquid.parseAndRender(`{{obj.foo}}`, { obj: new CustomDrop() }) const html = await liquid.parseAndRender(`{{obj.foo}}`, { obj: new CustomDrop() })
expect(html).to.equal('') expect(html).toBe('')
}) })
it('should respect liquidMethodMissing', async function () { it('should respect liquidMethodMissing', async function () {
const html = await liquid.parseAndRender(`{{obj.foo}}`, { obj: new CustomDropWithMethodMissing() }) 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 () { it('should call corresponding promise method', async function () {
const html = await liquid.parseAndRender(`{{obj.getName}}`, { obj: new PromiseDrop() }) 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 () { it('should read corresponding promise property', async function () {
const html = await liquid.parseAndRender(`{{obj.name}}`, { obj: new PromiseDrop() }) 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 () { it('should resolve before calling filters', async function () {
const html = await liquid.parseAndRender(`{{obj.name | downcase}}`, { obj: new PromiseDrop() }) 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 () { it('should support promise returned by liquidMethodMissing', async function () {
const html = await liquid.parseAndRender(`{{obj.foo}}`, { obj: new PromiseDrop() }) const html = await liquid.parseAndRender(`{{obj.foo}}`, { obj: new PromiseDrop() })
expect(html).to.equal('FOO') expect(html).toBe('FOO')
}) })
it('should respect valueOf', async () => { it('should respect valueOf', async () => {
class CustomDrop extends Drop { class CustomDrop extends Drop {
@@ -70,7 +69,7 @@ describe('drop/drop', function () {
} }
const tpl = '{{drop}}: {% for field in drop %}{{ field }};{% endfor %}' const tpl = '{{drop}}: {% for field in drop %}{{ field }};{% endfor %}'
const html = await liquid.parseAndRender(tpl, { drop: new CustomDrop() }) 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 () => { it('should support valueOf in == expression', async () => {
class AddressDrop extends Drop { class AddressDrop extends Drop {
@@ -82,6 +81,6 @@ describe('drop/drop', function () {
const customer = { default_address: new AddressDrop() } const customer = { default_address: new AddressDrop() }
const tpl = `{% if address == customer.default_address %}{{address}}{% endif %}` const tpl = `{% if address == customer.default_address %}{{address}}{% endif %}`
const html = await liquid.parseAndRender(tpl, { address, customer }) 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' import { Liquid } from '../../../src/liquid'
describe('drop/empty-drop', function () { describe('drop/empty-drop', function () {
let liquid: Liquid let liquid: Liquid
before(() => (liquid = new Liquid())) beforeEach(() => (liquid = new Liquid()))
it('render empty drop as empty string', async function () { it('render empty drop as empty string', async function () {
const html = await liquid.parseAndRender('{{empty}}') const html = await liquid.parseAndRender('{{empty}}')
expect(html).to.equal('') expect(html).toBe('')
}) })
it('nil is not empty', async function () { it('nil is not empty', async function () {
const src = '{%if nil == empty %}nil == empty{%else%}nil != empty{% endif %}' const src = '{%if nil == empty %}nil == empty{%else%}nil != empty{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('nil != empty') expect(html).toBe('nil != empty')
}) })
it('false is not empty', async function () { it('false is not empty', async function () {
const src = '{%if false == empty %}false == empty{%else%}false != empty{% endif %}' const src = '{%if false == empty %}false == empty{%else%}false != empty{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('false != empty') expect(html).toBe('false != empty')
}) })
it('"" is empty', async function () { it('"" is empty', async function () {
const src = '{%if "" == empty %}"" == empty{%else%}"" != empty{% endif %}' const src = '{%if "" == empty %}"" == empty{%else%}"" != empty{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('"" == empty') expect(html).toBe('"" == empty')
}) })
it('" " is not empty', async function () { it('" " is not empty', async function () {
const src = '{%if " " == empty %}" " == empty{%else%}" " != empty{% endif %}' const src = '{%if " " == empty %}" " == empty{%else%}" " != empty{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('" " != empty') expect(html).toBe('" " != empty')
}) })
it('{} is empty', async function () { it('{} is empty', async function () {
const src = '{%if obj == empty %}{} == empty{%else%}{} != empty{% endif %}' const src = '{%if obj == empty %}{} == empty{%else%}{} != empty{% endif %}'
const html = await liquid.parseAndRender(src, { obj: {} }) const html = await liquid.parseAndRender(src, { obj: {} })
expect(html).to.equal('{} == empty') expect(html).toBe('{} == empty')
}) })
it('{foo: 1} is not empty', async function () { it('{foo: 1} is not empty', async function () {
const src = '{%if obj == empty %}{foo: 1} == empty{%else%}{foo: 1} != empty{% endif %}' const src = '{%if obj == empty %}{foo: 1} == empty{%else%}{foo: 1} != empty{% endif %}'
const html = await liquid.parseAndRender(src, { obj: { foo: 1 } }) 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 () { it('[] is empty', async function () {
const src = '{%if arr == empty %}[] == empty{%else%}[] != empty{% endif %}' const src = '{%if arr == empty %}[] == empty{%else%}[] != empty{% endif %}'
const html = await liquid.parseAndRender(src, { arr: [] }) const html = await liquid.parseAndRender(src, { arr: [] })
expect(html).to.equal('[] == empty') expect(html).toBe('[] == empty')
}) })
it('[1] is not empty', async function () { it('[1] is not empty', async function () {
const src = '{%if arr == empty %}[1] == empty{%else%}[1] != empty{% endif %}' const src = '{%if arr == empty %}[1] == empty{%else%}[1] != empty{% endif %}'
const html = await liquid.parseAndRender(src, { arr: [1] }) 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 () { it('1 < empty should be false', async function () {
const src = '{%if 1 < empty %}true{%else%}false{% endif %}' const src = '{%if 1 < empty %}true{%else%}false{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('false') expect(html).toBe('false')
}) })
it('1 <= empty should be false', async function () { it('1 <= empty should be false', async function () {
const src = '{%if 1 <= empty %}true{%else%}false{% endif %}' const src = '{%if 1 <= empty %}true{%else%}false{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('false') expect(html).toBe('false')
}) })
it('1 > empty should be false', async function () { it('1 > empty should be false', async function () {
const src = '{%if 1 > empty %}true{%else%}false{% endif %}' const src = '{%if 1 > empty %}true{%else%}false{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('false') expect(html).toBe('false')
}) })
it('1 >= empty should be false', async function () { it('1 >= empty should be false', async function () {
const src = '{%if 1 >= empty %}true{%else%}false{% endif %}' const src = '{%if 1 >= empty %}true{%else%}false{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('false') expect(html).toBe('false')
}) })
it('1 == empty should be false', async function () { it('1 == empty should be false', async function () {
const src = '{%if 1 == empty %}true{%else%}false{% endif %}' const src = '{%if 1 == empty %}true{%else%}false{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('false') expect(html).toBe('false')
}) })
it('1 != empty should be true', async function () { it('1 != empty should be true', async function () {
const src = '{%if 1 != empty %}true{%else%}false{% endif %}' const src = '{%if 1 != empty %}true{%else%}false{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('true') expect(html).toBe('true')
}) })
it('empty != empty', async function () { it('empty != empty', async function () {
const src = '{%if empty == empty %}true{%else%}false{% endif %}' const src = '{%if empty == empty %}true{%else%}false{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('false') expect(html).toBe('false')
}) })
it('empty != nil', async function () { it('empty != nil', async function () {
const src = '{%if empty == nil %}true{%else%}false{% endif %}' const src = '{%if empty == nil %}true{%else%}false{% endif %}'
const html = await liquid.parseAndRender(src) 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' import { Liquid } from '../../../src/liquid'
describe('drop/null-drop', function () { describe('drop/null-drop', function () {
let liquid: Liquid let liquid: Liquid
before(() => (liquid = new Liquid())) beforeEach(() => (liquid = new Liquid()))
it('render nil as empty string', async function () { it('render nil as empty string', async function () {
const html = await liquid.parseAndRender('{{nil}}') const html = await liquid.parseAndRender('{{nil}}')
expect(html).to.equal('') expect(html).toBe('')
}) })
it('render null as empty string', async function () { it('render null as empty string', async function () {
const html = await liquid.parseAndRender('{{null}}') const html = await liquid.parseAndRender('{{null}}')
expect(html).to.equal('') expect(html).toBe('')
}) })
it('undefined == null', async function () { it('undefined == null', async function () {
const src = '{%if foo == nil %}foo == nil{%else%}foo != nil{% endif %}' const src = '{%if foo == nil %}foo == nil{%else%}foo != nil{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('foo == nil') expect(html).toBe('foo == nil')
}) })
it('nil != blank', async function () { it('nil != blank', async function () {
const src = '{%if nil == blank %}eq{%else%}neq{% endif %}' const src = '{%if nil == blank %}eq{%else%}neq{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('neq') expect(html).toBe('neq')
}) })
it('nil != empty', async function () { it('nil != empty', async function () {
const src = '{%if nil == empty %}eq{%else%}neq{% endif %}' const src = '{%if nil == empty %}eq{%else%}neq{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('neq') expect(html).toBe('neq')
}) })
it('0 != null', async function () { it('0 != null', async function () {
const src = '{%if 0 == null %}0 == null{%else%}0 != null{% endif %}' const src = '{%if 0 == null %}0 == null{%else%}0 != null{% endif %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
expect(html).to.equal('0 != null') expect(html).toBe('0 != null')
}) })
it('nil == null', async function () { it('nil == null', async function () {
const src = '{%if nil == null %}nil == null{%else%}nil != null{% endif %}' const src = '{%if nil == null %}nil == null{%else%}nil != null{% endif %}'
const html = await liquid.parseAndRender(src) 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 { test, render } from '../../stub/render'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('filters/array', function () { describe('filters/array', function () {
describe('index', function () { describe('index', function () {
@@ -25,7 +22,7 @@ describe('filters/array', function () {
it('should throw when comma missing', async () => { it('should throw when comma missing', async () => {
const src = '{% assign beatles = "John, Paul, George, Ringo" | split: ", " %}' + const src = '{% assign beatles = "John, Paul, George, Ringo" | split: ", " %}' +
'{{ beatles | join " and " }}' '{{ 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', () => { describe('last', () => {
@@ -104,7 +101,7 @@ describe('filters/array', function () {
const scope = { arr: ['a', 'b', 'c'] } const scope = { arr: ['a', 'b', 'c'] }
await render('{{ arr | reverse | join: "" }}', scope) await render('{{ arr | reverse | join: "" }}', scope)
const html = await render('{{ arr | join: "" }}', scope) const html = await render('{{ arr | join: "" }}', scope)
expect(html).to.equal('abc') expect(html).toBe('abc')
}) })
}) })
describe('size', function () { describe('size', function () {
@@ -1,5 +1,4 @@
import { test } from '../../stub/render' import { test } from '../../stub/render'
import { expect } from 'chai'
import { Liquid } from '../../../src/liquid' import { Liquid } from '../../../src/liquid'
describe('filters/html', function () { describe('filters/html', function () {
@@ -50,24 +49,24 @@ describe('filters/html', function () {
'Ulysses?') 'Ulysses?')
}) })
it('should strip multiline comments', function () { 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 () { it('should strip all style tags and their contents', function () {
return test('{{ "<style>cite { font-style: italic; }</style><cite>Ulysses<cite>?" | strip_html }}', return test('{{ "<style>cite { font-style: italic; }</style><cite>Ulysses<cite>?" | strip_html }}',
'Ulysses?') 'Ulysses?')
}) })
it('should strip multiline styles', function () { 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 () { it('should strip all scripts tags and their contents', function () {
return test('{{ "<script async>console.log(\'hello world\')</script><cite>Ulysses<cite>?" | strip_html }}', return test('{{ "<script async>console.log(\'hello world\')</script><cite>Ulysses<cite>?" | strip_html }}',
'Ulysses?') 'Ulysses?')
}) })
it('should strip multiline scripts', function () { 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 () { 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 () { it('should strip until empty', function () {
return test('{{"<br/><br />< p ></p></ p >" | strip_html }}', '') return test('{{"<br/><br />< p ></p></ p >" | strip_html }}', '')
@@ -1,4 +1,3 @@
import { expect } from 'chai'
import { test, liquid } from '../../stub/render' import { test, liquid } from '../../stub/render'
describe('filters/math', function () { 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" for 24,7', () => test('{{ 24 | modulo: 7 }}', '3'))
it('should return "3.357" for 183.357,12', async () => { it('should return "3.357" for 183.357,12', async () => {
const html = await liquid.parseAndRender('{{ 183.357 | modulo: 12 }}') 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')) it('should convert string', () => test('{{ "24" | modulo: "7" }}', '3'))
}) })
@@ -1,32 +1,28 @@
import { Liquid } from '../../../src/liquid' import { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('filters/object', function () { describe('filters/object', function () {
const liquid = new Liquid() const liquid = new Liquid()
describe('default', function () { describe('default', function () {
it('false should use default', async () => expect(await liquid.parseAndRender('{{false | default: "a"}}')).to.equal('a')) 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"}}')).to.equal('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: [] })).to.equal('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"}}')).to.equal(' ')) 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"}}')).to.equal('a')) 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"}}')).to.equal('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"}}')).to.equal('true')) 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"}}')).to.equal('0')) 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}}')).to.equal('false')) 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}}')).to.equal('true')) 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}}')).to.equal('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', () => { it('should throw for additional args', () => {
const src = `{{ age | default: 'now' date: '%d'}}` // missing `|` before `date` 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 () { describe('json', function () {
it('should stringify string', async () => expect(await liquid.parseAndRender('{{"foo" | json}}')).to.equal('"foo"')) it('should stringify string', async () => expect(await liquid.parseAndRender('{{"foo" | json}}')).toBe('"foo"'))
it('should stringify number', async () => expect(await liquid.parseAndRender('{{2 | json}}')).to.equal('2')) 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' } })).to.equal('{"foo":"bar"}')) 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'] })).to.equal('[-2,"a"]')) 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 { test } from '../../stub/render'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('filters/string', function () { describe('filters/string', function () {
describe('append', function () { describe('append', function () {
@@ -10,7 +6,7 @@ describe('filters/string', function () {
() => test('{{ -3 | append: "abc" }}', '-3abc')) () => test('{{ -3 | append: "abc" }}', '-3abc'))
it('should return "abar" for "a", foo', () => test('{{ "a" | append: foo }}', { foo: 'bar' }, 'abar')) it('should return "abar" for "a", foo', () => test('{{ "a" | append: foo }}', { foo: 'bar' }, 'abar'))
it('should throw if second argument not set', () => { 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')) 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')) () => test('{{ -3 | prepend: "abc" }}', 'abc-3'))
it('should return "abar" for "a", foo', () => test('{{ "a" | prepend: foo }}', { foo: 'bar' }, 'bara')) it('should return "abar" for "a", foo', () => test('{{ "a" | prepend: foo }}', { foo: 'bar' }, 'bara'))
it('should throw if second argument not set', () => { 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')) it('should return "falseabc" for "abc", false', () => test('{{ "abc" | prepend: false }}', 'falseabc'))
}) })
@@ -1,6 +1,6 @@
import { expect } from 'chai' import { Liquid } from '../../../src/liquid'
import { Liquid, Template } from '../../../src/liquid'
import { mock, restore } from '../../stub/mockfs' import { mock, restore } from '../../stub/mockfs'
import { Template } from '../../../src/template'
describe('LiquidOptions#cache', function () { describe('LiquidOptions#cache', function () {
afterEach(restore) afterEach(restore)
@@ -13,10 +13,10 @@ describe('LiquidOptions#cache', function () {
}) })
mock({ '/root/files/foo.html': 'foo' }) mock({ '/root/files/foo.html': 'foo' })
const x = await engine.renderFile('files/foo') const x = await engine.renderFile('files/foo')
expect(x).to.equal('foo') expect(x).toBe('foo')
mock({ '/root/files/foo.html': 'bar' }) mock({ '/root/files/foo.html': 'bar' })
const y = await engine.renderFile('files/foo') 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 () { it('should be disabled when cache <= 0', async function () {
const engine = new Liquid({ const engine = new Liquid({
@@ -26,10 +26,10 @@ describe('LiquidOptions#cache', function () {
}) })
mock({ '/root/files/foo.html': 'foo' }) mock({ '/root/files/foo.html': 'foo' })
const x = await engine.renderFile('files/foo') const x = await engine.renderFile('files/foo')
expect(x).to.equal('foo') expect(x).toBe('foo')
mock({ '/root/files/foo.html': 'bar' }) mock({ '/root/files/foo.html': 'bar' })
const y = await engine.renderFile('files/foo') const y = await engine.renderFile('files/foo')
expect(y).to.equal('bar') expect(y).toBe('bar')
}) })
it('should respect cache=true option', async function () { it('should respect cache=true option', async function () {
const engine = new Liquid({ const engine = new Liquid({
@@ -39,10 +39,10 @@ describe('LiquidOptions#cache', function () {
}) })
mock({ '/root/files/foo.html': 'foo' }) mock({ '/root/files/foo.html': 'foo' })
const x = await engine.renderFile('files/foo') const x = await engine.renderFile('files/foo')
expect(x).to.equal('foo') expect(x).toBe('foo')
mock({ '/root/files/foo.html': 'bar' }) mock({ '/root/files/foo.html': 'bar' })
const y = await engine.renderFile('files/foo') const y = await engine.renderFile('files/foo')
expect(y).to.equal('foo') expect(y).toBe('foo')
}) })
it('should respect cache=2 option', async function () { it('should respect cache=2 option', async function () {
const engine = new Liquid({ const engine = new Liquid({
@@ -57,12 +57,12 @@ describe('LiquidOptions#cache', function () {
mock({ '/root/files/foo.html': 'FOO' }) mock({ '/root/files/foo.html': 'FOO' })
await engine.renderFile('files/bar') await engine.renderFile('files/bar')
const x = await engine.renderFile('files/foo') 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/bar')
await engine.renderFile('files/coo') await engine.renderFile('files/coo')
const y = await engine.renderFile('files/foo') 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 () { it('should respect cache={read, write} option', async function () {
let last: Template[] | undefined let last: Template[] | undefined
@@ -70,6 +70,7 @@ describe('LiquidOptions#cache', function () {
root: '/root/', root: '/root/',
extname: '.html', extname: '.html',
cache: { cache: {
remove: () => void (0),
read: (): Template[] | undefined => last, read: (): Template[] | undefined => last,
write: (key: string, value: Template[]) => { last = value } write: (key: string, value: Template[]) => { last = value }
} }
@@ -77,9 +78,9 @@ describe('LiquidOptions#cache', function () {
mock({ '/root/files/foo.html': 'foo' }) mock({ '/root/files/foo.html': 'foo' })
mock({ '/root/files/bar.html': 'bar' }) mock({ '/root/files/bar.html': 'bar' })
mock({ '/root/files/coo.html': 'coo' }) mock({ '/root/files/coo.html': 'coo' })
expect(await engine.renderFile('files/foo')).to.equal('foo') expect(await engine.renderFile('files/foo')).toBe('foo')
expect(await engine.renderFile('files/bar')).to.equal('foo') expect(await engine.renderFile('files/bar')).toBe('foo')
expect(await engine.renderFile('files/coo')).to.equal('foo') expect(await engine.renderFile('files/coo')).toBe('foo')
}) })
it('should respect cache={ async read, async write } option', async function () { it('should respect cache={ async read, async write } option', async function () {
const cached: { [key: string]: Template[] | undefined } = {} const cached: { [key: string]: Template[] | undefined } = {}
@@ -87,6 +88,7 @@ describe('LiquidOptions#cache', function () {
root: '/root/', root: '/root/',
extname: '.html', extname: '.html',
cache: { cache: {
remove: (key: string) => { delete cached[key] },
read: (key: string) => Promise.resolve(cached[key]), read: (key: string) => Promise.resolve(cached[key]),
write: (key: string, value: Template[]) => { cached[key] = value; Promise.resolve() } 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/foo.html': 'foo' })
mock({ '/root/files/bar.html': 'bar' }) mock({ '/root/files/bar.html': 'bar' })
mock({ '/root/files/coo.html': 'coo' }) mock({ '/root/files/coo.html': 'coo' })
expect(await engine.renderFile('files/foo')).to.equal('foo') expect(await engine.renderFile('files/foo')).toBe('foo')
expect(await engine.renderFile('files/bar')).to.equal('bar') expect(await engine.renderFile('files/bar')).toBe('bar')
expect(await engine.renderFile('files/coo')).to.equal('coo') expect(await engine.renderFile('files/coo')).toBe('coo')
mock({ '/root/files/coo.html': '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 () { it('should handle concurrent cache read/write', async function () {
const engine = new Liquid({ const engine = new Liquid({
@@ -115,10 +117,10 @@ describe('LiquidOptions#cache', function () {
engine.renderFile('files/bar'), engine.renderFile('files/bar'),
engine.renderFile('files/coo') engine.renderFile('files/coo')
]) ])
expect(foo1).to.equal('foo') expect(foo1).toBe('foo')
expect(foo2).to.equal('foo') expect(foo2).toBe('foo')
expect(bar).to.equal('bar') expect(bar).toBe('bar')
expect(coo).to.equal('coo') expect(coo).toBe('coo')
}) })
it('should not cache not exist file', async function () { it('should not cache not exist file', async function () {
const engine = new Liquid({ const engine = new Liquid({
@@ -132,7 +134,7 @@ describe('LiquidOptions#cache', function () {
mock({ '/root/foo.html': 'foo' }) mock({ '/root/foo.html': 'foo' })
const html = await engine.renderFile('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' }) mock({ '/root/foo.html': 'foo' })
const x = engine.renderFileSync('foo') const x = engine.renderFileSync('foo')
expect(x).to.equal('foo') expect(x).toBe('foo')
mock({ '/root/foo.html': 'bar' }) mock({ '/root/foo.html': 'bar' })
const y = engine.renderFileSync('foo') const y = engine.renderFileSync('foo')
expect(y).to.equal('bar') expect(y).toBe('bar')
}) })
it('should respect cache=true option', function () { it('should respect cache=true option', function () {
const engine = new Liquid({ const engine = new Liquid({
@@ -157,9 +159,9 @@ describe('LiquidOptions#cache', function () {
cache: true cache: true
}) })
mock({ '/root/foo.html': 'foo' }) mock({ '/root/foo.html': 'foo' })
expect(engine.renderFileSync('foo')).to.equal('foo') expect(engine.renderFileSync('foo')).toBe('foo')
mock({ '/root/foo.html': 'bar' }) 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 () { it('should not cache not exist file', async function () {
const engine = new Liquid({ const engine = new Liquid({
@@ -171,7 +173,7 @@ describe('LiquidOptions#cache', function () {
mock({ '/root/foo.html': 'foo' }) mock({ '/root/foo.html': 'foo' })
const y = await engine.renderFile('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 () { it('should cache relative referenced files properly', async function () {
const engine = new Liquid({ const engine = new Liquid({
@@ -186,10 +188,10 @@ describe('LiquidOptions#cache', function () {
'/root/another/bar.html': 'bar2' '/root/another/bar.html': 'bar2'
}) })
const foo1 = await engine.renderFile('foo') const foo1 = await engine.renderFile('foo')
expect(foo1).to.equal('bar1') expect(foo1).toBe('bar1')
const foo2 = await engine.renderFile('another/foo') 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' import { Liquid } from '../../../src/liquid'
describe('LiquidOptions#*_delimiter_*', function () { describe('LiquidOptions#*_delimiter_*', function () {
@@ -8,7 +7,7 @@ describe('LiquidOptions#*_delimiter_*', function () {
tagDelimiterRight: '%>' tagDelimiterRight: '%>'
}) })
const html = await engine.parseAndRender('<%=if true%>foo<%=endif%> ') 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 () { it('should respect output_delimiter_*', async function () {
const engine = new Liquid({ const engine = new Liquid({
@@ -16,7 +15,7 @@ describe('LiquidOptions#*_delimiter_*', function () {
outputDelimiterRight: '>>' outputDelimiterRight: '>>'
}) })
const html = await engine.parseAndRender('<< "liquid" | capitalize >>') 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 () { it('should support trimming with tag_delimiter_* set', async function () {
const engine = new Liquid({ const engine = new Liquid({
@@ -26,6 +25,6 @@ describe('LiquidOptions#*_delimiter_*', function () {
trimTagRight: true trimTagRight: true
}) })
const html = await engine.parseAndRender(' <%=if true%> \tfoo\t <%=endif%> ') 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 { Liquid } from '../../../src/liquid'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('LiquidOptions#fs', function () { describe('LiquidOptions#fs', function () {
let engine: Liquid let engine: Liquid
const fs = { const fs = {
sep: '/',
dirname: (x: string) => x.split('/').slice(0, -1).join('/'),
exists: (x: string) => Promise.resolve(!x.match(/not-exist/)), exists: (x: string) => Promise.resolve(!x.match(/not-exist/)),
existsSync: (x: string) => !x.match(/not-exist/), existsSync: (x: string) => !x.match(/not-exist/),
readFile: (x: string) => Promise.resolve(`content for ${x}`), 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 () { it('should be used to read templates', async function () {
const html = await engine.renderFile('files/foo') 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 () { it('should support fallback', async function () {
const html = await engine.renderFile('not-exist/foo') 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 () { it('should support renderSync', function () {
const html = engine.renderFileSync('not-exist/foo') 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 () { it('should throw lookup failure if fallback not specified', function () {
@@ -40,6 +39,14 @@ describe('LiquidOptions#fs', function () {
fs: { ...fs, fallback: undefined } fs: { ...fs, fallback: undefined }
} as any) } as any)
return expect(engine.renderFile('not-exist/foo')) 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' import { Liquid } from '../../../src/liquid'
describe('LiquidOptions#*keepOutputType*', function () { describe('LiquidOptions#*keepOutputType*', function () {
@@ -12,13 +11,13 @@ describe('LiquidOptions#*keepOutputType*', function () {
'my-string': 'test' 'my-string': 'test'
} }
const booleanHtml = await engine.parseAndRender('{{my-boolean}}', context) 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) 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) 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) 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 () { it('should respect keepOutputType = false as default', async function () {
@@ -29,12 +28,12 @@ describe('LiquidOptions#*keepOutputType*', function () {
'my-string': 'test' 'my-string': 'test'
} }
const booleanHtml = await engine.parseAndRender('{{my-boolean}}', context) 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) 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) 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) 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 { Liquid, Context, isFalsy } from '../../../src'
import { mock, restore } from '../../stub/mockfs' import { mock, restore } from '../../stub/mockfs'
import { drainStream } from '../../stub/stream' import { drainStream } from '../../stub/stream'
const expect = chai.expect
chai.use(chaiAsPromised)
describe('Liquid', function () { describe('Liquid', function () {
describe('#plugin()', function () { describe('#plugin()', function () {
it('should call plugin on the instance', async function () { it('should call plugin on the instance', async function () {
@@ -15,7 +10,7 @@ describe('Liquid', function () {
this.registerFilter('foo', x => `foo${x}foo`) this.registerFilter('foo', x => `foo${x}foo`)
}) })
const html = await engine.parseAndRender('{{"bar"|foo}}') const html = await engine.parseAndRender('{{"bar"|foo}}')
expect(html).to.equal('foobarfoo') expect(html).toBe('foobarfoo')
}) })
it('should call plugin with Liquid', async function () { it('should call plugin with Liquid', async function () {
const engine = new Liquid() const engine = new Liquid()
@@ -23,92 +18,92 @@ describe('Liquid', function () {
this.registerFilter('t', function (v) { return isFalsy(v, this.context) }) this.registerFilter('t', function (v) { return isFalsy(v, this.context) })
}) })
const html = await engine.parseAndRender('{{false|t}}') const html = await engine.parseAndRender('{{false|t}}')
expect(html).to.equal('true') expect(html).toBe('true')
}) })
}) })
describe('#parseAndRender', function () { describe('#parseAndRender', function () {
const engine = new Liquid() const engine = new Liquid()
it('should parse and render variable output', async function () { it('should parse and render variable output', async function () {
const html = await engine.parseAndRender('{{"foo"}}') const html = await engine.parseAndRender('{{"foo"}}')
expect(html).to.equal('foo') expect(html).toBe('foo')
}) })
it('should parse and render complex output', async function () { it('should parse and render complex output', async function () {
const tpl = '{{ "Welcome|to]Liquid" | split: "|" | join: "("}}' const tpl = '{{ "Welcome|to]Liquid" | split: "|" | join: "("}}'
const html = await engine.parseAndRender(tpl) 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 () { it('should support for-in with variable', async function () {
const src = '{% assign total = 3 | minus: 1 %}' + const src = '{% assign total = 3 | minus: 1 %}' +
'{% for i in (1..total) %}{{ i }}{% endfor %}' '{% for i in (1..total) %}{{ i }}{% endfor %}'
const html = await engine.parseAndRender(src, {}) 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 () { it('should support `globals` render option', async function () {
const src = '{{ foo }}' const src = '{{ foo }}'
const html = await engine.parseAndRender(src, {}, { globals: { foo: '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 () { it('should support `strictVariables` render option', function () {
const src = '{{ foo }}' 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 () => { it('should support async variables in output', async () => {
const src = '{{ foo }}' const src = '{{ foo }}'
const html = await engine.parseAndRender(src, { foo: Promise.resolve('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 () { it('should parse and render with Context', async function () {
const html = await engine.parseAndRender('{{foo}}', new Context({ foo: 'FOO' })) const html = await engine.parseAndRender('{{foo}}', new Context({ foo: 'FOO' }))
expect(html).to.equal('FOO') expect(html).toBe('FOO')
}) })
}) })
describe('#parseAndRenderSync', function () { describe('#parseAndRenderSync', function () {
const engine = new Liquid() const engine = new Liquid()
it('should parse and render variable output', function () { it('should parse and render variable output', function () {
const html = engine.parseAndRenderSync('{{"foo"}}') const html = engine.parseAndRenderSync('{{"foo"}}')
expect(html).to.equal('foo') expect(html).toBe('foo')
}) })
it('should parse and render complex output', function () { it('should parse and render complex output', function () {
const tpl = '{{ "Welcome|to]Liquid" | split: "|" | join: "("}}' const tpl = '{{ "Welcome|to]Liquid" | split: "|" | join: "("}}'
const html = engine.parseAndRenderSync(tpl) 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 () { it('should support for-in with variable', function () {
const src = '{% assign total = 3 | minus: 1 %}' + const src = '{% assign total = 3 | minus: 1 %}' +
'{% for i in (1..total) %}{{ i }}{% endfor %}' '{% for i in (1..total) %}{{ i }}{% endfor %}'
const html = engine.parseAndRenderSync(src, {}) const html = engine.parseAndRenderSync(src, {})
return expect(html).to.equal('12') return expect(html).toBe('12')
}) })
it('should support `globals` render option', function () { it('should support `globals` render option', function () {
const src = '{{ foo }}' const src = '{{ foo }}'
const html = engine.parseAndRenderSync(src, {}, { globals: { foo: '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 () { it('should support `strictVariables` render option', function () {
const src = '{{ foo }}' 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 () { describe('#express()', function () {
const liquid = new Liquid({ root: '/root' }) const liquid = new Liquid({ root: '/root' })
const render = liquid.express() const render = liquid.express()
before(function () { beforeEach(function () {
mock({ mock({
'/root/foo': 'foo' '/root/foo': 'foo'
}) })
}) })
after(restore) afterEach(restore)
it('should render single template', function (done) { it('should render single template', function (done) {
render.call({ root: '/root' }, 'foo', null as any, (err: Error | null, result: string | undefined) => { render.call({ root: '/root' }, 'foo', null as any, (err: Error | null, result: string | undefined) => {
if (err) return done(err) if (err) return done(err)
expect(result).to.equal('foo') expect(result).toBe('foo')
done() done()
}) })
}) })
it('should render single template with Array-typed root', function (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) => { render.call({ root: ['/root'] }, 'foo', null as any, (err: Error | null, result: string | undefined) => {
if (err) return done(err) if (err) return done(err)
expect(result).to.equal('foo') expect(result).toBe('foo')
done() done()
}) })
}) })
@@ -119,8 +114,7 @@ describe('Liquid', function () {
root: ['/boo', '/root/'], root: ['/boo', '/root/'],
extname: '.html' extname: '.html'
}) })
return expect(engine.renderFile('/not/exist.html')).to return expect(engine.renderFile('/not/exist.html')).rejects.toThrow(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
.be.rejectedWith(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
}) })
}) })
describe('#parseFile', function () { describe('#parseFile', function () {
@@ -129,17 +123,16 @@ describe('Liquid', function () {
root: ['/boo', '/root/'], root: ['/boo', '/root/'],
extname: '.html' extname: '.html'
}) })
return expect(engine.parseFile('/not/exist.html')).to return expect(engine.parseFile('/not/exist.html')).rejects.toThrow(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
.be.rejectedWith(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
}) })
it('should fallback to require.resolve in Node.js', async function () { it('should fallback to require.resolve in Node.js', async function () {
const engine = new Liquid({ const engine = new Liquid({
root: ['/root/'], root: ['/root/'],
extname: '.html' extname: '.html'
}) })
const tpls = await engine.parseFileSync('mocha') const tpls = await engine.parseFileSync('jest')
expect(tpls.length).to.gte(1) expect(tpls.length).toBeGreaterThanOrEqual(1)
expect(tpls[0].token.getText()).to.contain('module.exports') expect(tpls[0].token.getText()).toContain('use strict')
}) })
}) })
describe('#evalValue', function () { describe('#evalValue', function () {
@@ -147,12 +140,12 @@ describe('Liquid', function () {
const engine = new Liquid() const engine = new Liquid()
const ctx = new Context() const ctx = new Context()
const str = await engine.evalValue('"foo"', ctx) const str = await engine.evalValue('"foo"', ctx)
expect(str).to.equal('foo') expect(str).toBe('foo')
}) })
it('should support plain scope', async function () { it('should support plain scope', async function () {
const engine = new Liquid() const engine = new Liquid()
const str = await engine.evalValue('foo', { foo: 'FOO' }) const str = await engine.evalValue('foo', { foo: 'FOO' })
expect(str).to.equal('FOO') expect(str).toBe('FOO')
}) })
}) })
describe('#evalValueSync', function () { describe('#evalValueSync', function () {
@@ -160,7 +153,7 @@ describe('Liquid', function () {
const engine = new Liquid() const engine = new Liquid()
const ctx = new Context() const ctx = new Context()
const str = engine.evalValueSync('"foo"', ctx) const str = engine.evalValueSync('"foo"', ctx)
expect(str).to.equal('foo') expect(str).toBe('foo')
}) })
}) })
describe('#parse', function () { describe('#parse', function () {
@@ -173,7 +166,7 @@ describe('Liquid', function () {
'/root/partial.html': 'foo' '/root/partial.html': 'foo'
}) })
const tpls = engine.parse('{% render "./partial.html" %}', '/root/index.html') 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 () { it('should resolve against pwd for relative filepath', function () {
const engine = new Liquid({ const engine = new Liquid({
@@ -184,7 +177,7 @@ describe('Liquid', function () {
[`${process.cwd()}/partial.html`]: 'foo' [`${process.cwd()}/partial.html`]: 'foo'
}) })
const tpls = engine.parse('{% render "./partial.html" %}', './index.html') 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 () { describe('#parseFileSync', function () {
@@ -194,7 +187,7 @@ describe('Liquid', function () {
extname: '.html' extname: '.html'
}) })
return expect(() => engine.parseFileSync('/not/exist.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 () { it('should throw with lookup list when file not exist', function () {
const engine = new Liquid({ const engine = new Liquid({
@@ -202,19 +195,19 @@ describe('Liquid', function () {
extname: '.html' extname: '.html'
}) })
return expect(() => engine.parseFileSync('/not/exist.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 () { describe('#enderToNodeStream', function () {
const engine = new Liquid() const engine = new Liquid()
it('should render a simple value', async () => { it('should render a simple value', async () => {
const stream = engine.renderToNodeStream(engine.parse('{{"foo"}}')) const stream = engine.renderToNodeStream(engine.parse('{{"foo"}}'))
expect(drainStream(stream)).to.eventually.equal('foo') expect(drainStream(stream)).resolves.toBe('foo')
}) })
}) })
describe('#enderFileToNodeStream', function () { describe('#enderFileToNodeStream', function () {
let engine: Liquid let engine: Liquid
before(function () { beforeEach(function () {
mock({ mock({
'/root/foo.html': 'foo', '/root/foo.html': 'foo',
'/root/error.html': 'A{%throwingTag%}B' '/root/error.html': 'A{%throwingTag%}B'
@@ -226,14 +219,14 @@ describe('Liquid', function () {
} }
}) })
}) })
after(restore) afterEach(restore)
it('should render a simple value', async () => { it('should render a simple value', async () => {
const stream = await engine.renderFileToNodeStream('foo.html') 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 () => { it('should throw RenderError when tag throws', async () => {
const stream = await engine.renderFileToNodeStream('error.html') 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' import { Liquid, defaultOperators } from '../../../src'
describe('LiquidOptions#operators', function () { describe('LiquidOptions#operators', function () {
@@ -8,27 +7,27 @@ describe('LiquidOptions#operators', function () {
engine = new Liquid({ engine = new Liquid({
operators: { operators: {
...defaultOperators, ...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 () { it('should evaluate the default operators', async function () {
const result = await engine.parseAndRender('{% if "foo" == "foo" %}True{% endif %}') 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 () { it('should evaluate a custom operator', async function () {
const first = await engine.parseAndRender('{% if "foo" isFooBar "bar" %}True{% else %}False{% endif %}') 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 %}') 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 () { 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 %}') 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 %}') 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' import { Liquid } from '../../../src/liquid'
describe('LiquidOptions#*outputEscape*', function () { describe('LiquidOptions#*outputEscape*', function () {
it('when outputEscape is not set', async function () { it('when outputEscape is not set', async function () {
const engine = new Liquid() const engine = new Liquid()
const html = await engine.parseAndRender('{{"<"}}') const html = await engine.parseAndRender('{{"<"}}')
expect(html).to.equal('<') expect(html).toBe('<')
}) })
it('should escape when outputEscape="escape"', async function () { it('should escape when outputEscape="escape"', async function () {
@@ -13,7 +12,7 @@ describe('LiquidOptions#*outputEscape*', function () {
outputEscape: 'escape' outputEscape: 'escape'
}) })
const html = await engine.parseAndRender('{{"<"}}') const html = await engine.parseAndRender('{{"<"}}')
expect(html).to.equal('&lt;') expect(html).toBe('&lt;')
}) })
it('should json stringify when outputEscape="json"', async function () { it('should json stringify when outputEscape="json"', async function () {
@@ -21,7 +20,7 @@ describe('LiquidOptions#*outputEscape*', function () {
outputEscape: 'json' outputEscape: 'json'
}) })
const html = await engine.parseAndRender('{{"<"}}') const html = await engine.parseAndRender('{{"<"}}')
expect(html).to.equal('"<"') expect(html).toBe('"<"')
}) })
it('should support outputEscape=Function', async function () { it('should support outputEscape=Function', async function () {
@@ -29,7 +28,7 @@ describe('LiquidOptions#*outputEscape*', function () {
outputEscape: (v: any) => `{${v}}` outputEscape: (v: any) => `{${v}}`
}) })
const html = await engine.parseAndRender('{{"<"}}') const html = await engine.parseAndRender('{{"<"}}')
expect(html).to.equal('{<}') expect(html).toBe('{<}')
}) })
it('should skip escape for output with filter "| raw"', async function () { it('should skip escape for output with filter "| raw"', async function () {
@@ -37,6 +36,6 @@ describe('LiquidOptions#*outputEscape*', function () {
outputEscape: 'escape' outputEscape: 'escape'
}) })
const html = await engine.parseAndRender('{{"<" | raw}}') 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' import { Liquid } from '../../../src/liquid'
describe('liquid#registerFilter()', function () { describe('liquid#registerFilter()', function () {
@@ -15,13 +14,13 @@ describe('liquid#registerFilter()', function () {
const src = `{{ "a" | obj_test: k1: "v1", k2: foo }}` const src = `{{ "a" | obj_test: k1: "v1", k2: foo }}`
const dst = '["a",["k1","v1"],["k2","bar"]]' const dst = '["a",["k1","v1"],["k2","bar"]]'
const html = await liquid.parseAndRender(src, { foo: '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 () => { it('should support mixed arguments', async () => {
const src = `{{ "a" | obj_test: "something", k1: "v1", k2: foo }}` const src = `{{ "a" | obj_test: "something", k1: "v1", k2: foo }}`
const dst = '["a","something",["k1","v1"],["k2","bar"]]' const dst = '["a","something",["k1","v1"],["k2","bar"]]'
const html = await liquid.parseAndRender(src, { foo: '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 src = `{{ userId | get_user_data | json }}`
const dst = '{"userId":"alice","userName":"ALICE"}' const dst = '{"userId":"alice","userName":"ALICE"}'
const html = await liquid.parseAndRender(src, { userId: '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 src = `{{ "a\nb" | break }}`
const dst = 'a&lt;br/&gt;b' const dst = 'a&lt;br/&gt;b'
const html = await liquid.parseAndRender(src) 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 () => { it('should not escape filter output when registered as "raw"', async () => {
liquid.registerFilter('break', { liquid.registerFilter('break', {
@@ -58,7 +57,7 @@ describe('liquid#registerFilter()', function () {
const src = `{{ "a\nb" | break }}` const src = `{{ "a\nb" | break }}`
const dst = 'a<br/>b' const dst = 'a<br/>b'
const html = await liquid.parseAndRender(src) 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' import { Liquid } from '../../../src/liquid'
describe('liquid#registerTag()', function () { describe('liquid#registerTag()', function () {
@@ -8,7 +7,7 @@ describe('liquid#registerTag()', function () {
render: () => 'B' render: () => 'B'
}) })
const html = await liquid.parseAndRender(`A{% simple-string %}C`) 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 () => { it('should support async tag render', async () => {
const liquid = new Liquid() const liquid = new Liquid()
@@ -16,7 +15,7 @@ describe('liquid#registerTag()', function () {
render: async () => 'B' render: async () => 'B'
}) })
const html = await liquid.parseAndRender(`A{% async-string %}C`) 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 () => { it('should have access to ctx in render()', async () => {
const liquid = new Liquid() const liquid = new Liquid()
@@ -26,7 +25,7 @@ describe('liquid#registerTag()', function () {
const html = await liquid.parseAndRender(`A{% dynamic-string %}C`, { const html = await liquid.parseAndRender(`A{% dynamic-string %}C`, {
c: 'B' c: 'B'
}) })
return expect(html).to.equal('ABC') return expect(html).toBe('ABC')
}) })
it('should have access to tag arguments', async () => { it('should have access to tag arguments', async () => {
const liquid = new Liquid() const liquid = new Liquid()
@@ -37,6 +36,6 @@ describe('liquid#registerTag()', function () {
const html = await liquid.parseAndRender(`A{% argument-reflector variable=c %}C`, { const html = await liquid.parseAndRender(`A{% argument-reflector variable=c %}C`, {
c: 'B' c: 'B'
}) })
return expect(html).to.equal('ABC') return expect(html).toBe('ABC')
}) })
}) })
@@ -1,15 +1,14 @@
import { normalize } from '../../../src/liquid-options' import { normalize } from '../../../src/liquid-options'
import { expect } from 'chai'
describe('LiquidOptions#root', function () { describe('LiquidOptions#root', function () {
describe('#normalize ()', function () { describe('#normalize ()', function () {
it('should normalize string typed root array', function () { it('should normalize string typed root array', function () {
const options = normalize({ root: 'foo' }) 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 () { it('should normalize null typed root as empty array', function () {
const options = normalize({ root: null } as any) 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 { 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 () { describe('LiquidOptions#strict*', function () {
let engine: Liquid let engine: Liquid
@@ -16,7 +11,7 @@ describe('LiquidOptions#strict*', function () {
}) })
it('should not throw when strictVariables false (default)', async function () { it('should not throw when strictVariables false (default)', async function () {
const html = await engine.parseAndRender('before{{notdefined}}after', ctx) 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 () { it('should throw when strictVariables true', function () {
const tpl = engine.parse('before{{notdefined}}after') const tpl = engine.parse('before{{notdefined}}after')
@@ -25,8 +20,7 @@ describe('LiquidOptions#strict*', function () {
extname: '.html', extname: '.html',
strictVariables: true strictVariables: true
}) })
return expect(engine.render(tpl, ctx)).to return expect(engine.render(tpl, ctx)).rejects.toThrow(/undefined variable: notdefined/)
.be.rejectedWith(/undefined variable: notdefined/)
}) })
it('should pass strictVariables to render by parseAndRender', function () { it('should pass strictVariables to render by parseAndRender', function () {
const html = 'before{{notdefined}}after' const html = 'before{{notdefined}}after'
@@ -35,8 +29,7 @@ describe('LiquidOptions#strict*', function () {
extname: '.html', extname: '.html',
strictVariables: true strictVariables: true
}) })
return expect(engine.parseAndRender(html, ctx)).to return expect(engine.parseAndRender(html, ctx)).rejects.toThrow(/undefined variable: notdefined/)
.be.rejectedWith(/undefined variable: notdefined/)
}) })
describe('with strictVariables and lenientIf', function () { describe('with strictVariables and lenientIf', function () {
beforeEach(() => { beforeEach(() => {
@@ -50,31 +43,31 @@ describe('LiquidOptions#strict*', function () {
it('should not throw in `if` with a single variable', async function () { it('should not throw in `if` with a single variable', async function () {
const tpl = engine.parse('before{% if notdefined %}{{notdefined}}{% endif %}after') const tpl = engine.parse('before{% if notdefined %}{{notdefined}}{% endif %}after')
const html = await engine.render(tpl, ctx) 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 () { 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 tpl = engine.parse('{% if notdefined1 %}a{% elsif notdefined2 %}b{% elsif defined3 %}{{defined3}}{% else %}d{% endif %}')
const html = await engine.render(tpl, { 'defined3': 'bla' }) 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 () { 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 tpl = engine.parse('before{% unless notdefined %}X{% else %}{{notdefined}}{% endunless %}after')
const html = await engine.render(tpl, ctx) 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 () { it('should still throw with an undefined variable in a compound `if` expression', function () {
const tpl = engine.parse('{% if notdefined == 15 %}a{% endif %}') const tpl = engine.parse('{% if notdefined == 15 %}a{% endif %}')
const fhtml = engine.render(tpl, ctx) 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 () { it('should allow an undefined variable when before the `default` filter', async function () {
const tpl = engine.parse('{{notdefined | default: "a" | tolower}}') const tpl = engine.parse('{{notdefined | default: "a" | tolower}}')
const html = await engine.render(tpl, ctx) 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 () { it('should not allow undefined variable even if `lenientIf` set', async function () {
const tpl = engine.parse('{{notdefined | tolower}}') 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' import { Liquid } from '../../../src/liquid'
describe('LiquidOptions#trimming', function () { describe('LiquidOptions#trimming', function () {
@@ -8,34 +7,34 @@ describe('LiquidOptions#trimming', function () {
it('should respect trimTagLeft', async function () { it('should respect trimTagLeft', async function () {
const engine = new Liquid({ trimTagLeft: true }) const engine = new Liquid({ trimTagLeft: true })
const html = await engine.parseAndRender(' \n \t{%if true%}foo{%endif%} ') 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 () { it('should respect trimTagRight', async function () {
const engine = new Liquid({ trimTagRight: true } as any) const engine = new Liquid({ trimTagRight: true } as any)
const html = await engine.parseAndRender('\t{%if true%}foo{%endif%} \n') 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 () { it('should not trim value', async function () {
const engine = new Liquid({ trimTagLeft: true, trimTagRight: true } as any) const engine = new Liquid({ trimTagLeft: true, trimTagRight: true } as any)
const html = await engine.parseAndRender('{%if true%}a {{name}} b{%endif%}', ctx) 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 () { describe('value trimming', function () {
it('should respect trimOutputLeft', async function () { it('should respect trimOutputLeft', async function () {
const engine = new Liquid({ trimOutputLeft: true } as any) const engine = new Liquid({ trimOutputLeft: true } as any)
const html = await engine.parseAndRender(' \n \t{{name}} ', ctx) 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 () { it('should respect trimOutputRight', async function () {
const engine = new Liquid({ trimOutputRight: true } as any) const engine = new Liquid({ trimOutputRight: true } as any)
const html = await engine.parseAndRender(' \n \t{{name}} ', ctx) 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 () { it('should respect not trim tag', async function () {
const engine = new Liquid({ trimOutputLeft: true, trimOutputRight: true } as any) const engine = new Liquid({ trimOutputLeft: true, trimOutputRight: true } as any)
const html = await engine.parseAndRender('\t{% if true %} aha {%endif%}\t') 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 () { describe('greedy', function () {
@@ -43,12 +42,12 @@ describe('LiquidOptions#trimming', function () {
it('should enable greedy by default', async function () { it('should enable greedy by default', async function () {
const engine = new Liquid() const engine = new Liquid()
const html = await engine.parseAndRender(src, ctx) 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 () { it('should allow greedy:false', async function () {
const engine = new Liquid({ greedy: false } as any) const engine = new Liquid({ greedy: false } as any)
const html = await engine.parseAndRender(src, ctx) 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 () { describe('markup', function () {
@@ -64,7 +63,7 @@ describe('LiquidOptions#trimming', function () {
].join('\n') ].join('\n')
const dst = 'Wow, John G. Chalmers-Smith, you have a long name!' const dst = 'Wow, John G. Chalmers-Smith, you have a long name!'
const html = await engine.parseAndRender(src) 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 () { it('should not trim when not specified', async function () {
const engine = new Liquid() const engine = new Liquid()
@@ -78,7 +77,7 @@ describe('LiquidOptions#trimming', function () {
].join('\n') ].join('\n')
const dst = '\n\n Wow, John G. Chalmers-Smith, you have a long name!\n' const dst = '\n\n Wow, John G. Chalmers-Smith, you have a long name!\n'
const html = await engine.parseAndRender(src) 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' import { Liquid } from '../../../src/liquid'
const liquid = new Liquid() const liquid = new Liquid()
@@ -444,7 +443,7 @@ describe('Whitespace Control', function () {
item.text, item.text,
async () => { async () => {
const html = await liquid.parseAndRender(item.text) 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 { Liquid } from '../../../src/liquid'
import { ParseError } from '../../../src' import { ParseError } from '../../../src'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/assign', function () { describe('tags/assign', function () {
const liquid = new Liquid() const liquid = new Liquid()
it('should throw when variable name illegal', function () { it('should throw when variable name illegal', function () {
const src = '{% assign / %}' const src = '{% assign / %}'
const ctx = {} 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 () { it('should support assign to a string', async function () {
const src = '{% assign foo="bar" %}{{foo}}' const src = '{% assign foo="bar" %}{{foo}}'
const html = await liquid.parseAndRender(src) 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 () { it('should throw when variable value illegal', function () {
const src = '{% assign foo = “bar” %}' const src = '{% assign foo = “bar” %}'
expect(() => liquid.parse(src)).to.throw(/unexpected token at "“bar”"/) expect(() => liquid.parse(src)).toThrow(/unexpected token at "“bar”"/)
expect(() => liquid.parse(src)).to.throw(ParseError) expect(() => liquid.parse(src)).toThrow(ParseError)
}) })
it('should support assign to a number', async function () { it('should support assign to a number', async function () {
const src = '{% assign foo=10086 %}{{foo}}' const src = '{% assign foo=10086 %}{{foo}}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('10086') return expect(html).toBe('10086')
}) })
it('should assign as array', async function () { it('should assign as array', async function () {
const src = '{% assign foo=(1..3) %}{{foo}}' const src = '{% assign foo=(1..3) %}{{foo}}'
const html = await liquid.parseAndRender(src) 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 () { it('should assign as filter result', async function () {
const src = '{% assign foo="a b" | capitalize | split: " " | first %}{{foo}}' const src = '{% assign foo="a b" | capitalize | split: " " | first %}{{foo}}'
const html = await liquid.parseAndRender(src) 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 () { it('should assign as filter across multiple lines as result', async function () {
const src = `{% assign foo="a b" const src = `{% assign foo="a b"
@@ -43,64 +39,64 @@ describe('tags/assign', function () {
| split: " " | split: " "
| first %}{{foo}}` | first %}{{foo}}`
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('A') return expect(html).toBe('A')
}) })
it('should assign var-1', async function () { it('should assign var-1', async function () {
const src = '{% assign var-1 = 5 %}{{ var-1 }}' const src = '{% assign var-1 = 5 %}{{ var-1 }}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('5') return expect(html).toBe('5')
}) })
it('should assign var-', async function () { it('should assign var-', async function () {
const src = '{% assign var- = 5 %}{{ var- }}' const src = '{% assign var- = 5 %}{{ var- }}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('5') return expect(html).toBe('5')
}) })
it('should assign -var', async function () { it('should assign -var', async function () {
const src = '{% assign -let = 5 %}{{ -let }}' const src = '{% assign -let = 5 %}{{ -let }}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('5') return expect(html).toBe('5')
}) })
it('should assign -5-5', async function () { it('should assign -5-5', async function () {
const src = '{% assign -5-5 = 5 %}{{ -5-5 }}' const src = '{% assign -5-5 = 5 %}{{ -5-5 }}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('5') return expect(html).toBe('5')
}) })
it('should assign 4-3', async function () { it('should assign 4-3', async function () {
const src = '{% assign 4-3 = 5 %}{{ 4-3 }}' const src = '{% assign 4-3 = 5 %}{{ 4-3 }}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('5') return expect(html).toBe('5')
}) })
it('should not assign -6', async function () { it('should not assign -6', async function () {
const src = '{% assign -6 = 5 %}{{ -6 }}' const src = '{% assign -6 = 5 %}{{ -6 }}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('-6') return expect(html).toBe('-6')
}) })
it('should allow reassignment', async function () { it('should allow reassignment', async function () {
const src = '{% assign var = 1 %}{% assign var = 2 %}{{ var }}' const src = '{% assign var = 1 %}{% assign var = 2 %}{{ var }}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('2') return expect(html).toBe('2')
}) })
describe('scope', function () { describe('scope', function () {
it('should read from parent scope', async function () { it('should read from parent scope', async function () {
const src = '{%for a in (1..2)%}{{num}}{%endfor%}' const src = '{%for a in (1..2)%}{{num}}{%endfor%}'
const html = await liquid.parseAndRender(src, { num: 1 }) 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 () { it('should write to the root scope', async function () {
const src = '{%for a in (1..2)%}{%assign num = a%}{{a}}{%endfor%}' const src = '{%for a in (1..2)%}{%assign num = a%}{{a}}{%endfor%}'
const html = await liquid.parseAndRender(src, { num: 1 }) 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 () { it('should not change input scope', async function () {
const src = '{%for a in (1..2)%}{%assign num = a%}{{a}}{%endfor%} {{num}}' const src = '{%for a in (1..2)%}{%assign num = a%}{{a}}{%endfor%} {{num}}'
const ctx = { num: 1 } const ctx = { num: 1 }
await liquid.parseAndRender(src, ctx) await liquid.parseAndRender(src, ctx)
return expect(ctx.num).to.equal(1) return expect(ctx.num).toBe(1)
}) })
}) })
it('should support sync', function () { it('should support sync', function () {
const src = '{% assign foo="bar" %}{{foo}}' const src = '{% assign foo="bar" %}{{foo}}'
const html = liquid.parseAndRenderSync(src) 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 { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/capture', function () { describe('tags/capture', function () {
const liquid = new Liquid() const liquid = new Liquid()
@@ -10,37 +6,37 @@ describe('tags/capture', function () {
it('should support capture', async function () { it('should support capture', async function () {
const src = '{% capture f %}{{"a" | capitalize}}{%endcapture%}{{f}}' const src = '{% capture f %}{{"a" | capitalize}}{%endcapture%}{{f}}'
const html = await liquid.parseAndRender(src) 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 () { it('should support quoted variable name', async function () {
const src = '{% capture "f" %}{{"a" | capitalize}}{%endcapture%}{{f}}' const src = '{% capture "f" %}{{"a" | capitalize}}{%endcapture%}{{f}}'
const html = await liquid.parseAndRender(src) 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 () { it('should not change root scope', async function () {
const src = '{% capture var %}10{% endcapture %}{{var}}' const src = '{% capture var %}10{% endcapture %}{{var}}'
const ctx = { 'var': 20 } const ctx = { 'var': 20 }
const html = await liquid.parseAndRender(src, ctx) const html = await liquid.parseAndRender(src, ctx)
expect(html).to.equal('10') expect(html).toBe('10')
expect(ctx.var).to.equal(20) expect(ctx.var).toBe(20)
}) })
it('should throw on invalid identifier', function () { it('should throw on invalid identifier', function () {
const src = '{% capture = %}{%endcapture%}' const src = '{% capture = %}{%endcapture%}'
return expect(liquid.parseAndRender(src)) return expect(liquid.parseAndRender(src))
.to.be.rejectedWith(/= not valid identifier/) .rejects.toThrow(/= not valid identifier/)
}) })
it('should throw when capture not closed', function () { it('should throw when capture not closed', function () {
const src = '{%capture c%}{{c}}' const src = '{%capture c%}{{c}}'
return expect(liquid.parseAndRender(src)) return expect(liquid.parseAndRender(src))
.to.be.rejectedWith(/tag .* not closed/) .rejects.toThrow(/tag .* not closed/)
}) })
it('should support sync', function () { it('should support sync', function () {
const src = '{% capture f %}{{"a" | capitalize}}{%endcapture%}{{f}}' const src = '{% capture f %}{{"a" | capitalize}}{%endcapture%}{{f}}'
const html = liquid.parseAndRenderSync(src) 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 { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/case', function () { describe('tags/case', function () {
const liquid = new Liquid() const liquid = new Liquid()
@@ -10,43 +6,43 @@ describe('tags/case', function () {
it('should reject if not closed', function () { it('should reject if not closed', function () {
const src = '{% case "foo"%}' const src = '{% case "foo"%}'
return expect(liquid.parseAndRender(src)) 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 () { it('should hit the specified case', async function () {
const src = '{% case "foo"%}' + const src = '{% case "foo"%}' +
'{% when "foo" %}foo{% when "bar"%}bar' + '{% when "foo" %}foo{% when "bar"%}bar' +
'{%endcase%}' '{%endcase%}'
const html = await liquid.parseAndRender(src) 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 () { it('should resolve blank as empty string', async function () {
const src = '{% case blank %}{% when ""%}bar{%endcase%}' const src = '{% case blank %}{% when ""%}bar{%endcase%}'
const html = await liquid.parseAndRender(src) 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 () { it('should resolve empty as empty string', async function () {
const src = '{% case empty %}{% when ""%}bar{%endcase%}' const src = '{% case empty %}{% when ""%}bar{%endcase%}'
const html = await liquid.parseAndRender(src) 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 () { it('should accept empty string as branch name', async function () {
const src = '{% case "" %}{% when ""%}bar{%endcase%}' const src = '{% case "" %}{% when ""%}bar{%endcase%}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('bar') return expect(html).toBe('bar')
}) })
it('should support boolean case', async function () { it('should support boolean case', async function () {
const src = '{% case false %}' + const src = '{% case false %}' +
'{% when "foo" %}foo{% when false%}bar' + '{% when "foo" %}foo{% when false%}bar' +
'{%endcase%}' '{%endcase%}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('bar') return expect(html).toBe('bar')
}) })
it('should support else branch', async function () { it('should support else branch', async function () {
const src = '{% case "a" %}' + const src = '{% case "a" %}' +
'{% when "b" %}b{% when "c"%}c{%else %}d' + '{% when "b" %}b{% when "c"%}c{%else %}d' +
'{%endcase%}' '{%endcase%}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('d') return expect(html).toBe('d')
}) })
describe('sync support', function () { describe('sync support', function () {
it('should hit the specified case', function () { it('should hit the specified case', function () {
@@ -54,14 +50,14 @@ describe('tags/case', function () {
'{% when "foo" %}foo{% when "bar"%}bar' + '{% when "foo" %}foo{% when "bar"%}bar' +
'{%endcase%}' '{%endcase%}'
const html = liquid.parseAndRenderSync(src) const html = liquid.parseAndRenderSync(src)
return expect(html).to.equal('foo') return expect(html).toBe('foo')
}) })
it('should support else branch', function () { it('should support else branch', function () {
const src = '{% case "a" %}' + const src = '{% case "a" %}' +
'{% when "b" %}b{% when "c"%}c{%else %}d' + '{% when "b" %}b{% when "c"%}c{%else %}d' +
'{%endcase%}' '{%endcase%}'
const html = liquid.parseAndRenderSync(src) 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 () { it('should support case with multiple values', async function () {
@@ -69,7 +65,7 @@ describe('tags/case', function () {
'{% when "a", "b" %}foo' + '{% when "a", "b" %}foo' +
'{%endcase%}' '{%endcase%}'
const html = await liquid.parseAndRender(src) 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 () { it('should render multiple matching branches', async function () {
const src = '{% case "b" %}' + const src = '{% case "b" %}' +
@@ -77,6 +73,6 @@ describe('tags/case', function () {
'{% when "b" %}second' + '{% when "b" %}second' +
'{%endcase%}' '{%endcase%}'
const html = await liquid.parseAndRender(src) 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 { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/comment', function () { describe('tags/comment', function () {
const liquid = new Liquid() const liquid = new Liquid()
it('should support empty content', function () { it('should support empty content', function () {
const src = '{% comment %}{% raw%}' const src = '{% comment %}{% raw%}'
return expect(liquid.parseAndRender(src)) return expect(liquid.parseAndRender(src))
.to.be.rejectedWith(/{% comment %} not closed/) .rejects.toThrow(/{% comment %} not closed/)
}) })
it('should ignore plain string', async function () { it('should ignore plain string', async function () {
const src = 'My name is {% comment %}super{% endcomment %} Shopify.' const src = 'My name is {% comment %}super{% endcomment %} Shopify.'
const html = await liquid.parseAndRender(src) 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 () { it('should ignore output tokens', async function () {
const src = '{% comment %}\n{{ foo}} \n{% endcomment %}' const src = '{% comment %}\n{{ foo}} \n{% endcomment %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('') return expect(html).toBe('')
}) })
it('should ignore tag tokens', async function () { it('should ignore tag tokens', async function () {
const src = '{% comment %}{%if true%}true{%else%}false{%endif%}{% endcomment %}' const src = '{% comment %}{%if true%}true{%else%}false{%endif%}{% endcomment %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('') return expect(html).toBe('')
}) })
it('should ignore un-balenced tag tokens', async function () { it('should ignore un-balenced tag tokens', async function () {
const src = '{% comment %}{%if true%}true{%else%}false{% endcomment %}' const src = '{% comment %}{%if true%}true{%else%}false{% endcomment %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('') return expect(html).toBe('')
}) })
describe('sync support', function () { describe('sync support', function () {
it('should ignore plain string', function () { it('should ignore plain string', function () {
const src = 'My name is {% comment %}super{% endcomment %} Shopify.' const src = 'My name is {% comment %}super{% endcomment %} Shopify.'
const html = liquid.parseAndRenderSync(src) 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 { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/cycle', function () { describe('tags/cycle', function () {
const liquid = new Liquid() const liquid = new Liquid()
@@ -10,12 +6,12 @@ describe('tags/cycle', function () {
it('should support cycle', async function () { it('should support cycle', async function () {
const src = "{% cycle '1', '2', '3' %}" const src = "{% cycle '1', '2', '3' %}"
const html = await liquid.parseAndRender(src + src + src + src) 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 () { it('should throw when cycle candidates empty', function () {
return expect(liquid.parseAndRender('{%cycle%}')) return expect(liquid.parseAndRender('{%cycle%}'))
.to.be.rejectedWith(/empty candidates/) .rejects.toThrow(/empty candidates/)
}) })
it('should support cycle in for block', async function () { it('should support cycle in for block', async function () {
@@ -24,7 +20,7 @@ describe('tags/cycle', function () {
one: 1 one: 1
} }
const html = await liquid.parseAndRender(src, ctx) 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 () { it('should considered different groups for different arguments', async function () {
@@ -32,7 +28,7 @@ describe('tags/cycle', function () {
"{% cycle '1', '2'%}" + "{% cycle '1', '2'%}" +
"{% cycle '1', '2', '3'%}" "{% cycle '1', '2', '3'%}"
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('112') return expect(html).toBe('112')
}) })
it('should support cycle group', async function () { it('should support cycle group', async function () {
@@ -41,11 +37,11 @@ describe('tags/cycle', function () {
"{% cycle 2: '1', '2', '3'%}" "{% cycle 2: '1', '2', '3'%}"
const ctx = { one: 1 } const ctx = { one: 1 }
const html = await liquid.parseAndRender(src, ctx) const html = await liquid.parseAndRender(src, ctx)
return expect(html).to.equal('121') return expect(html).toBe('121')
}) })
it('should support sync', function () { it('should support sync', function () {
const src = "{% cycle '1', '2', '3' %}" const src = "{% cycle '1', '2', '3' %}"
const html = liquid.parseAndRenderSync(src + src + src + src) 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 { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/decrement', function () { describe('tags/decrement', function () {
const liquid = new Liquid() const liquid = new Liquid()
@@ -10,44 +6,44 @@ describe('tags/decrement', function () {
it('should decrement undefined variable', async function () { it('should decrement undefined variable', async function () {
const src = '{% decrement var %}{% decrement var %}{% decrement var %}' const src = '{% decrement var %}{% decrement var %}{% decrement var %}'
const html = await liquid.parseAndRender(src) 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 () { it('should decrement defined variable', async function () {
const src = '{% decrement var %}{% decrement var %}{% decrement var %}' const src = '{% decrement var %}{% decrement var %}{% decrement var %}'
const ctx = { 'var': 10 } const ctx = { 'var': 10 }
const html = await liquid.parseAndRender(src, ctx) const html = await liquid.parseAndRender(src, ctx)
expect(html).to.equal('987') expect(html).toBe('987')
expect(ctx.var).to.equal(7) expect(ctx.var).toBe(7)
}) })
it('should be independent from assign', async function () { it('should be independent from assign', async function () {
const src = '{% assign var=10 %}{% decrement var %}{% decrement var %}{% decrement var %}' const src = '{% assign var=10 %}{% decrement var %}{% decrement var %}{% decrement var %}'
const html = await liquid.parseAndRender(src) 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 () { it('should be independent from capture', async function () {
const src = '{% capture var %}10{% endcapture %}{% decrement var %}{% decrement var %}{% decrement var %}' const src = '{% capture var %}10{% endcapture %}{% decrement var %}{% decrement var %}{% decrement var %}'
const html = await liquid.parseAndRender(src) 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 () { it('should not shading assign', async function () {
const src = '{% assign var=10 %}{% decrement var %}{% decrement var %}{% decrement var %} {{var}}' const src = '{% assign var=10 %}{% decrement var %}{% decrement var %}{% decrement var %} {{var}}'
const html = await liquid.parseAndRender(src) 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 () { it('should not shading capture', async function () {
const src = '{% capture var %}10{% endcapture %}{% decrement var %}{% decrement var %}{% decrement var %} {{var}}' const src = '{% capture var %}10{% endcapture %}{% decrement var %}{% decrement var %}{% decrement var %} {{var}}'
const html = await liquid.parseAndRender(src) 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 () { it('should share the same variable with increment', async function () {
const src = '{%increment var%}{%increment var%}{%decrement var%}{%decrement var%}{%increment var%}' const src = '{%increment var%}{%increment var%}{%decrement var%}{%decrement var%}{%increment var%}'
const html = await liquid.parseAndRender(src) 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 { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/echo', function () { describe('tags/echo', function () {
const liquid = new Liquid() const liquid = new Liquid()
@@ -10,25 +6,25 @@ describe('tags/echo', function () {
it('should output literals', async function () { it('should output literals', async function () {
const src = '{% echo 1 %} {% echo "1" %} {% echo 1.1 %}' const src = '{% echo 1 %} {% echo "1" %} {% echo 1.1 %}'
const html = await liquid.parseAndRender(src) 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 () { it('should output variables', async function () {
const src = '{% echo people.users[0].name %}' const src = '{% echo people.users[0].name %}'
const html = await liquid.parseAndRender(src, { people: { users: [ { name: 'Sally' } ] } }) 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 () { it('should apply filters before output', async function () {
const src = '{% echo user.name | upcase | prepend: "Hello, " | append: "!" %}' const src = '{% echo user.name | upcase | prepend: "Hello, " | append: "!" %}'
const html = await liquid.parseAndRender(src, { user: { name: 'Sally' } }) 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 () { it('should handle empty tag', async function () {
const src = '{% echo %}' const src = '{% echo %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('') return expect(html).toBe('')
}) })
it('should handle extra whitespace', async function () { it('should handle extra whitespace', async function () {
@@ -38,6 +34,6 @@ describe('tags/echo', function () {
"Hello, " | append: "!" "Hello, " | append: "!"
%}` %}`
const html = await liquid.parseAndRender(src, { user: { name: 'Sally' } }) 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 { Liquid } from '../../../src/liquid'
import { Drop } from '../../../src/drop/drop' import { Drop } from '../../../src/drop/drop'
import { Scope } from '../../../src/context/scope' import { Scope } from '../../../src/context/scope'
use(chaiAsPromised)
describe('tags/for', function () { describe('tags/for', function () {
let liquid: Liquid, scope: Scope let liquid: Liquid, scope: Scope
before(function () { beforeEach(function () {
liquid = new Liquid() liquid = new Liquid()
liquid.registerTag('throwingTag', { liquid.registerTag('throwingTag', {
render: function () { throw new Error('intended render error') } render: function () { throw new Error('intended render error') }
@@ -29,57 +25,57 @@ describe('tags/for', function () {
it('should support array', async function () { it('should support array', async function () {
const src = '{%for c in alpha%}{{c}}{%endfor%}' const src = '{%for c in alpha%}{{c}}{%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should support promise of array', async function () {
const src = '{%for c in promiseArray%}{{c}}{%endfor%}' const src = '{%for c in promiseArray%}{{c}}{%endfor%}'
const html = await liquid.parseAndRender(src, scope) const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('abc') return expect(html).toBe('abc')
}) })
it('should support object', async function () { it('should support object', async function () {
const src = '{%for item in obj%}{{item[0]}},{{item[1]}}-{%else%}b{%endfor%}' const src = '{%for item in obj%}{{item[0]}},{{item[1]}}-{%else%}b{%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should output forloop', async function () {
const src = '{%for i in (1..1)%}{{forloop}}{%endfor%}' const src = '{%for i in (1..1)%}{{forloop}}{%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should output forloop collection name', async function () {
const src = '{%for c in alpha%}{{forloop.name}}-{{c}}{%endfor%}' const src = '{%for c in alpha%}{{forloop.name}}-{{c}}{%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should output forloop property accessor name', async function () {
const src = '{%for c in obj.foo%}{{forloop.name}}-{{c}}{%endfor%}' const src = '{%for c in obj.foo%}{{forloop.name}}-{{c}}{%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should output forloop quoted name', async function () {
const src = '{%for str in "string"%}{{forloop.name}}-{{str}}{%endfor%}' const src = '{%for str in "string"%}{{forloop.name}}-{{str}}{%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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 () { describe('illegal', function () {
it('should reject when for not closed', function () { it('should reject when for not closed', function () {
const src = '{%for c in alpha%}{{c}}' const src = '{%for c in alpha%}{{c}}'
return expect(liquid.parseAndRender(src, scope)) 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 () { it('should reject when for in not found', function () {
const src = '{%for c alpha%}{{c}}' const src = '{%for c alpha%}{{c}}'
return expect(liquid.parseAndRender(src, scope)) 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 () { it('should reject when inner templates rejected', function () {
const src = '{%for c in alpha%}{%throwingTag%}{%endfor%}' const src = '{%for c in alpha%}{%throwingTag%}{%endfor%}'
return expect(liquid.parseAndRender(src, scope)) 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 () { it('should goto else for empty array', async function () {
const src = '{%for c in emptyArray%}a{%else%}b{%endfor%}' const src = '{%for c in emptyArray%}a{%else%}b{%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should treat non-empty string as one single element', async function () {
const src = '{%for c in "abc"%}x{{c}}{%else%}y{%endfor%}' const src = '{%for c in "abc"%}x{{c}}{%else%}y{%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should goto else for empty string', async function () {
const src = '{%for c in ""%}a{%else%}b{%endfor%}' const src = '{%for c in ""%}a{%else%}b{%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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 goto else for empty string object', async function () {
// it should be false although `new String` is none-conform // it should be false although `new String` is none-conform
const src = '{%for c in strObj%}a{%else%}b{%endfor%}' const src = '{%for c in strObj%}a{%else%}b{%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should goto else for empty object', async function () {
const src = '{%for c in emptyObj%}a{%else%}b{%endfor%}' const src = '{%for c in emptyObj%}a{%else%}b{%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should goto else for null-prototyped object', async function () {
const src = '{%for c in nullProtoObj%}a{%else%}b{%endfor%}' const src = '{%for c in nullProtoObj%}a{%else%}b{%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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.2.1.false.3.2.1b\n' +
'false.3.2.true.3.1.0c\n' 'false.3.2.true.3.1.0c\n'
const html = await liquid.parseAndRender(src, scope) const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal(dst) return expect(html).toBe(dst)
}) })
describe('continue', function () { describe('continue', function () {
@@ -142,7 +138,7 @@ describe('tags/for', function () {
'{% if i == 4 %}continue{% continue %}{% endif %}{{i}}' + '{% if i == 4 %}continue{% continue %}{% endif %}{{i}}' +
'{% endfor %}' '{% endfor %}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should output contents before continue', async function () {
const src = '{% for i in (1..5) %}' + const src = '{% for i in (1..5) %}' +
@@ -150,7 +146,7 @@ describe('tags/for', function () {
'{{ i }}' + '{{ i }}' +
'{% endfor %}' '{% endfor %}'
const html = await liquid.parseAndRender(src, scope) const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('123continue5') return expect(html).toBe('123continue5')
}) })
}) })
describe('break', function () { describe('break', function () {
@@ -160,7 +156,7 @@ describe('tags/for', function () {
'{{ i }}' + '{{ i }}' +
'{% endfor %}' '{% endfor %}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should output contents before break', async function () {
const src = '{% for i in (1..5) %}' + const src = '{% for i in (1..5) %}' +
@@ -168,7 +164,7 @@ describe('tags/for', function () {
'{{ i }}' + '{{ i }}' +
'{% endfor %}' '{% endfor %}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should support for with limit', async function () {
const src = '{% for i in (1..5) limit:2 %}{{ i }}{% endfor %}' const src = '{% for i in (1..5) limit:2 %}{{ i }}{% endfor %}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should set forloop.last properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.last}} {%endfor%}' const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.last}} {%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should set forloop.first properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.first}} {%endfor%}' const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.first}} {%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should set forloop.length properly', function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.length}} {%endfor%}' const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.length}} {%endfor%}'
return expect(liquid.parseAndRender(src, scope)) 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 () { it('should support offset with limit', async function () {
const src = '{% for i in (1..10) limit:2 offset:5%}{{ i }}{% endfor %}' const src = '{% for i in (1..10) limit:2 offset:5%}{{ i }}{% endfor %}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should set index properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.index}} {%endfor%}' const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.index}} {%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should set index0 properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.index0}} {%endfor%}' const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.index0}} {%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should set rindex properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.rindex}} {%endfor%}' const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.rindex}} {%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should set rindex0 properly', async function () {
const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.rindex0}} {%endfor%}' const src = '{%for i in (1..10) limit:2 offset:3%}{{forloop.rindex0}} {%endfor%}'
const html = await liquid.parseAndRender(src, scope) 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 () { 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 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] }) 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 () { 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 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] }) 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 () { 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 src = '{%for i in (1..5)%}{{i}}{%endfor%}-{%for j in (1..5) offset:continue%}{{j}}{%endfor%}'
const html = await liquid.parseAndRender(src, {}) 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 () { 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 src = '{%for i in arr1%}{{i}}{%endfor%}-{%for i in arr2 offset:continue%}{{i}}{%endfor%}'
const arr = [1, 2, 3, 4, 5] const arr = [1, 2, 3, 4, 5]
const html = await liquid.parseAndRender(src, { arr1: arr, arr2: arr }) 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 () { it('should support for reversed in the last position', async function () {
const src = '{% for i in (1..8) limit:2 reversed %}{{ i }}{% endfor %}' const src = '{% for i in (1..8) limit:2 reversed %}{{ i }}{% endfor %}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should support for reversed in the first position', async function () {
const src = '{% for i in (1..8) reversed limit:2 %}{{ i }}{% endfor %}' const src = '{% for i in (1..8) reversed limit:2 %}{{ i }}{% endfor %}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should support for reversed in the first position with orderedFilterParameters=true', async function () {
const liquid = new Liquid({ orderedFilterParameters: true }) const liquid = new Liquid({ orderedFilterParameters: true })
const src = '{% for i in (1..8) reversed limit:2 %}{{ i }}{% endfor %}' const src = '{% for i in (1..8) reversed limit:2 %}{{ i }}{% endfor %}'
const html = await liquid.parseAndRender(src, scope) 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 () { 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 src = '{% for i in (1..8) offset:2 reversed limit:3 %}{{ i }}{% endfor %}'
const html = await liquid.parseAndRender(src) 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 () { it('should support for reversed in the middle position with orderedFilterParameters=true', async function () {
const liquid = new Liquid({ orderedFilterParameters: true }) const liquid = new Liquid({ orderedFilterParameters: true })
const src = '{% for i in (1..8) offset:2 reversed limit:3 %}{{ i }}{% endfor %}' const src = '{% for i in (1..8) offset:2 reversed limit:3 %}{{ i }}{% endfor %}'
const html = await liquid.parseAndRender(src) 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 () { it('should support sync', function () {
const src = '{% for i in (1..5) %}{{i}}{%endfor%}' const src = '{% for i in (1..5) %}{{i}}{%endfor%}'
const html = liquid.parseAndRenderSync(src) const html = liquid.parseAndRenderSync(src)
return expect(html).to.equal('12345') return expect(html).toBe('12345')
}) })
it('should output contents before break', function () { it('should output contents before break', function () {
const src = '{% for i in (1..5) %}' + const src = '{% for i in (1..5) %}' +
@@ -290,19 +286,19 @@ describe('tags/for', function () {
'{{ i }}' + '{{ i }}' +
'{% endfor %}' '{% endfor %}'
const html = liquid.parseAndRenderSync(src, scope) const html = liquid.parseAndRenderSync(src, scope)
return expect(html).to.equal('123breaking') return expect(html).toBe('123breaking')
}) })
it('should support for with continue', function () { it('should support for with continue', function () {
const src = '{% for i in (1..5) %}' + const src = '{% for i in (1..5) %}' +
'{% if i == 4 %}continue{% continue %}{% endif %}{{i}}' + '{% if i == 4 %}continue{% continue %}{% endif %}{{i}}' +
'{% endfor %}' '{% endfor %}'
const html = liquid.parseAndRenderSync(src, scope) 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 () { it('should goto else for empty array', async function () {
const src = '{%for c in emptyArray%}a{%else%}b{%endfor%}' const src = '{%for c in emptyArray%}a{%else%}b{%endfor%}'
const html = await liquid.parseAndRenderSync(src, scope) 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 () { it('should loop over iterable objects', function () {
const src = '{% for i in someIterable %}{{i}}{%endfor%}' const src = '{% for i in someIterable %}{{i}}{%endfor%}'
const html = liquid.parseAndRenderSync(src, { someIterable: new MockIterable() }) 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 () { it('should loop over iterable drops', function () {
const src = '{{ someDrop }}: {% for i in someDrop %}{{i}}{%endfor%}' const src = '{{ someDrop }}: {% for i in someDrop %}{{i}}{%endfor%}'
const html = liquid.parseAndRenderSync(src, { someDrop: new MockIterableDrop() }) 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 () { it('should loop over iterable objects with a limit', function () {
const src = '{% for i in someIterable limit:2 %}{{i}}{%endfor%}' const src = '{% for i in someIterable limit:2 %}{{i}}{%endfor%}'
const html = liquid.parseAndRenderSync(src, { someIterable: new MockIterable() }) 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 () { it('should loop over iterable objects with an offset', function () {
const src = '{% for i in someIterable offset:1 %}{{i}}{%endfor%}' const src = '{% for i in someIterable offset:1 %}{{i}}{%endfor%}'
const html = liquid.parseAndRenderSync(src, { someIterable: new MockIterable() }) 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 () { it('should loop over iterable objects in reverse', function () {
const src = '{% for i in someIterable reversed %}{{i}}{%endfor%}' const src = '{% for i in someIterable reversed %}{{i}}{%endfor%}'
const html = liquid.parseAndRenderSync(src, { someIterable: new MockIterable() }) 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 () { it('should go to else for an empty iterable', function () {
const src = '{% for i in emptyIterable reversed %}{{i}}{%else%}EMPTY{%endfor%}' const src = '{% for i in emptyIterable reversed %}{{i}}{%else%}EMPTY{%endfor%}'
const html = liquid.parseAndRenderSync(src, { emptyIterable: new MockEmptyIterable() }) 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 () { it('should support iterable names', function () {
const src = '{% for i in someDrop %}{{forloop.name}} {%else%}EMPTY{%endfor%}' const src = '{% for i in someDrop %}{{forloop.name}} {%else%}EMPTY{%endfor%}'
const html = liquid.parseAndRenderSync(src, { someDrop: new MockIterableDrop() }) 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 { 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 () { describe('tags/if', function () {
const liquid = new Liquid() const liquid = new Liquid()
@@ -17,67 +12,67 @@ describe('tags/if', function () {
it('should throw if not closed', function () { it('should throw if not closed', function () {
const src = '{% if false%}yes' const src = '{% if false%}yes'
return expect(liquid.parseAndRender(src, scope)) 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 () { it('should support nested', async function () {
const src = '{%if false%}{%if true%}{%else%}a{%endif%}{%endif%}' const src = '{%if false%}{%if true%}{%else%}a{%endif%}{%endif%}'
const html = await liquid.parseAndRender(src, scope) const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('') return expect(html).toBe('')
}) })
describe('single value as condition', function () { describe('single value as condition', function () {
it('should support boolean', async function () { it('should support boolean', async function () {
const src = '{% if false %}1{%elsif true%}2{%else%}3{%endif%}' const src = '{% if false %}1{%elsif true%}2{%else%}3{%endif%}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should treat Array truthy', async function () {
const src = '{%if emptyArray%}a{%endif%}' const src = '{%if emptyArray%}a{%endif%}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should return true if empty string', async function () {
const src = '{%if emptyString%}a{%endif%}' const src = '{%if emptyString%}a{%endif%}'
const html = await liquid.parseAndRender(src, scope) const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('a') return expect(html).toBe('a')
}) })
}) })
describe('expression as condition', function () { describe('expression as condition', function () {
it('should support ==', async function () { it('should support ==', async function () {
const src = '{% if 2 == 3 %}yes{%else%}no{%endif%}' const src = '{% if 2 == 3 %}yes{%else%}no{%endif%}'
const html = await liquid.parseAndRender(src, scope) const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('no') return expect(html).toBe('no')
}) })
it('should support >=', async function () { it('should support >=', async function () {
const src = '{% if 1 >= 2 and one<two %}a{%endif%}' const src = '{% if 1 >= 2 and one<two %}a{%endif%}'
const html = await liquid.parseAndRender(src, scope) const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('') return expect(html).toBe('')
}) })
it('should support !=', async function () { it('should support !=', async function () {
const src = '{% if one != two %}yes{%else%}no{%endif%}' const src = '{% if one != two %}yes{%else%}no{%endif%}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should support value and expression', async function () {
const src = `X{%if version and version != '' %}x{{version}}y{%endif%}Y` const src = `X{%if version and version != '' %}x{{version}}y{%endif%}Y`
const scope = { 'version': '' } const scope = { 'version': '' }
const html = await liquid.parseAndRender(src, scope) 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 () { it('should evaluate right to left', async function () {
const src = `{% if false and false or true %}true{%endif%}` const src = `{% if false and false or true %}true{%endif%}`
const html = await liquid.parseAndRender(src) 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 () { it('should allow no spaces around operator for literal', async function () {
const src = `{% if true==true %}success{%else%}fail{% endif %}` const src = `{% if true==true %}success{%else%}fail{% endif %}`
const html = await liquid.parseAndRender(src) 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 () { it('should allow no spaces around operator for variables', async function () {
const src = `{%assign var = 1%}{%if var ==1%}success{%else%}fail{%endif%}` const src = `{%assign var = 1%}{%if var ==1%}success{%else%}fail{%endif%}`
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('success') return expect(html).toBe('success')
}) })
}) })
describe('filters as condition', function () { describe('filters as condition', function () {
@@ -85,67 +80,67 @@ describe('tags/if', function () {
liquid.registerFilter('negate', (val) => !val) liquid.registerFilter('negate', (val) => !val)
const src = '{% if 2 == 3 | negate %}yes{%else%}no{%endif%}' const src = '{% if 2 == 3 | negate %}yes{%else%}no{%endif%}'
const html = await liquid.parseAndRender(src, scope) const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('yes') return expect(html).toBe('yes')
}) })
}) })
describe('compare to null', function () { describe('compare to null', function () {
it('should evaluate false for null < 10', async function () { it('should evaluate false for null < 10', async function () {
const src = '{% if null < 10 %}yes{% else %}no{% endif %}' const src = '{% if null < 10 %}yes{% else %}no{% endif %}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should evaluate false for null > 10', async function () {
const src = '{% if null > 10 %}yes{% else %}no{% endif %}' const src = '{% if null > 10 %}yes{% else %}no{% endif %}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should evaluate false for null <= 10', async function () {
const src = '{% if null <= 10 %}yes{% else %}no{% endif %}' const src = '{% if null <= 10 %}yes{% else %}no{% endif %}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should evaluate false for null >= 10', async function () {
const src = '{% if null >= 10 %}yes{% else %}no{% endif %}' const src = '{% if null >= 10 %}yes{% else %}no{% endif %}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should evaluate false for 10 < null', async function () {
const src = '{% if 10 < null %}yes{% else %}no{% endif %}' const src = '{% if 10 < null %}yes{% else %}no{% endif %}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should evaluate false for 10 > null', async function () {
const src = '{% if 10 > null %}yes{% else %}no{% endif %}' const src = '{% if 10 > null %}yes{% else %}no{% endif %}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should evaluate false for 10 <= null', async function () {
const src = '{% if 10 <= null %}yes{% else %}no{% endif %}' const src = '{% if 10 <= null %}yes{% else %}no{% endif %}'
const html = await liquid.parseAndRender(src, scope) 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 () { it('should evaluate false for 10 >= null', async function () {
const src = '{% if 10 >= null %}yes{% else %}no{% endif %}' const src = '{% if 10 >= null %}yes{% else %}no{% endif %}'
const html = await liquid.parseAndRender(src, scope) const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('no') return expect(html).toBe('no')
}) })
}) })
it('should support sync', function () { it('should support sync', function () {
const src = '{%if true%}true{%else%}false{%endif%}' const src = '{%if true%}true{%else%}false{%endif%}'
const html = liquid.parseAndRenderSync(src, scope) const html = liquid.parseAndRenderSync(src, scope)
return expect(html).to.equal('true') return expect(html).toBe('true')
}) })
it('should support async variables', async () => { it('should support async variables', async () => {
const src = `{%if var == 'var' %}success{%endif%}` const src = `{%if var == 'var' %}success{%endif%}`
const scope = { 'var': Promise.resolve('var') } const scope = { 'var': Promise.resolve('var') }
const html = await liquid.parseAndRender(src, scope) 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 { Liquid } from '../../../src/liquid'
import { Drop } from '../../../src/drop/drop' import { Drop } from '../../../src/drop/drop'
import { expect } from 'chai'
import { mock, restore } from '../../stub/mockfs' import { mock, restore } from '../../stub/mockfs'
describe('tags/include', function () { describe('tags/include', function () {
let liquid: Liquid let liquid: Liquid
before(function () { beforeEach(function () {
liquid = new Liquid({ liquid = new Liquid({
root: '/', root: '/',
extname: '.html' extname: '.html'
@@ -18,7 +17,7 @@ describe('tags/include', function () {
'/bar/foo.html': 'foo' '/bar/foo.html': 'foo'
}) })
const html = await liquid.renderFile('/current.html') 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 () { it('should support relative reference', async function () {
mock({ mock({
@@ -26,7 +25,7 @@ describe('tags/include', function () {
'/foo/coo/foo.html': 'foo' '/foo/coo/foo.html': 'foo'
}) })
const html = await liquid.renderFile('/foo/bar/current.html') 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 () { it('should support template string', async function () {
mock({ mock({
@@ -34,7 +33,7 @@ describe('tags/include', function () {
'/bar/foo.html': 'foo' '/bar/foo.html': 'foo'
}) })
const html = await liquid.renderFile('/current.html', { name: 'foo.html' }) 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 () { it('should allow escape in template string', async function () {
mock({ mock({
@@ -42,7 +41,7 @@ describe('tags/include', function () {
'/bar/foo.html': 'foo' '/bar/foo.html': 'foo'
}) })
const html = await liquid.renderFile('/current.html', { name: '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 () { it('should throw when not specified', function () {
@@ -50,8 +49,8 @@ describe('tags/include', function () {
'/parent.html': '{%include , %}' '/parent.html': '{%include , %}'
}) })
return liquid.renderFile('/parent.html').catch(function (e) { return liquid.renderFile('/parent.html').catch(function (e) {
expect(e.name).to.equal('ParseError') expect(e.name).toBe('ParseError')
expect(e.message).to.match(/illegal argument ","/) expect(e.message).toMatch(/illegal argument ","/)
}) })
}) })
@@ -60,8 +59,8 @@ describe('tags/include', function () {
'/parent.html': '{%include not-exist%}' '/parent.html': '{%include not-exist%}'
}) })
return liquid.renderFile('/parent.html').catch(function (e) { return liquid.renderFile('/parent.html').catch(function (e) {
expect(e.name).to.equal('RenderError') expect(e.name).toBe('RenderError')
expect(e.message).to.match(/illegal filename "undefined"/) expect(e.message).toMatch(/illegal filename "undefined"/)
}) })
}) })
@@ -71,7 +70,7 @@ describe('tags/include', function () {
'/foo/relative.html': 'bar{% include "../bar/foo.html" %}bar' '/foo/relative.html': 'bar{% include "../bar/foo.html" %}bar'
}) })
const html = await liquid.renderFile('foo/relative.html') 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 () { it('should support include: hash list', async function () {
@@ -80,7 +79,7 @@ describe('tags/include', function () {
'/user.html': '{{name}} : {{role}} : {{alias}}' '/user.html': '{{name}} : {{role}} : {{alias}}'
}) })
const html = await liquid.renderFile('hash.html') 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 () { it('should support include: parent scope', async function () {
@@ -89,7 +88,7 @@ describe('tags/include', function () {
'/color.html': 'color:{{color}}, shape:{{shape}}' '/color.html': 'color:{{color}}, shape:{{shape}}'
}) })
const html = await liquid.renderFile('scope.html') 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 () { it('should support include: with', async function () {
@@ -98,7 +97,7 @@ describe('tags/include', function () {
'/color.html': 'color:{{color}}, shape:{{shape}}' '/color.html': 'color:{{color}}, shape:{{shape}}'
}) })
const html = await liquid.renderFile('with.html') 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 () { it('should ignore if with value not specified', async function () {
mock({ mock({
@@ -106,7 +105,7 @@ describe('tags/include', function () {
'/color.html': 'color:{{color}}, shape:{{shape}}' '/color.html': 'color:{{color}}, shape:{{shape}}'
}) })
const html = await liquid.renderFile('with.html') 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 () { it('should treat with as a valid key', async function () {
mock({ mock({
@@ -114,7 +113,7 @@ describe('tags/include', function () {
'/color.html': 'with:{{with}}' '/color.html': 'with:{{with}}'
}) })
const html = await liquid.renderFile('with.html') 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 () { it('should support include: with as Drop', async function () {
class ColorDrop extends Drop { class ColorDrop extends Drop {
@@ -127,7 +126,7 @@ describe('tags/include', function () {
'/color.html': 'color:{{color}}' '/color.html': 'color:{{color}}'
}) })
const html = await liquid.renderFile('with.html', { color: new ColorDrop() }) 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 () { it('should support include: with passed as Drop', async function () {
class ColorDrop extends Drop { class ColorDrop extends Drop {
@@ -141,7 +140,7 @@ describe('tags/include', function () {
'/color.html': '{{color | name}}' '/color.html': '{{color | name}}'
}) })
const html = await liquid.renderFile('with.html', { color: new ColorDrop() }) 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 () { it('should support nested includes', async function () {
@@ -160,7 +159,7 @@ describe('tags/include', function () {
} }
} }
const html = await liquid.renderFile('personInfo.html', ctx) 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 () { describe('static partial', function () {
@@ -171,7 +170,7 @@ describe('tags/include', function () {
}) })
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' }) const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
const html = await staticLiquid.renderFile('parent.html') 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 () { it('should support parent paths', async function () {
@@ -181,7 +180,7 @@ describe('tags/include', function () {
}) })
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' }) const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
const html = await staticLiquid.renderFile('parent.html') const html = await staticLiquid.renderFile('parent.html')
return expect(html).to.equal('XchildY') return expect(html).toBe('XchildY')
}) })
it('should support subpaths', async function () { it('should support subpaths', async function () {
@@ -191,7 +190,7 @@ describe('tags/include', function () {
}) })
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' }) const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
const html = await staticLiquid.renderFile('parent.html') 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 () { it('should support comma separated arguments', async function () {
@@ -201,7 +200,7 @@ describe('tags/include', function () {
}) })
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' }) const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
const html = await staticLiquid.renderFile('parent.html') 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 () { it('should support single liquid output', async function () {
@@ -211,7 +210,7 @@ describe('tags/include', function () {
}) })
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' }) const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
const html = await staticLiquid.renderFile('parent.html', { child: 'child.html' }) 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 () { describe('sync support', function () {
@@ -221,7 +220,7 @@ describe('tags/include', function () {
'/bar/foo.html': 'foo' '/bar/foo.html': 'foo'
}) })
const html = liquid.renderFileSync('/current.html') const html = liquid.renderFileSync('/current.html')
return expect(html).to.equal('barfoobar') return expect(html).toBe('barfoobar')
}) })
it('should support variable', function () { it('should support variable', function () {
mock({ mock({
@@ -229,7 +228,7 @@ describe('tags/include', function () {
'/bar/foo.html': 'foo' '/bar/foo.html': 'foo'
}) })
const html = liquid.renderFileSync('/current.html', { name: '/bar/foo.html' }) 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 () { it('should support include: with', function () {
mock({ mock({
@@ -237,7 +236,7 @@ describe('tags/include', function () {
'/color.html': 'color:{{color}}, shape:{{shape}}' '/color.html': 'color:{{color}}, shape:{{shape}}'
}) })
const html = liquid.renderFileSync('with.html') 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 () { it('should support filename with extension', function () {
mock({ mock({
@@ -246,12 +245,12 @@ describe('tags/include', function () {
}) })
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' }) const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
const html = staticLiquid.renderFileSync('parent.html') 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 () { describe('Jekyll include', function () {
before(function () { beforeEach(function () {
liquid = new Liquid({ liquid = new Liquid({
root: '/', root: '/',
extname: '.html', extname: '.html',
@@ -264,7 +263,7 @@ describe('tags/include', function () {
'/bar/foo.html': '{{include.content}}-{{content}}' '/bar/foo.html': '{{include.content}}-{{content}}'
}) })
const html = liquid.renderFileSync('/current.html') const html = liquid.renderFileSync('/current.html')
return expect(html).to.equal('FOO-') return expect(html).toBe('FOO-')
}) })
it('should support multiple parameters', function () { it('should support multiple parameters', function () {
mock({ mock({
@@ -272,7 +271,7 @@ describe('tags/include', function () {
'/bar/foo.html': '<h2>{{include.header}}</h2>{{include.content}}' '/bar/foo.html': '<h2>{{include.header}}</h2>{{include.content}}'
}) })
const html = liquid.renderFileSync('/current.html') 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 () { it('should support dynamicPartials=true', function () {
mock({ mock({
@@ -286,7 +285,7 @@ describe('tags/include', function () {
dynamicPartials: true dynamicPartials: true
}) })
const html = liquid.renderFileSync('/current.html') 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 { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/increment', function () { describe('tags/increment', function () {
const liquid = new Liquid() const liquid = new Liquid()
@@ -10,38 +6,38 @@ describe('tags/increment', function () {
it('should increment undefined variable', async function () { it('should increment undefined variable', async function () {
const src = '{% increment one %}{% increment one %}{% increment one %}' const src = '{% increment one %}{% increment one %}{% increment one %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('012') return expect(html).toBe('012')
}) })
it('should increment defined variable', async function () { it('should increment defined variable', async function () {
const src = '{% increment one %}{% increment one %}{% increment one %}' const src = '{% increment one %}{% increment one %}{% increment one %}'
const ctx = { one: 7 } const ctx = { one: 7 }
const html = await liquid.parseAndRender(src, ctx) const html = await liquid.parseAndRender(src, ctx)
expect(html).to.equal('789') expect(html).toBe('789')
expect(ctx.one).to.equal(10) expect(ctx.one).toBe(10)
}) })
it('should be independent from assign', async function () { it('should be independent from assign', async function () {
const src = '{% assign var=10 %}{% increment var %}{% increment var %}{% increment var %}' const src = '{% assign var=10 %}{% increment var %}{% increment var %}{% increment var %}'
const html = await liquid.parseAndRender(src) 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 () { it('should be independent from capture', async function () {
const src = '{% capture var %}10{% endcapture %}{% increment var %}{% increment var %}{% increment var %}' const src = '{% capture var %}10{% endcapture %}{% increment var %}{% increment var %}{% increment var %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('012') return expect(html).toBe('012')
}) })
it('should not shading assign', async function () { it('should not shading assign', async function () {
const src = '{% assign var=10 %}{% increment var %}{% increment var %}{% increment var %} {{var}}' const src = '{% assign var=10 %}{% increment var %}{% increment var %}{% increment var %} {{var}}'
const html = await liquid.parseAndRender(src) 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 () { it('should not hide capture', async function () {
const src = '{% capture var %}10{% endcapture %}{% increment var %}{% increment var %}{% increment var %} {{var}}' const src = '{% capture var %}10{% endcapture %}{% increment var %}{% increment var %}{% increment var %} {{var}}'
const html = await liquid.parseAndRender(src) 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 { Liquid } from '../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/inline-comment', function () { describe('tags/inline-comment', function () {
const liquid = new Liquid() const liquid = new Liquid()
it('should ignore plain string', async function () { it('should ignore plain string', async function () {
const src = 'My name is {% # super %} Shopify.' const src = 'My name is {% # super %} Shopify.'
const html = await liquid.parseAndRender(src) 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 () { it('should ignore output tokens', async function () {
const src = '{% #\n{{ foo}} \n %}' const src = '{% #\n{{ foo}} \n %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('') return expect(html).toBe('')
}) })
it('should support whitespace control', async function () { it('should support whitespace control', async function () {
const src = '{%- # some comment \n -%}\nfoo' const src = '{%- # some comment \n -%}\nfoo'
const html = await liquid.parseAndRender(src) 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 () { it('should handle hash without trailing whitespace', async function () {
const src = '{% #some comment %}' const src = '{% #some comment %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('') return expect(html).toBe('')
}) })
it('should handle hash without leading whitespace', async function () { it('should handle hash without leading whitespace', async function () {
const src = '{%#some comment %}' const src = '{%#some comment %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('') return expect(html).toBe('')
}) })
it('should handle empty comment', async function () { it('should handle empty comment', async function () {
const src = '{%#%}' const src = '{%#%}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('') return expect(html).toBe('')
}) })
it('should support multiple lines', async function () { it('should support multiple lines', async function () {
const src = [ const src = [
@@ -44,7 +40,7 @@ describe('tags/inline-comment', function () {
'-%}' '-%}'
].join('\n') ].join('\n')
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('') return expect(html).toBe('')
}) })
it('should enforce leading hashes', async function () { it('should enforce leading hashes', async function () {
const src = [ const src = [
@@ -54,13 +50,13 @@ describe('tags/inline-comment', function () {
'-%}' '-%}'
].join('\n') ].join('\n')
return expect(liquid.parseAndRender(src)) 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 () { describe('sync support', function () {
it('should ignore plain string', function () { it('should ignore plain string', function () {
const src = 'My name is {% # super %} Shopify.' const src = 'My name is {% # super %} Shopify.'
const html = liquid.parseAndRenderSync(src) 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 () { describe('liquid tag', function () {
@@ -78,7 +74,7 @@ describe('tags/inline-comment', function () {
'-%}' '-%}'
].join('\n') ].join('\n')
const html = await liquid.parseAndRender(src) 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 () { it('should handle lots of hashes', async function () {
const src = [ const src = [
@@ -89,7 +85,7 @@ describe('tags/inline-comment', function () {
'-%}' '-%}'
].join('\n') ].join('\n')
const html = await liquid.parseAndRender(src) 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 { Liquid } from '../../../src/liquid'
import { mock, restore } from '../../stub/mockfs' import { mock, restore } from '../../stub/mockfs'
use(chaiAsPromised)
describe('tags/layout', function () { describe('tags/layout', function () {
let liquid: Liquid let liquid: Liquid
before(function () { beforeEach(function () {
liquid = new Liquid({ liquid = new Liquid({
root: '/', root: '/',
extname: '.html' extname: '.html'
@@ -20,16 +16,15 @@ describe('tags/layout', function () {
'/parent.html': 'parent' '/parent.html': 'parent'
}) })
const src = '{% layout "parent" %}{%block%}A' const src = '{% layout "parent" %}{%block%}A'
return expect(liquid.parseAndRender(src)).to return expect(liquid.parseAndRender(src)).rejects.toThrow(/tag {%block%} not closed/)
.be.rejectedWith(/tag {%block%} not closed/)
}) })
it('should throw when filename not specified', function () { it('should throw when filename not specified', function () {
mock({ mock({
'/parent.html': '{%layout%}' '/parent.html': '{%layout%}'
}) })
return liquid.renderFile('/parent.html').catch(function (e) { return liquid.renderFile('/parent.html').catch(function (e) {
expect(e.name).to.equal('ParseError') expect(e.name).toBe('ParseError')
expect(e.message).to.match(/illegal argument ""/) expect(e.message).toMatch(/illegal argument ""/)
}) })
}) })
it('should throw when filename resolved to falsy', function () { it('should throw when filename resolved to falsy', function () {
@@ -37,8 +32,8 @@ describe('tags/layout', function () {
'/parent.html': '{%layout foo%}' '/parent.html': '{%layout foo%}'
}) })
return liquid.renderFile('/parent.html').catch(function (e) { return liquid.renderFile('/parent.html').catch(function (e) {
expect(e.name).to.equal('RenderError') expect(e.name).toBe('RenderError')
expect(e.message).to.contain('illegal filename "undefined"') expect(e.message).toContain('illegal filename "undefined"')
}) })
}) })
it('should handle layout none', async function () { it('should handle layout none', async function () {
@@ -46,7 +41,7 @@ describe('tags/layout', function () {
'{%block a%}A{%endblock%}' + '{%block a%}A{%endblock%}' +
'B' 'B'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('AB') return expect(html).toBe('AB')
}) })
describe('anonymous block', function () { describe('anonymous block', function () {
it('should handle anonymous block', async 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 src = '{% layout "parent.html" %}{%block%}A{%endblock%}'
const html = await liquid.parseAndRender(src) 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 () { it('should handle top level contents as anonymous block', async function () {
mock({ mock({
@@ -63,7 +58,7 @@ describe('tags/layout', function () {
}) })
const src = '{% layout "parent.html" %}A' const src = '{% layout "parent.html" %}A'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('XAY') return expect(html).toBe('XAY')
}) })
}) })
it('should handle named blocks', async function () { it('should handle named blocks', async function () {
@@ -74,7 +69,7 @@ describe('tags/layout', function () {
'{%block a%}A{%endblock%}' + '{%block a%}A{%endblock%}' +
'{%block b%}B{%endblock%}' '{%block b%}B{%endblock%}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('XAYBZ') return expect(html).toBe('XAYBZ')
}) })
it('should support `options.layouts`', async () => { it('should support `options.layouts`', async () => {
mock({ mock({
@@ -83,7 +78,7 @@ describe('tags/layout', function () {
const src = '{% layout "parent.html" %}{%block a%}A{%endblock%}' const src = '{% layout "parent.html" %}{%block a%}A{%endblock%}'
const liquid = new Liquid({ layouts: '/layouts' }) const liquid = new Liquid({ layouts: '/layouts' })
const html = await liquid.parseAndRender(src) 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 () { it('should use `layouts` if specified', async function () {
mock({ mock({
@@ -93,7 +88,7 @@ describe('tags/layout', function () {
}) })
const staticLiquid = new Liquid({ root: '/root', layouts: '/layouts', dynamicPartials: false }) const staticLiquid = new Liquid({ root: '/root', layouts: '/layouts', dynamicPartials: false })
const html = await staticLiquid.renderFile('main.html') 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 () { 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%}' '{%block css%}{{block.super}}<link href="extra.css" rel="stylesheet">{%endblock%}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
const output = '<link href="base.css" rel="stylesheet"><link href="extra.css" rel="stylesheet">' 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 () { it('should render block.super to empty if no parent exists', async function () {
mock({ mock({
@@ -114,7 +109,7 @@ describe('tags/layout', function () {
'{%block css%}{{block.super}}<link href="extra.css" rel="stylesheet">{%endblock%}' '{%block css%}{{block.super}}<link href="extra.css" rel="stylesheet">{%endblock%}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
const output = '<link href="base.css" rel="stylesheet"><link href="extra.css" rel="stylesheet">' 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 () { it('should support nested block.super', async function () {
mock({ 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 src = '{% layout "parent.html" %}{%block css%}{{block.super}}<link href="extra.css" rel="stylesheet">{%endblock%}'
const html = await liquid.parseAndRender(src) 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">' 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 () { it('should support variable as layout name', async function () {
mock({ mock({
@@ -132,7 +127,7 @@ describe('tags/layout', function () {
}) })
const src = '{% layout parent %}{%block a%}A{%endblock%}' const src = '{% layout parent %}{%block a%}A{%endblock%}'
const html = await liquid.parseAndRender(src, { parent: 'parent.html' }) 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 () { it('should support default block content', async function () {
mock({ mock({
@@ -140,7 +135,7 @@ describe('tags/layout', function () {
}) })
const src = '{% layout "parent.html" %}{%block a%}a{%endblock%}' const src = '{% layout "parent.html" %}{%block a%}a{%endblock%}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('XaYBZ') return expect(html).toBe('XaYBZ')
}) })
it('should handle nested block', async function () { it('should handle nested block', async function () {
mock({ mock({
@@ -149,7 +144,7 @@ describe('tags/layout', function () {
'/main.html': '{%layout "parent"%}{%block a%}A{%endblock%}' '/main.html': '{%layout "parent"%}{%block a%}A{%endblock%}'
}) })
const html = await liquid.renderFile('/main.html') 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 () { it('should not bleed scope into `include` layout', async function () {
mock({ mock({
@@ -160,7 +155,7 @@ describe('tags/layout', function () {
'/included.html': '{%layout "parent"%}{%block a%}a{%endblock%}' '/included.html': '{%layout "parent"%}{%block a%}a{%endblock%}'
}) })
const html = await liquid.renderFile('main') 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 () { it('should not bleed scope into `render` layout', async function () {
mock({ mock({
@@ -171,7 +166,7 @@ describe('tags/layout', function () {
'/included.html': '{%layout "parent"%}{%block a%}a{%endblock%}' '/included.html': '{%layout "parent"%}{%block a%}a{%endblock%}'
}) })
const html = await liquid.renderFile('main') const html = await liquid.renderFile('main')
return expect(html).to.equal('XAYIXaYZJZ') return expect(html).toBe('XAYIXaYZJZ')
}) })
it('should support hash list', async function () { it('should support hash list', async function () {
mock({ mock({
@@ -179,7 +174,7 @@ describe('tags/layout', function () {
'/main.html': '{% layout "parent.html" color:"black"%}{%block%}A{%endblock%}' '/main.html': '{% layout "parent.html" color:"black"%}{%block%}A{%endblock%}'
}) })
const html = await liquid.renderFile('/main.html') 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 () { it('should support multiple hash', async function () {
mock({ mock({
@@ -187,7 +182,7 @@ describe('tags/layout', function () {
'/main.html': '{% layout "parent.html" color:"black", bg:"red"%}{%block%}A{%endblock%}' '/main.html': '{% layout "parent.html" color:"black", bg:"red"%}{%block%}A{%endblock%}'
}) })
const html = await liquid.renderFile('/main.html') 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 () { it('should support relative reference', async function () {
@@ -197,7 +192,7 @@ describe('tags/layout', function () {
}) })
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false }) const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
const html = await staticLiquid.renderFile('/foo/bar/main.html') 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 () { it('should support relative root', async function () {
@@ -207,7 +202,7 @@ describe('tags/layout', function () {
}) })
const staticLiquid = new Liquid({ root: './foo', dynamicPartials: false }) const staticLiquid = new Liquid({ root: './foo', dynamicPartials: false })
const html = await staticLiquid.renderFile('bar/main.html') const html = await staticLiquid.renderFile('bar/main.html')
return expect(html).to.equal('blackA') return expect(html).toBe('blackA')
}) })
describe('static partial', function () { describe('static partial', function () {
@@ -218,7 +213,7 @@ describe('tags/layout', function () {
}) })
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false }) const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
const html = await staticLiquid.renderFile('/main.html') 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 () { it('should support parent paths', async function () {
@@ -228,7 +223,7 @@ describe('tags/layout', function () {
}) })
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false }) const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
const html = await staticLiquid.renderFile('/main.html') const html = await staticLiquid.renderFile('/main.html')
return expect(html).to.equal('blackA') return expect(html).toBe('blackA')
}) })
it('should support none', async function () { it('should support none', async function () {
@@ -237,7 +232,7 @@ describe('tags/layout', function () {
}) })
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false }) const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
const html = await staticLiquid.renderFile('/main.html') const html = await staticLiquid.renderFile('/main.html')
return expect(html).to.equal('foo') return expect(html).toBe('foo')
}) })
it('should support subpaths', async function () { it('should support subpaths', async function () {
@@ -247,7 +242,7 @@ describe('tags/layout', function () {
}) })
const staticLiquid = new Liquid({ root: '/', dynamicPartials: false }) const staticLiquid = new Liquid({ root: '/', dynamicPartials: false })
const html = await staticLiquid.renderFile('/main.html') const html = await staticLiquid.renderFile('/main.html')
return expect(html).to.equal('blackA') return expect(html).toBe('blackA')
}) })
}) })
it('should support sync', function () { it('should support sync', function () {
@@ -257,6 +252,6 @@ describe('tags/layout', function () {
'/main.html': '{%layout "parent"%}{%block a%}A{%endblock%}' '/main.html': '{%layout "parent"%}{%block a%}A{%endblock%}'
}) })
const html = liquid.renderFileSync('/main.html') 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' import { Liquid } from '../../../src/liquid'
use(chaiAsPromised)
describe('tags/liquid', function () { describe('tags/liquid', function () {
const liquid = new Liquid() const liquid = new Liquid()
@@ -19,7 +15,7 @@ describe('tags/liquid', function () {
-%} -%}
` `
const html = await liquid.parseAndRender(src, { array: [1, 2, 3] }) 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 () { 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] }) 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 () { it('should handle empty tag', async function () {
const src = '{% liquid %}' const src = '{% liquid %}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('') return expect(html).toBe('')
}) })
it('should handle lines containing only whitespace', async function () { it('should handle lines containing only whitespace', async function () {
@@ -56,7 +52,7 @@ describe('tags/liquid', function () {
echo 'goodbye' echo 'goodbye'
%}` %}`
const html = await liquid.parseAndRender(src) 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 () { it('should fail with carriage return terminated tags', async function () {
@@ -71,6 +67,6 @@ describe('tags/liquid', function () {
'-%}' '-%}'
].join('\r') ].join('\r')
return expect(liquid.parseAndRender(src)) 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' import { Liquid } from '../../../src/liquid'
const expect = chai.expect
chai.use(chaiAsPromised)
describe('tags/raw', function () { describe('tags/raw', function () {
const liquid = new Liquid() const liquid = new Liquid()
it('should throw when not closed', async function () { it('should throw when not closed', async function () {
const p = liquid.parseAndRender('{% raw %}') 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 () { it('should output filters as it is', async function () {
const src = '{% raw %}{{ 5 | plus: 6 }}{% endraw %} is equal to 11.' const src = '{% raw %}{{ 5 | plus: 6 }}{% endraw %} is equal to 11.'
const dst = '{{ 5 | plus: 6 }} is equal to 11.' const dst = '{{ 5 | plus: 6 }} is equal to 11.'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst) return expect(html).toBe(dst)
}) })
it('should preserve blank characters', async function () { it('should preserve blank characters', async function () {
const src = '{% raw %}\n{{ foo}} \n{% endraw %}' const src = '{% raw %}\n{{ foo}} \n{% endraw %}'
const dst = '\n{{ foo}} \n' const dst = '\n{{ foo}} \n'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst) return expect(html).toBe(dst)
}) })
it('should support sync', function () { it('should support sync', function () {
const html = liquid.parseAndRenderSync('{% raw %}{{foo}}{% endraw %}') 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 { Liquid } from '../../../src/liquid'
import { Drop } from '../../../src/drop/drop' import { Drop } from '../../../src/drop/drop'
import { mock, restore } from '../../stub/mockfs' import { mock, restore } from '../../stub/mockfs'
use(chaiAsPromised)
describe('tags/render', function () { describe('tags/render', function () {
let liquid: Liquid let liquid: Liquid
before(function () { beforeEach(function () {
liquid = new Liquid({ liquid = new Liquid({
root: '/', root: '/',
extname: '.html' extname: '.html'
@@ -20,7 +17,7 @@ describe('tags/render', function () {
'/bar/foo.html': 'foo' '/bar/foo.html': 'foo'
}) })
const html = await liquid.renderFile('/current.html') const html = await liquid.renderFile('/current.html')
expect(html).to.equal('barfoobar') expect(html).toBe('barfoobar')
}) })
it('should support render', async function () { it('should support render', async function () {
mock({ mock({
@@ -29,7 +26,7 @@ describe('tags/render', function () {
}) })
const liquid = new Liquid({ partials: '/partials', root: '/' }) const liquid = new Liquid({ partials: '/partials', root: '/' })
const html = await liquid.renderFile('/current.html') const html = await liquid.renderFile('/current.html')
expect(html).to.equal('barfoobar') expect(html).toBe('barfoobar')
}) })
it('should support template string', async function () { it('should support template string', async function () {
mock({ mock({
@@ -37,7 +34,7 @@ describe('tags/render', function () {
'/bar/foo.html': 'foo' '/bar/foo.html': 'foo'
}) })
const html = await liquid.renderFile('/current.html', { name: 'foo.html' }) 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 () { it('should throw when not specified', function () {
@@ -45,8 +42,8 @@ describe('tags/render', function () {
'/parent.html': '{%render%}' '/parent.html': '{%render%}'
}) })
return liquid.renderFile('/parent.html').catch(function (e) { return liquid.renderFile('/parent.html').catch(function (e) {
expect(e.name).to.equal('ParseError') expect(e.name).toBe('ParseError')
expect(e.message).to.match(/illegal argument ""/) expect(e.message).toMatch(/illegal argument ""/)
}) })
}) })
@@ -55,8 +52,8 @@ describe('tags/render', function () {
'/parent.html': '{%render not-exist%}' '/parent.html': '{%render not-exist%}'
}) })
return liquid.renderFile('/parent.html').catch(function (e) { return liquid.renderFile('/parent.html').catch(function (e) {
expect(e.name).to.equal('RenderError') expect(e.name).toBe('RenderError')
expect(e.message).to.match(/illegal filename "undefined"/) expect(e.message).toMatch(/illegal filename "undefined"/)
}) })
}) })
@@ -66,7 +63,7 @@ describe('tags/render', function () {
'/foo/relative.html': 'bar{% render "../bar/foo.html" %}bar' '/foo/relative.html': 'bar{% render "../bar/foo.html" %}bar'
}) })
const html = await liquid.renderFile('foo/relative.html') 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 () { it('should support render: hash list', async function () {
@@ -75,7 +72,7 @@ describe('tags/render', function () {
'/user.html': '{{role}} : {{alias}}' '/user.html': '{{role}} : {{alias}}'
}) })
const html = await liquid.renderFile('hash.html') 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 () { it('should not bleed into child template', async function () {
@@ -84,7 +81,7 @@ describe('tags/render', function () {
'/user.html': 'InChild: {{name}}' '/user.html': 'InChild: {{name}}'
}) })
const html = await liquid.renderFile('hash.html') 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 () { it('should allow argument reassignment', async function () {
@@ -94,7 +91,7 @@ describe('tags/render', function () {
}) })
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' }) const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
const html = await staticLiquid.renderFile('parent.html') 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 () { it('should be able to access globals', async function () {
@@ -104,7 +101,7 @@ describe('tags/render', function () {
'/user.html': 'InChild: {{name}}' '/user.html': 'InChild: {{name}}'
}) })
const html = await liquid.renderFile('hash', { name: 'harttle' }) 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 () { it('should support with', async function () {
@@ -113,7 +110,7 @@ describe('tags/render', function () {
'/color.html': 'color:{{color}}, shape:{{shape}}' '/color.html': 'color:{{color}}, shape:{{shape}}'
}) })
const html = await liquid.renderFile('with.html') 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 () => { it('should treat as normal key/value if followed by ":"', async () => {
mock({ mock({
@@ -121,7 +118,7 @@ describe('tags/render', function () {
'/color.html': 'color:{{color}}, with:{{with}}' '/color.html': 'color:{{color}}, with:{{with}}'
}) })
const html = await liquid.renderFile('with.html') 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 () => { it('should treat as normal key if with value not specified', async () => {
mock({ mock({
@@ -129,7 +126,7 @@ describe('tags/render', function () {
'/color.html': 'color:{{color}}, with:{{with}}, shape:{{shape}}' '/color.html': 'color:{{color}}, with:{{with}}, shape:{{shape}}'
}) })
const html = await liquid.renderFile('with.html') 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 () { it('should support with...as', async function () {
mock({ mock({
@@ -137,7 +134,7 @@ describe('tags/render', function () {
'/color.html': 'color:{{c}}' '/color.html': 'color:{{c}}'
}) })
const html = await liquid.renderFile('with.html', { color: 'red' }) 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 () { it('should support with...as and other parameters', async function () {
mock({ mock({
@@ -146,7 +143,7 @@ describe('tags/render', function () {
}) })
const scope = { color: 'red', shape: 'rect' } const scope = { color: 'red', shape: 'rect' }
const html = await liquid.renderFile('index.html', scope) 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 () { it('should support for...as', async function () {
mock({ mock({
@@ -154,7 +151,7 @@ describe('tags/render', function () {
'/item.html': '{{forloop.index}}: {{color}}\n' '/item.html': '{{forloop.index}}: {{color}}\n'
}) })
const html = await liquid.renderFile('index.html', { colors: ['red', 'green'] }) 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 () { it('should support for <iterable> as', async function () {
class MockIterable { class MockIterable {
@@ -168,7 +165,7 @@ describe('tags/render', function () {
'/item.html': '{{forloop.index}}: {{color}}\n' '/item.html': '{{forloop.index}}: {{color}}\n'
}) })
const html = await liquid.renderFile('index.html', { colors: new MockIterable() }) 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 () { it('should support for <non-array> as', async function () {
mock({ mock({
@@ -176,7 +173,7 @@ describe('tags/render', function () {
'/item.html': '{{forloop.index}}: {{color}}\n' '/item.html': '{{forloop.index}}: {{color}}\n'
}) })
const html = await liquid.renderFile('index.html') 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 () { it('should support for without as', async function () {
mock({ mock({
@@ -184,7 +181,7 @@ describe('tags/render', function () {
'/item.html': '{{forloop.index}}: {{color}}\n' '/item.html': '{{forloop.index}}: {{color}}\n'
}) })
const html = await liquid.renderFile('index.html', { colors: ['red', 'green'] }) 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 () { it('should support for...as with other parameters', async function () {
mock({ mock({
@@ -192,7 +189,7 @@ describe('tags/render', function () {
'/item.html': '{{forloop.index}}{{sep}}{{color}}{{tail}}' '/item.html': '{{forloop.index}}{{sep}}{{color}}{{tail}}'
}) })
const html = await liquid.renderFile('index.html', { colors: ['red', 'green'] }) 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 () { it('should support for...as with other parameters (comma separated)', async function () {
mock({ mock({
@@ -200,7 +197,7 @@ describe('tags/render', function () {
'/item.html': '{{forloop.index}}{{sep}}{{color}}{{tail}}' '/item.html': '{{forloop.index}}{{sep}}{{color}}{{tail}}'
}) })
const html = await liquid.renderFile('index.html', { colors: ['red', 'green'] }) 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 () { it('should support render: with as Drop', async function () {
class ColorDrop extends Drop { class ColorDrop extends Drop {
@@ -213,7 +210,7 @@ describe('tags/render', function () {
'/color.html': 'color:{{color}}' '/color.html': 'color:{{color}}'
}) })
const html = await liquid.renderFile('with.html', { color: new ColorDrop() }) 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 () { it('should support render: with passed as Drop', async function () {
class ColorDrop extends Drop { class ColorDrop extends Drop {
@@ -227,7 +224,7 @@ describe('tags/render', function () {
'/color.html': '{{color | name}}' '/color.html': '{{color | name}}'
}) })
const html = await liquid.renderFile('with.html', { color: new ColorDrop() }) 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 () { it('should support nested renders', async function () {
@@ -246,7 +243,7 @@ describe('tags/render', function () {
} }
} }
const html = await liquid.renderFile('personInfo.html', ctx) 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 () { it('should support relative reference', async function () {
mock({ mock({
@@ -255,7 +252,7 @@ describe('tags/render', function () {
}) })
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/foo' }) const staticLiquid = new Liquid({ dynamicPartials: false, root: '/foo' })
const html = await staticLiquid.renderFile('coo/parent.html') 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', () => { it('should disable relative reference if specified', () => {
mock({ mock({
@@ -263,7 +260,7 @@ describe('tags/render', function () {
'/foo/bar/child.html': 'child with {{color}}' '/foo/bar/child.html': 'child with {{color}}'
}) })
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/foo', relativeReference: false }) 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', () => { it('should throw not found if relative reference out of root', () => {
mock({ mock({
@@ -271,7 +268,7 @@ describe('tags/render', function () {
'/bar/child.html': 'child with {{color}}' '/bar/child.html': 'child with {{color}}'
}) })
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/foo', partials: '/foo' }) 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 () { describe('static partial', function () {
@@ -285,7 +282,7 @@ describe('tags/render', function () {
'/child.html': 'child with {{color}}' '/child.html': 'child with {{color}}'
}) })
const html = await staticLiquid.renderFile('parent.html') 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 () { it('should support parent paths', async function () {
@@ -294,7 +291,7 @@ describe('tags/render', function () {
'/foo/child.html': 'child' '/foo/child.html': 'child'
}) })
const html = await staticLiquid.renderFile('parent.html') const html = await staticLiquid.renderFile('parent.html')
expect(html).to.equal('XchildY') expect(html).toBe('XchildY')
}) })
it('should support subpaths', async function () { it('should support subpaths', async function () {
@@ -303,7 +300,7 @@ describe('tags/render', function () {
'/foo/child.html': 'child' '/foo/child.html': 'child'
}) })
const html = await staticLiquid.renderFile('parent.html') const html = await staticLiquid.renderFile('parent.html')
expect(html).to.equal('XchildY') expect(html).toBe('XchildY')
}) })
it('should support comma separated arguments', async function () { it('should support comma separated arguments', async function () {
@@ -312,7 +309,7 @@ describe('tags/render', function () {
'/child.html': 'child with {{color}}' '/child.html': 'child with {{color}}'
}) })
const html = await staticLiquid.renderFile('parent.html') 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 () { it('should support template string', async function () {
@@ -321,7 +318,7 @@ describe('tags/render', function () {
'/bar/foo.html': 'foo' '/bar/foo.html': 'foo'
}) })
const html = await staticLiquid.renderFile('/current.html', { name: 'foo.html' }) 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 () { it('should support filters in template string', async function () {
@@ -330,7 +327,7 @@ describe('tags/render', function () {
'/bar/foo.html': 'foo' '/bar/foo.html': 'foo'
}) })
const html = await staticLiquid.renderFile('/current.html', { name: 'foo' }) const html = await staticLiquid.renderFile('/current.html', { name: 'foo' })
expect(html).to.equal('barfoobar') expect(html).toBe('barfoobar')
}) })
}) })
describe('sync support', function () { describe('sync support', function () {
@@ -340,7 +337,7 @@ describe('tags/render', function () {
'/bar/foo.html': 'foo' '/bar/foo.html': 'foo'
}) })
const html = liquid.renderFileSync('/current.html') const html = liquid.renderFileSync('/current.html')
expect(html).to.equal('barfoobar') expect(html).toBe('barfoobar')
}) })
it('should support value string', function () { it('should support value string', function () {
mock({ mock({
@@ -348,7 +345,7 @@ describe('tags/render', function () {
'/bar/foo.html': 'foo' '/bar/foo.html': 'foo'
}) })
const html = liquid.renderFileSync('/current.html', { name: '/bar/foo.html' }) 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 () { it('should support template string', function () {
mock({ mock({
@@ -356,7 +353,7 @@ describe('tags/render', function () {
'/bar/foo.html': 'foo' '/bar/foo.html': 'foo'
}) })
const html = liquid.renderFileSync('/current.html', { name: '/foo.html' }) const html = liquid.renderFileSync('/current.html', { name: '/foo.html' })
expect(html).to.equal('barfoobar') expect(html).toBe('barfoobar')
}) })
it('should support with', function () { it('should support with', function () {
mock({ mock({
@@ -364,7 +361,7 @@ describe('tags/render', function () {
'/color.html': 'color:{{color}}, shape:{{shape}}' '/color.html': 'color:{{color}}, shape:{{shape}}'
}) })
const html = liquid.renderFileSync('with.html') 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 () { it('should support filename with extension', function () {
mock({ mock({
@@ -373,7 +370,7 @@ describe('tags/render', function () {
}) })
const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' }) const staticLiquid = new Liquid({ dynamicPartials: false, root: '/' })
const html = staticLiquid.renderFileSync('parent.html') 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' import { Liquid } from '../../../src/liquid'
use(chaiAsPromised)
describe('tags/tablerow', function () { describe('tags/tablerow', function () {
const liquid = new Liquid() const liquid = new Liquid()
@@ -11,7 +7,7 @@ describe('tags/tablerow', function () {
const src = '{% tablerow i in (1..3)%}{{ i }}{% endtablerow %}' const src = '{% tablerow i in (1..3)%}{{ i }}{% endtablerow %}'
const dst = '<tr class="row1"><td class="col1">1</td><td class="col2">2</td><td class="col3">3</td></tr>' const dst = '<tr class="row1"><td class="col1">1</td><td class="col2">2</td><td class="col3">3</td></tr>'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst) return expect(html).toBe(dst)
}) })
it('should support promises', async function () { 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 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) const html = await liquid.parseAndRender(src, ctx)
return expect(html).to.equal(dst) return expect(html).toBe(dst)
}) })
it('should support iterables', async function () { 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 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) const html = await liquid.parseAndRender(src, ctx)
return expect(html).to.equal(dst) return expect(html).toBe(dst)
}) })
it('should support cols', async function () { 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="row1"><td class="col1">a</td><td class="col2">b</td></tr>' +
'<tr class="row2"><td class="col1">c</td></tr>' '<tr class="row2"><td class="col1">c</td></tr>'
const html = await liquid.parseAndRender(src, ctx) 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 () { it('should support cols set to 0', async function () {
const src = '{% tablerow i in (1..3) cols:0 %}{{ i }}{% endtablerow %}' const src = '{% tablerow i in (1..3) cols:0 %}{{ i }}{% endtablerow %}'
const dst = '<tr class="row1"><td class="col1">1</td><td class="col2">2</td><td class="col3">3</td></tr>' const dst = '<tr class="row1"><td class="col1">1</td><td class="col2">2</td><td class="col3">3</td></tr>'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst) return expect(html).toBe(dst)
}) })
it('should support empty tablerow', async function () { it('should support empty tablerow', async function () {
const src = '{% tablerow i in (1..0) cols:2 %}{{ i }}{% endtablerow %}' const src = '{% tablerow i in (1..0) cols:2 %}{{ i }}{% endtablerow %}'
const dst = '' const dst = ''
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst) return expect(html).toBe(dst)
}) })
it('should support empty array', async function () { it('should support empty array', async function () {
const src = '{% tablerow i in alpha.z cols:2 %}{{ i }}{% endtablerow %}' const src = '{% tablerow i in alpha.z cols:2 %}{{ i }}{% endtablerow %}'
const dst = '' const dst = ''
const html = await liquid.parseAndRender(src) 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 () { it('should throw when tablerow not closed', function () {
const src = '{% tablerow i in (1..0) cols:2 %}{{ i }}' const src = '{% tablerow i in (1..0) cols:2 %}{{ i }}'
return expect(liquid.parseAndRender(src)) 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 () { it('should throw when x in y not found', function () {
const src = '{% tablerow i (1..3) %}{{ i }}' const src = '{% tablerow i (1..3) %}{{ i }}'
return expect(liquid.parseAndRender(src)) 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 () { 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="row2"><td class="col1">3</td><td class="col2">4</td></tr>' +
'<tr class="row3"><td class="col1">5</td></tr>' '<tr class="row3"><td class="col1">5</td></tr>'
const html = await liquid.parseAndRender(src) 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 () { 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="row1"><td class="col1">1</td><td class="col2">2</td></tr>' +
'<tr class="row2"><td class="col1">3</td></tr>' '<tr class="row2"><td class="col1">3</td></tr>'
const html = await liquid.parseAndRender(src) 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 () { 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 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 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) 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 () { it('should support first, last, length', async function () {
const src = '{% tablerow i in (1..3)%}{{tablerowloop.first}} {{tablerowloop.last}} {{tablerowloop.length}}{% endtablerow %}' 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 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) 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 () { 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 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 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) const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst) return expect(html).toBe(dst)
}) })
describe('offset', function () { describe('offset', function () {
it('should support tablerow with offset', async function () { it('should support tablerow with offset', async function () {
const src = '{% tablerow i in (1..5) cols:2 offset:3 %}{{ i }}{% endtablerow %}' const src = '{% tablerow i in (1..5) cols:2 offset:3 %}{{ i }}{% endtablerow %}'
const dst = '<tr class="row1"><td class="col1">4</td><td class="col2">5</td></tr>' const dst = '<tr class="row1"><td class="col1">4</td><td class="col2">5</td></tr>'
const html = await liquid.parseAndRender(src) 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 () { it('index should also start at 1', async function () {
const src = '{% tablerow i in (1..4) cols:2 offset:2 %}{{tablerowloop.index}}{% endtablerow %}' 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 dst = '<tr class="row1"><td class="col1">1</td><td class="col2">2</td></tr>'
const html = await liquid.parseAndRender(src) 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 () { it('col should also start at 1', async function () {
const src = '{% tablerow i in (1..4) cols:2 offset:3 %}{{tablerowloop.col}}{% endtablerow %}' 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 dst = '<tr class="row1"><td class="col1">1</td></tr>'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal(dst) return expect(html).toBe(dst)
}) })
}) })
describe('sync support', function () { describe('sync support', function () {
@@ -148,13 +144,13 @@ describe('tags/tablerow', function () {
const src = '{% tablerow i in (1..3)%}{{ i }}{% endtablerow %}' const src = '{% tablerow i in (1..3)%}{{ i }}{% endtablerow %}'
const dst = '<tr class="row1"><td class="col1">1</td><td class="col2">2</td><td class="col3">3</td></tr>' const dst = '<tr class="row1"><td class="col1">1</td><td class="col2">2</td><td class="col3">3</td></tr>'
const html = liquid.parseAndRenderSync(src) const html = liquid.parseAndRenderSync(src)
expect(html).to.equal(dst) expect(html).toBe(dst)
}) })
it('should support empty tablerow', function () { it('should support empty tablerow', function () {
const src = '{% tablerow i in "" cols:2 %}{{ i }}{% endtablerow %}' const src = '{% tablerow i in "" cols:2 %}{{ i }}{% endtablerow %}'
const dst = '' const dst = ''
const html = liquid.parseAndRenderSync(src) 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' import { Liquid } from '../../../src/liquid'
use(chaiAsPromised)
describe('tags/unless', function () { describe('tags/unless', function () {
let liquid: Liquid let liquid: Liquid
before(() => { liquid = new Liquid() }) beforeEach(() => { liquid = new Liquid() })
it('should render else when predicate yields true', async function () { it('should render else when predicate yields true', async function () {
// 0 is truthy // 0 is truthy
const src = '{% unless 0 %}yes{%else%}no{%endunless%}' const src = '{% unless 0 %}yes{%else%}no{%endunless%}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('no') return expect(html).toBe('no')
}) })
it('should support elsif', async function () { it('should support elsif', async function () {
const src = '{% unless true %}1{%elsif true%}2{%else%}3{%endunless%}' const src = '{% unless true %}1{%elsif true%}2{%else%}3{%endunless%}'
const html = await liquid.parseAndRender(src) 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 () { it('should render unless when predicate yields false', async function () {
const src = '{% unless false %}yes{%else%}no{%endunless%}' const src = '{% unless false %}yes{%else%}no{%endunless%}'
const html = await liquid.parseAndRender(src) 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 () { it('should reject when tag not closed', function () {
const src = '{% unless 1 > 2 %}yes' const src = '{% unless 1 > 2 %}yes'
return expect(liquid.parseAndRender(src)) return expect(liquid.parseAndRender(src))
.to.be.rejectedWith(/tag {% unless 1 > 2 %} not closed/) .rejects.toThrow(/tag {% unless 1 > 2 %} not closed/)
}) })
it('should render unless when predicate yields false and else undefined', async function () { it('should render unless when predicate yields false and else undefined', async function () {
const src = '{% unless 1 > 2 %}yes{%endunless%}' const src = '{% unless 1 > 2 %}yes{%endunless%}'
const html = await liquid.parseAndRender(src) 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 () { it('should render "" when predicate yields false and else undefined', async function () {
const src = '{% unless 1 < 2 %}yes{%endunless%}' const src = '{% unless 1 < 2 %}yes{%endunless%}'
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('') return expect(html).toBe('')
}) })
it('should output unless contents in order', async function () { it('should output unless contents in order', async function () {
@@ -46,7 +42,7 @@ describe('tags/unless', function () {
{% unless false %}Inside {{ location }}{% endunless %} {% unless false %}Inside {{ location }}{% endunless %}
After {{ location }}` After {{ location }}`
const html = await liquid.parseAndRender(src, { location: 'wonderland' }) const html = await liquid.parseAndRender(src, { location: 'wonderland' })
expect(html).to.equal(` expect(html).toBe(`
Before wonderland Before wonderland
Inside wonderland Inside wonderland
After wonderland`) After wonderland`)
@@ -56,12 +52,12 @@ describe('tags/unless', function () {
it('should render else when predicate yields true', function () { it('should render else when predicate yields true', function () {
const src = '{% unless 0 %}yes{%else%}no{%endunless%}' const src = '{% unless 0 %}yes{%else%}no{%endunless%}'
const html = liquid.parseAndRenderSync(src) const html = liquid.parseAndRenderSync(src)
expect(html).to.equal('no') expect(html).toBe('no')
}) })
it('should render unless when predicate yields false', function () { it('should render unless when predicate yields false', function () {
const src = '{% unless false %}yes{%else%}no{%endunless%}' const src = '{% unless false %}yes{%else%}no{%endunless%}'
const html = liquid.parseAndRenderSync(src) 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 { RenderError } from '../../../src/util/error'
import { Liquid } from '../../../src/liquid' import { Liquid } from '../../../src/liquid'
import * as path from 'path' import * as path from 'path'
import { mock, restore } from '../../stub/mockfs' import { mock, restore } from '../../stub/mockfs'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
let engine = new Liquid() let engine = new Liquid()
const strictEngine = new Liquid({ const strictEngine = new Liquid({
@@ -18,9 +14,10 @@ describe('error', function () {
describe('TokenizationError', function () { describe('TokenizationError', function () {
it('should throw TokenizationError when tag illegal', async function () { it('should throw TokenizationError when tag illegal', async function () {
const err = await expect(engine.parseAndRender('{% . a %}', {})).be.rejected await expect(engine.parseAndRender('{% . a %}', {})).rejects.toMatchObject({
expect(err.name).to.equal('TokenizationError') name: 'TokenizationError',
expect(err.message).to.contain('illegal tag syntax') message: expect.stringContaining('illegal tag syntax')
})
}) })
it('should contain template content in err.message', async function () { it('should contain template content in err.message', async function () {
const html = ['1st', '2nd', 'X{% . a %} Y', '4th'] const html = ['1st', '2nd', 'X{% . a %} Y', '4th']
@@ -31,33 +28,41 @@ describe('error', function () {
' 4| 4th', ' 4| 4th',
'TokenizationError' 'TokenizationError'
] ]
const err = await expect(engine.parseAndRender(html.join('\n'))).be.rejected await expect(engine.parseAndRender(html.join('\n'))).rejects.toMatchObject({
expect(err.message).to.equal('illegal tag syntax, line:3, col:2') message: 'illegal tag syntax, line:3, col:2',
expect(err.stack).to.contain(message.join('\n')) stack: expect.stringContaining(message.join('\n')),
expect(err.name).to.equal('TokenizationError') name: 'TokenizationError'
})
}) })
it('should contain the whole template content in err.token.input', async function () { it('should contain the whole template content in err.token.input', async function () {
const html = 'bar\nfoo{% . a %}\nfoo' const html = 'bar\nfoo{% . a %}\nfoo'
const err = await expect(engine.parseAndRender(html)).be.rejected await expect(engine.parseAndRender(html)).rejects.toMatchObject({
expect(err.token.input).to.equal(html) token: expect.objectContaining({
input: html
})
})
}) })
it('should contain stack in err.stack', async function () { it('should contain stack in err.stack', async function () {
const err = await expect(engine.parseAndRender('{% . a %}')).be.rejected await expect(engine.parseAndRender('{% . a %}')).rejects.toMatchObject({
expect(err.message).to.contain('illegal tag syntax') message: expect.stringContaining('illegal tag syntax'),
expect(err.stack).to.contain('at Liquid.parse') stack: expect.stringContaining('at Liquid.parse')
})
}) })
describe('captureStackTrace compatibility', function () { describe('captureStackTrace compatibility', function () {
it('should be empty when captureStackTrace undefined', async function () { it('should be empty when captureStackTrace undefined', async function () {
const err = await expect(engine.parseAndRender('{% . a %}')).be.rejected await expect(engine.parseAndRender('{% . a %}')).rejects.toMatchObject({
expect(err.stack).to.contain('illegal tag syntax') stack: expect.stringContaining('illegal tag syntax')
expect(err.stack).to.not.contain('at Object.parse') })
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 () { 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 await expect(engine.parseAndRender('1\n2\nfoo{% assign a = 4 }\n4')).rejects.toMatchObject({
console.log(err.stack) name: 'TokenizationError',
expect(err.name).to.equal('TokenizationError') message: 'tag "{% assign a =..." not closed, line:3, col:4'
expect(err.message).to.equal('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 () { it('should throw RenderError when tag throws', async function () {
const src = '{%throwingTag%}' const src = '{%throwingTag%}'
const err = await expect(engine.parseAndRender(src)).be.rejected await expect(engine.parseAndRender(src)).rejects.toMatchObject({
expect(err.name).to.equal('RenderError') name: 'RenderError',
expect(err.message).to.contain('intended render error') message: expect.stringContaining('intended render error')
})
}) })
it('should throw RenderError when tag rejects', async function () { it('should throw RenderError when tag rejects', async function () {
const src = '{%rejectingTag%}' const src = '{%rejectingTag%}'
const err = await expect(engine.parseAndRender(src)).be.rejected await expect(engine.parseAndRender(src)).rejects.toMatchObject({
expect(err.name).to.equal('RenderError') name: 'RenderError',
expect(err.message).to.contain('intended render reject') message: expect.stringContaining('intended render reject')
})
}) })
it('should throw RenderError when filter throws', async function () { it('should throw RenderError when filter throws', async function () {
const src = '{{1|throwingFilter}}' const src = '{{1|throwingFilter}}'
const err = await expect(engine.parseAndRender(src)).be.rejected await expect(engine.parseAndRender(src)).rejects.toMatchObject({
expect(err.name).to.equal('RenderError') name: 'RenderError',
expect(err.message).to.contain('throwed by filter') message: expect.stringContaining('throwed by filter')
})
}) })
it('should not throw when variable undefined by default', async function () { it('should not throw when variable undefined by default', async function () {
const html = await engine.parseAndRender('X{{a}}Y') 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 () { it('should throw RenderError when variable not defined', async function () {
const err = await expect(strictEngine.parseAndRender('{{a}}')).be.rejected await expect(strictEngine.parseAndRender('{{a}}')).rejects.toMatchObject({
expect(err).to.have.property('name', 'RenderError') name: 'RenderError',
expect(err.message).to.contain('undefined variable: a') message: expect.stringContaining('undefined variable: a')
})
}) })
it('should contain template context in err.stack', async function () { it('should contain template context in err.stack', async function () {
const html = ['1st', '2nd', '3rd', 'X{%throwingTag%} Y', '5th', '6th', '7th'] const html = ['1st', '2nd', '3rd', 'X{%throwingTag%} Y', '5th', '6th', '7th']
@@ -118,10 +127,11 @@ describe('error', function () {
' 7| 7th', ' 7| 7th',
'RenderError' 'RenderError'
] ]
const err = await expect(engine.parseAndRender(html.join('\n'))).be.rejected await expect(engine.parseAndRender(html.join('\n'))).rejects.toMatchObject({
expect(err.message).to.equal('intended render error, line:4, col:2') name: 'RenderError',
expect(err.stack).to.contain(message.join('\n')) message: 'intended render error, line:4, col:2',
expect(err.name).to.equal('RenderError') stack: expect.stringContaining(message.join('\n'))
})
}) })
it('should contain original error info for {% layout %}', async function () { it('should contain original error info for {% layout %}', async function () {
mock({ mock({
@@ -145,10 +155,11 @@ describe('error', function () {
' 7| 7th', ' 7| 7th',
'RenderError' 'RenderError'
] ]
const err = await expect(engine.parseAndRender(html)).be.rejected await expect(engine.parseAndRender(html)).rejects.toMatchObject({
expect(err.message).to.equal(`intended render error, file:${path.resolve('/throwing-tag.html')}, line:4, col:2`) name: 'RenderError',
expect(err.stack).to.contain(message.join('\n')) message: `intended render error, file:${path.resolve('/throwing-tag.html')}, line:4, col:2`,
expect(err.name).to.equal('RenderError') stack: expect.stringContaining(message.join('\n'))
})
}) })
it('should contain original error info for {% include %}', async function () { it('should contain original error info for {% include %}', async function () {
const origin = ['1st', '2nd', '3rd', 'X{%throwingTag%} Y', '5th', '6th', '7th'] const origin = ['1st', '2nd', '3rd', 'X{%throwingTag%} Y', '5th', '6th', '7th']
@@ -165,15 +176,17 @@ describe('error', function () {
' 7| 7th', ' 7| 7th',
'RenderError' 'RenderError'
] ]
const err = await expect(engine.parseAndRender(html)).be.rejected await expect(engine.parseAndRender(html)).rejects.toMatchObject({
expect(err.message).to.equal(`intended render error, file:${path.resolve('/throwing-tag.html')}, line:4, col:2`) name: 'RenderError',
expect(err.stack).to.contain(message.join('\n')) message: `intended render error, file:${path.resolve('/throwing-tag.html')}, line:4, col:2`,
expect(err.name).to.equal('RenderError') stack: expect.stringContaining(message.join('\n'))
})
}) })
it('should contain stack in err.stack', async function () { it('should contain stack in err.stack', async function () {
const err = await expect(engine.parseAndRender('{%rejectingTag%}')).be.rejected await expect(engine.parseAndRender('{%rejectingTag%}')).rejects.toMatchObject({
expect(err.message).to.contain('intended render reject') message: expect.stringContaining('intended render reject'),
expect(err.stack).to.match(/at .*:\d+:\d+/) stack: expect.stringMatching(/at .*:\d+:\d+/)
})
}) })
}) })
@@ -188,30 +201,35 @@ describe('error', function () {
}) })
}) })
it('should throw ParseError when filter not defined', async function () { it('should throw ParseError when filter not defined', async function () {
const err = await expect(strictEngine.parseAndRender('{{1 | a}}')).be.rejected await expect(strictEngine.parseAndRender('{{1 | a}}')).rejects.toMatchObject({
expect(err).to.have.property('name', 'ParseError') name: 'ParseError',
expect(err.message).to.contain('undefined filter: a') message: expect.stringContaining('undefined filter: a')
})
}) })
it('should throw ParseError when tag not closed', async function () { it('should throw ParseError when tag not closed', async function () {
const err = await expect(engine.parseAndRender('{% if %}')).be.rejected await expect(engine.parseAndRender('{% if %}')).rejects.toMatchObject({
expect(err.name).to.equal('ParseError') name: 'ParseError',
expect(err.message).to.contain('tag {% if %} not closed') message: expect.stringContaining('tag {% if %} not closed')
})
}) })
it('should throw ParseError when tag parse throws', async function () { it('should throw ParseError when tag parse throws', async function () {
const err = await expect(engine.parseAndRender('{%throwsOnParse%}')).be.rejected await expect(engine.parseAndRender('{%throwsOnParse%}')).rejects.toMatchObject({
expect(err.name).to.equal('ParseError') name: 'ParseError',
expect(err.message).to.contain('intended parse error') message: expect.stringContaining('intended parse error')
})
}) })
it('should throw ParseError when tag not found', async function () { it('should throw ParseError when tag not found', async function () {
const src = '{%if true%}\naaa{%endif%}\n{% -a %}\n3' const src = '{%if true%}\naaa{%endif%}\n{% -a %}\n3'
const err = await expect(engine.parseAndRender(src)).be.rejected await expect(engine.parseAndRender(src)).rejects.toMatchObject({
expect(err.name).to.equal('ParseError') name: 'ParseError',
expect(err.message).to.contain('tag "-a" not found') message: expect.stringContaining('tag "-a" not found')
})
}) })
it('should throw ParseError when tag not exist', async function () { it('should throw ParseError when tag not exist', async function () {
const err = await expect(engine.parseAndRender('{% a %}')).be.rejected await expect(engine.parseAndRender('{% a %}')).rejects.toMatchObject({
expect(err.name).to.equal('ParseError') name: 'ParseError',
expect(err.message).to.contain('tag "a" not found') message: expect.stringContaining('tag "a" not found')
})
}) })
it('should contain template context in err.stack', async function () { it('should contain template context in err.stack', async function () {
@@ -225,10 +243,11 @@ describe('error', function () {
' 7| 7th', ' 7| 7th',
'ParseError: tag "a" not found' 'ParseError: tag "a" not found'
] ]
const err = await expect(engine.parseAndRender(html.join('\n'))).be.rejected await expect(engine.parseAndRender(html.join('\n'))).rejects.toMatchObject({
expect(err.message).to.equal('tag "a" not found, line:4, col:2') name: 'ParseError',
expect(err.stack).to.contain(message.join('\n')) message: 'tag "a" not found, line:4, col:2',
expect(err.name).to.equal('ParseError') stack: expect.stringContaining(message.join('\n'))
})
}) })
it('should handle err.message when context not enough', async function () { it('should handle err.message when context not enough', async function () {
@@ -240,15 +259,19 @@ describe('error', function () {
' 4| 4th', ' 4| 4th',
'ParseError: tag "a" not found' 'ParseError: tag "a" not found'
] ]
const err = await expect(engine.parseAndRender(html.join('\n'))).be.rejected await expect(engine.parseAndRender(html.join('\n'))).rejects.toMatchObject({
expect(err.message).to.equal('tag "a" not found, line:2, col:2') message: 'tag "a" not found, line:2, col:2',
expect(err.stack).to.contain(message.join('\n')) stack: expect.stringContaining(message.join('\n'))
})
}) })
it('should contain stack in err.stack', async function () { it('should contain stack in err.stack', async function () {
const err = await expect(engine.parseAndRender('{% -a %}')).be.rejected await expect(engine.parseAndRender('{% -a %}')).rejects.toMatchObject({
expect(err.stack).to.contain('ParseError: tag "-a" not found') stack: expect.stringContaining('ParseError: tag "-a" not found')
expect(err.stack).to.match(/at .*:\d+:\d+\)/) })
await expect(engine.parseAndRender('{% -a %}')).rejects.toMatchObject({
stack: expect.stringMatching(/at .*:\d+:\d+\)/)
})
}) })
}) })
describe('sync support', function () { describe('sync support', function () {
@@ -265,8 +288,8 @@ describe('error', function () {
}) })
it('should throw RenderError when tag throws', function () { it('should throw RenderError when tag throws', function () {
const src = '{%throwingTag%}' const src = '{%throwingTag%}'
expect(() => engine.parseAndRenderSync(src)) expect(() => engine.parseAndRenderSync(src)).toThrow(RenderError)
.to.throw(RenderError, /intended render error/) expect(() => engine.parseAndRenderSync(src)).toThrow(/intended render error/)
}) })
it('should contain original error info for {% include %}', function () { it('should contain original error info for {% include %}', function () {
mock({ mock({
@@ -286,9 +309,9 @@ describe('error', function () {
engine.parseAndRenderSync(html) engine.parseAndRenderSync(html)
throw new Error('expected throw') throw new Error('expected throw')
} catch (err) { } catch (err) {
expect(err.message).to.equal(`intended render error, file:${path.resolve('/throwing-tag.html')}, line:4, col:2`) expect(err).toHaveProperty('name', 'RenderError')
expect(err.stack).to.contain(message.join('\n')) expect(err).toHaveProperty('message', `intended render error, file:${path.resolve('/throwing-tag.html')}, line:4, col:2`)
expect(err.name).to.equal('RenderError') 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 { 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' import { resolve } from 'path'
interface FileDescriptor { interface FileDescriptor {
+2 -3
View File
@@ -1,6 +1,5 @@
import { Liquid } from '../../src/liquid' import { Liquid } from '../../src/liquid'
import { LiquidOptions } from '../../src/liquid-options' import { LiquidOptions } from '../../src/liquid-options'
import { expect } from 'chai'
export const liquid = new Liquid() 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 engine = opts ? new Liquid(opts) : liquid
const result = await engine.parseAndRender(src, ctx as object) const result = await engine.parseAndRender(src, ctx as object)
if (expected instanceof RegExp) return expect(result).to.match(expected) if (expected instanceof RegExp) return expect(result).toMatch(expected)
return expect(result).to.equal(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')
})
})
})