feat: move filters/tags to instances, fixes #188

This commit is contained in:
harttle
2020-03-03 00:02:29 +08:00
parent f6762078e0
commit df8a919f71
47 changed files with 240 additions and 221 deletions
+2 -2
View File
@@ -9,13 +9,13 @@ describe('liquid#registerFilter()', function () {
return JSON.stringify(args)
})
it('should support object', async () => {
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 html = await liquid.parseAndRender(src, { foo: 'bar' })
return expect(html).to.equal(dst)
})
it('should support mixed object', 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 html = await liquid.parseAndRender(src, { foo: 'bar' })
return expect(html).to.equal(dst)
+8 -8
View File
@@ -214,12 +214,12 @@ describe('error', function () {
const src = '{%if true%}\naaa{%endif%}\n{% -a %}\n3'
const err = await expect(engine.parseAndRender(src)).be.rejected
expect(err.name).to.equal('ParseError')
expect(err.message).to.contain('tag -a not found')
expect(err.message).to.contain('tag "-a" not found')
})
it('should throw ParseError when tag not exist', async function () {
const err = await expect(engine.parseAndRender('{% a %}')).be.rejected
expect(err.name).to.equal('ParseError')
expect(err.message).to.contain('tag a not found')
expect(err.message).to.contain('tag "a" not found')
})
it('should contain template context in err.stack', async function () {
@@ -231,10 +231,10 @@ describe('error', function () {
' 5| 5th',
' 6| 6th',
' 7| 7th',
'ParseError: tag a not found'
'ParseError: tag "a" not found'
]
const err = await expect(engine.parseAndRender(html.join('\n'))).be.rejected
expect(err.message).to.equal('tag a not found, line:4, col:2')
expect(err.message).to.equal('tag "a" not found, line:4, col:2')
expect(err.stack).to.contain(message.join('\n'))
expect(err.name).to.equal('ParseError')
})
@@ -246,10 +246,10 @@ describe('error', function () {
'>> 2| X{% a %} {% enda %} Y',
' 3| 3rd',
' 4| 4th',
'ParseError: tag a not found'
'ParseError: tag "a" not found'
]
const err = await expect(engine.parseAndRender(html.join('\n'))).be.rejected
expect(err.message).to.equal('tag a not found, line:2, col:2')
expect(err.message).to.equal('tag "a" not found, line:2, col:2')
expect(err.stack).to.contain(message.join('\n'))
})
@@ -261,12 +261,12 @@ describe('error', function () {
it('should contain stack in err.stack', async function () {
const err = await expect(engine.parseAndRender('{% -a %}')).be.rejected
expect(err.stack).to.contain('ParseError: tag -a not found')
expect(err.stack).to.contain('ParseError: tag "-a" not found')
expect(err.stack).to.match(/at .*:\d+:\d+\)/)
})
})
describe('sync support', function () {
let engine
let engine: Liquid
beforeEach(function () {
engine = new Liquid({
root: '/'
-4
View File
@@ -1,8 +1,6 @@
import { expect } from 'chai'
import { Context } from '../../../src/context/context'
import { Token } from '../../../src/parser/token'
import { Tag } from '../../../src/template/tag/tag'
import { Filter } from '../../../src/template/filter/filter'
import { Render } from '../../../src/render/render'
import { HTML } from '../../../src/template/html'
import { toThenable } from '../../../src/util/async'
@@ -10,8 +8,6 @@ import { toThenable } from '../../../src/util/async'
describe('render', function () {
let render: Render
before(function () {
Filter.clear()
Tag.clear()
render = new Render()
})
+20 -19
View File
@@ -1,69 +1,70 @@
import * as chai from 'chai'
import * as sinon from 'sinon'
import * as sinonChai from 'sinon-chai'
import { Filter } from '../../../../src/template/filter/filter'
import { Context } from '../../../../src/context/context'
import { toThenable } from '../../../../src/util/async'
import { FilterMap } from '../../../../src/template/filter/filter-map'
chai.use(sinonChai)
const expect = chai.expect
describe('filter', function () {
let ctx: Context
let filters: FilterMap
beforeEach(function () {
Filter.clear()
filters = new FilterMap(false)
ctx = new Context()
})
it('should create default filter if not registered', async function () {
const result = new Filter('foo', [], false) as any
const result = filters.create('foo', []) as any
expect(result.name).to.equal('foo')
})
it('should render input if filter not registered', async function () {
expect(await toThenable(new Filter('undefined', [], false).render('foo', ctx))).to.equal('foo')
expect(await toThenable(filters.create('undefined', []).render('foo', ctx))).to.equal('foo')
})
it('should call filter impl with correct arguments', async function () {
const spy = sinon.spy()
Filter.register('foo', spy)
await toThenable(new Filter('foo', ['33'], false).render('foo', ctx))
filters.set('foo', spy)
await toThenable(filters.create('foo', ['33']).render('foo', ctx))
expect(spy).to.have.been.calledWith('foo', 33)
})
it('should call filter impl with correct this arg', async function () {
const spy = sinon.spy()
Filter.register('foo', spy)
await toThenable(new Filter('foo', ['33'], false).render('foo', ctx))
filters.set('foo', spy)
await toThenable(filters.create('foo', ['33']).render('foo', ctx))
expect(spy).to.have.been.calledOn(sinon.match.has('context', ctx))
})
it('should render a simple filter', async function () {
Filter.register('upcase', x => x.toUpperCase())
expect(await toThenable(new Filter('upcase', [], false).render('foo', ctx))).to.equal('FOO')
filters.set('upcase', x => x.toUpperCase())
expect(await toThenable(filters.create('upcase', []).render('foo', ctx))).to.equal('FOO')
})
it('should render filters with argument', async function () {
Filter.register('add', (a, b) => a + b)
expect(await toThenable(new Filter('add', ['2'], false).render(3, ctx))).to.equal(5)
filters.set('add', (a, b) => a + b)
expect(await toThenable(filters.create('add', ['2']).render(3, ctx))).to.equal(5)
})
it('should render filters with multiple arguments', async function () {
Filter.register('add', (a, b, c) => a + b + c)
expect(await toThenable(new Filter('add', ['2', '"c"'], false).render(3, ctx))).to.equal('5c')
filters.set('add', (a, b, c) => a + b + c)
expect(await toThenable(filters.create('add', ['2', '"c"']).render(3, ctx))).to.equal('5c')
})
it('should pass Objects/Drops as it is', async function () {
Filter.register('name', a => a.constructor.name)
filters.set('name', a => a.constructor.name)
class Foo {}
expect(await toThenable(new Filter('name', [], false).render(new Foo(), ctx))).to.equal('Foo')
expect(await toThenable(filters.create('name', []).render(new Foo(), ctx))).to.equal('Foo')
})
it('should not throw when filter name illegal', function () {
expect(function () {
new Filter('/', [], false)
filters.create('/', [])
}).to.not.throw()
})
it('should support key value pairs', async function () {
Filter.register('add', (a, b) => b[0] + ':' + (a + b[1]))
expect(await toThenable((new Filter('add', [['num', '2']], false).render(3, ctx)))).to.equal('num:5')
filters.set('add', (a, b) => b[0] + ':' + (a + b[1]))
expect(await toThenable((filters.create('add', [['num', '2']]).render(3, ctx)))).to.equal('num:5')
})
})
+7 -6
View File
@@ -3,14 +3,15 @@ import { toThenable } from '../../../src/util/async'
import { Context } from '../../../src/context/context'
import { Output } from '../../../src/template/output'
import { OutputToken } from '../../../src/parser/output-token'
import { Filter } from '../../../src/template/filter/filter'
import { FilterMap } from '../../../src/template/filter/filter-map'
const expect = chai.expect
describe('Output', function () {
const emitter: any = { write: (html: string) => (emitter.html += html), html: '' }
let filters: FilterMap
beforeEach(function () {
Filter.clear()
filters = new FilterMap(false)
emitter.html = ''
})
@@ -18,25 +19,25 @@ describe('Output', function () {
const scope = new Context({
foo: { obj: { arr: ['a', 2] } }
})
const output = new Output({ value: 'foo' } as OutputToken, false)
const output = new Output({ value: 'foo' } as OutputToken, filters)
await toThenable(output.render(scope, emitter))
return expect(emitter.html).to.equal('[object Object]')
})
it('should skip function property', async function () {
const scope = new Context({ obj: { foo: 'foo', bar: (x: any) => x } })
const output = new Output({ value: 'obj' } as OutputToken, false)
const output = new Output({ value: 'obj' } as OutputToken, filters)
await toThenable(output.render(scope, emitter))
return expect(emitter.html).to.equal('[object Object]')
})
it('should respect to .toString()', async () => {
const scope = new Context({ obj: { toString: () => 'FOO' } })
const output = new Output({ value: 'obj' } as OutputToken, false)
const output = new Output({ value: 'obj' } as OutputToken, filters)
await toThenable(output.render(scope, emitter))
return expect(emitter.html).to.equal('FOO')
})
it('should respect to .toString()', async () => {
const scope = new Context({ obj: { toString: () => 'FOO' } })
const output = new Output({ value: 'obj' } as OutputToken, false)
const output = new Output({ value: 'obj' } as OutputToken, filters)
await toThenable(output.render(scope, emitter))
return expect(emitter.html).to.equal('FOO')
})
+7 -13
View File
@@ -9,10 +9,10 @@ import { toThenable } from '../../../src/util/async'
chai.use(sinonChai)
const expect = chai.expect
const liquid = new Liquid()
describe('Tag', function () {
let ctx: Context
let liquid: Liquid
const emitter: any = { write: (html: string) => (emitter.html += html), html: '' }
before(function () {
ctx = new Context({
@@ -22,7 +22,7 @@ describe('Tag', function () {
coo: 'uoo'
}
})
Tag.clear()
liquid = new Liquid()
})
beforeEach(function () {
emitter.html = ''
@@ -33,24 +33,20 @@ describe('Tag', function () {
new Tag({ // eslint-disable-line
type: 'tag',
value: 'foo',
name: 'foo'
name: 'not-exist'
} as TagToken, [], liquid)
}).to.throw(/tag foo not found/)
}).to.throw(/tag "not-exist" not found/)
})
it('should register simple tag', function () {
expect(function () {
Tag.register('foo', {
render: () => 'bar'
})
liquid.registerTag('foo', { render: () => 'bar' })
}).not.throw()
})
it('should call tag.render', async function () {
const spy = sinon.spy()
Tag.register('foo', {
render: spy
})
liquid.registerTag('foo', { render: spy })
const token = {
type: 'tag',
value: 'foo',
@@ -64,9 +60,7 @@ describe('Tag', function () {
let spy: sinon.SinonSpy, token: TagToken
beforeEach(function () {
spy = sinon.spy()
Tag.register('foo', {
render: spy
})
liquid.registerTag('foo', { render: spy })
token = {
type: 'tag',
value: 'foo aa:foo bb: arr[0] cc: 2.3\ndd:bar.coo',
+18 -18
View File
@@ -1,9 +1,9 @@
import * as chai from 'chai'
import { toThenable } from '../../../src/util/async'
import { FilterMap } from '../../../src/template/filter/filter-map'
import * as sinonChai from 'sinon-chai'
import * as sinon from 'sinon'
import { Context } from '../../../src/context/context'
import { Filter } from '../../../src/template/filter/filter'
import { Value } from '../../../src/template/value'
chai.use(sinonChai)
@@ -11,74 +11,73 @@ chai.use(sinonChai)
const expect = chai.expect
describe('Value', function () {
beforeEach(() => Filter.clear())
describe('#constructor()', function () {
const filterMap = new FilterMap(false)
it('should parse "foo', function () {
const tpl = new Value('foo', false)
const tpl = new Value('foo', filterMap)
expect(tpl.initial).to.equal('foo')
expect(tpl.filters).to.deep.equal([])
})
it('should parse "foo | add"', function () {
const tpl = new Value('foo | add', false)
const tpl = new Value('foo | add', filterMap)
expect(tpl.initial).to.equal('foo')
expect(tpl.filters.length).to.equal(1)
expect(tpl.filters[0].args).to.eql([])
})
it('should parse "foo,foo | add"', function () {
const tpl = new Value('foo,foo | add', false)
const tpl = new Value('foo,foo | add', filterMap)
expect(tpl.initial).to.equal('foo')
expect(tpl.filters.length).to.equal(1)
expect(tpl.filters[0].args).to.eql([])
})
it('should parse "foo | add: 3, false"', function () {
const tpl = new Value('foo | add: 3, "foo"', false)
const tpl = new Value('foo | add: 3, "foo"', filterMap)
expect(tpl.initial).to.equal('foo')
expect(tpl.filters.length).to.equal(1)
expect(tpl.filters[0].args).to.eql(['3', '"foo"'])
})
it('should parse "foo | add: "foo" bar, 3"', function () {
const tpl = new Value('foo | add: "foo" bar, 3', false)
const tpl = new Value('foo | add: "foo" bar, 3', filterMap)
expect(tpl.initial).to.equal('foo')
expect(tpl.filters.length).to.equal(1)
expect(tpl.filters[0].name).to.eql('add')
expect(tpl.filters[0].args).to.eql(['"foo"', '3'])
})
it('should parse "foo | add: "|", 3', function () {
const tpl = new Value('foo | add: "|", 3', false)
const tpl = new Value('foo | add: "|", 3', filterMap)
expect(tpl.initial).to.equal('foo')
expect(tpl.filters.length).to.equal(1)
expect(tpl.filters[0].args).to.eql(['"|"', '3'])
})
it('should parse "foo | add: "|", 3', function () {
const tpl = new Value('foo | add: "|", 3', false)
const tpl = new Value('foo | add: "|", 3', filterMap)
expect(tpl.initial).to.equal('foo')
expect(tpl.filters.length).to.equal(1)
expect(tpl.filters[0].args).to.eql(['"|"', '3'])
})
it('should support arguments as named key/values', function () {
const f = new Value('o | foo: key1: "literal1", key2: value2', false)
const f = new Value('o | foo: key1: "literal1", key2: value2', filterMap)
expect(f.filters[0].name).to.equal('foo')
expect(f.filters[0].args).to.eql([['key1', '"literal1"'], ['key2', 'value2']])
})
it('should support arguments as named key/values with inline literals', function () {
const f = new Value('o | foo: "test0", key1: "literal1", key2: value2', false)
const f = new Value('o | foo: "test0", key1: "literal1", key2: value2', filterMap)
expect(f.filters[0].name).to.equal('foo')
expect(f.filters[0].args).to.deep.equal(['"test0"', ['key1', '"literal1"'], ['key2', 'value2']])
})
it('should support arguments as named key/values with inline values', function () {
const f = new Value('o | foo: test0, key1: "literal1", key2: value2', false)
const f = new Value('o | foo: test0, key1: "literal1", key2: value2', filterMap)
expect(f.filters[0].name).to.equal('foo')
expect(f.filters[0].args).to.deep.equal(['test0', ['key1', '"literal1"'], ['key2', 'value2']])
})
it('should support argument values named same as keys', function () {
const f = new Value('o | foo: a: a', false)
const f = new Value('o | foo: a: a', filterMap)
expect(f.filters[0].name).to.equal('foo')
expect(f.filters[0].args).to.deep.equal([['a', 'a']])
})
it('should support argument literals named same as keys', function () {
const f = new Value('o | foo: a: "a"', false)
const f = new Value('o | foo: a: "a"', filterMap)
expect(f.filters[0].name).to.equal('foo')
expect(f.filters[0].args).to.deep.equal([['a', '"a"']])
})
@@ -115,9 +114,10 @@ describe('Value', function () {
it('should call chained filters correctly', async function () {
const date = sinon.stub().returns('y')
const time = sinon.spy()
Filter.register('date', date)
Filter.register('time', time)
const tpl = new Value('foo.bar | date: "b" | time:2', false)
const filterMap = new FilterMap(false)
filterMap.set('date', date)
filterMap.set('time', time)
const tpl = new Value('foo.bar | date: "b" | time:2', filterMap)
const scope = new Context({
foo: { bar: 'bar' }
})