perf: introduce AST to avoid reparse

This commit is contained in:
harttle
2020-03-15 02:51:25 +08:00
committed by Jun Yang
parent 3b58f1c3f6
commit d2d6a38235
96 changed files with 1553 additions and 1168 deletions
+30 -104
View File
@@ -24,123 +24,49 @@ describe('Context', function () {
ctx = new Context(scope)
})
describe('#propertyAccessSeq()', function () {
it('should handle dot syntax', async function () {
expect(ctx.parseProp('foo.bar'))
.to.deep.equal(['foo', 'bar'])
})
it('should handle [<String>] syntax', async function () {
expect(ctx.parseProp('foo["bar"]'))
.to.deep.equal(['foo', 'bar'])
})
it('should handle [<Identifier>] syntax', async function () {
expect(ctx.parseProp('foo[foo]'))
.to.deep.equal(['foo', 'zoo'])
})
it('should handle nested access 1', async function () {
expect(ctx.parseProp('foo[bar.zoo]'))
.to.deep.equal(['foo', 'coo'])
})
it('should handle nested access 2', async function () {
expect(ctx.parseProp('foo[bar["zoo"]]'))
.to.deep.equal(['foo', 'coo'])
})
it('should handle nested access 3', async function () {
expect(ctx.parseProp('bar["foo"].zoo'))
.to.deep.equal(['bar', 'foo', 'zoo'])
})
it('should handle nested access 4', async function () {
expect(ctx.parseProp('foo[0].bar'))
.to.deep.equal(['foo', '0', 'bar'])
})
it('should handle nested access 5', async function () {
expect(ctx.parseProp('foo[one].bar'))
.to.deep.equal(['foo', '1', 'bar'])
})
it('should handle nested access 6', async function () {
expect(ctx.parseProp('foo[two].bar'))
.to.deep.equal(['foo', 'undefined', 'bar'])
})
})
describe('#get()', function () {
it('should get direct property', async function () {
expect(ctx.get('foo')).equal('zoo')
expect(ctx.get(['foo'])).equal('zoo')
})
it('should read nested property', async function () {
expect(ctx.get(['obj', 'first'])).to.equal('f')
expect(ctx.get(['obj', 'last'])).to.equal('l')
expect(ctx.get(['obj', 'size'])).to.equal(undefined)
})
it('undefined property should yield undefined', async function () {
expect(ctx.get('notdefined')).to.equal(undefined)
expect(ctx.get(false as any)).to.equal(undefined)
})
it('should throw for invalid path', async function () {
expect(() => ctx.get('')).to.throw('invalid path:""')
})
it('should throw when [] unbalanced', async function () {
expect(() => ctx.get('foo[bar')).to.throw(/unbalanced \[\]/)
})
it('should throw when "" unbalanced', async function () {
expect(() => ctx.get('foo["bar]')).to.throw(/unbalanced "/)
})
it("should throw when '' unbalanced", async function () {
expect(() => ctx.get("foo['bar]")).to.throw(/unbalanced '/)
expect(ctx.get(['notdefined'])).to.equal(undefined)
expect(ctx.get([false as any])).to.equal(undefined)
})
it('should respect to toLiquid', async function () {
const scope = new Context({ foo: {
toLiquid: () => ({ bar: 'BAR' }),
bar: 'bar'
} })
expect(scope.get('foo.bar')).to.equal('BAR')
expect(scope.get(['foo', 'bar'])).to.equal('BAR')
})
it('should access child property via dot syntax', async function () {
expect(ctx.get('bar.zoo')).to.equal('coo')
expect(ctx.get('bar.arr')).to.deep.equal(['a', 'b'])
})
it('should access child property via [<String>] syntax', async function () {
expect(ctx.get('bar["zoo"]')).to.equal('coo')
})
it('should access child property via [<Number>] syntax', async function () {
expect(ctx.get('bar.arr[0]')).to.equal('a')
})
it('should access child property via [<Identifier>] syntax', async function () {
expect(ctx.get('bar[foo]')).to.equal('coo')
})
it('should return undefined when not exist', async function () {
expect(ctx.get('foo.foo.foo')).to.be.undefined
expect(ctx.get(['foo', 'foo', 'foo'])).to.be.undefined
})
it('should return string length as size', async function () {
expect(ctx.get('foo.size')).to.equal(3)
expect(ctx.get(['foo', 'size'])).to.equal(3)
})
it('should return array length as size', async function () {
expect(ctx.get('bar.arr.size')).to.equal(2)
})
it('should return size property if exists', async function () {
expect(ctx.get('zoo.size')).to.equal(4)
})
it('should return undefined if do not have size and length', async function () {
expect(ctx.get('one.size')).to.equal(undefined)
expect(ctx.get(['bar', 'arr', 'size'])).to.equal(2)
})
it('should read .first of array', async function () {
expect(ctx.get('bar.arr.first')).to.equal('a')
})
it('should read .first of object', async function () {
expect(ctx.get('obj.first')).to.equal('f')
expect(ctx.get(['bar', 'arr', 'first'])).to.equal('a')
})
it('should read .last of array', async function () {
expect(ctx.get('bar.arr.last')).to.equal('b')
})
it('should read .last of object', async function () {
expect(ctx.get('obj.last')).to.equal('l')
expect(ctx.get(['bar', 'arr', 'last'])).to.equal('b')
})
})
describe('#getFromScope()', function () {
it('should support string', () => {
expect(ctx.getFromScope({ obj: { foo: 'FOO' } }, 'obj.foo')).to.equal('FOO')
})
})
describe('strictVariables', async function () {
let ctx: Context
beforeEach(function () {
@@ -149,22 +75,22 @@ describe('Context', function () {
} as any)
})
it('should throw when variable not defined', function () {
return expect(() => ctx.get('notdefined')).to.throw(/undefined variable: notdefined/)
return expect(() => ctx.get(['notdefined'])).to.throw(/undefined variable: notdefined/)
})
it('should throw when deep variable not exist', async function () {
ctx.push({ foo: 'FOO' })
return expect(() => ctx.get('foo.bar.not.defined')).to.throw(/undefined variable: bar/)
return expect(() => ctx.get(['foo', 'bar', 'not', 'defined'])).to.throw(/undefined variable: bar/)
})
it('should throw when itself not defined', async function () {
ctx.push({ foo: 'FOO' })
return expect(() => ctx.get('foo.BAR')).to.throw(/undefined variable: BAR/)
return expect(() => ctx.get(['foo', 'BAR'])).to.throw(/undefined variable: BAR/)
})
it('should find variable in parent scope', async function () {
ctx.push({ 'foo': 'foo' })
ctx.push({
'bar': 'bar'
})
expect(ctx.get('foo')).to.equal('foo')
expect(ctx.get(['foo'])).to.equal('foo')
})
})
@@ -180,14 +106,14 @@ describe('Context', function () {
ctx.push({
foo: 'foo'
})
expect(ctx.get('foo')).to.equal('foo')
expect(ctx.get('bar')).to.equal('bar')
expect(ctx.get(['foo'])).to.equal('foo')
expect(ctx.get(['bar'])).to.equal('bar')
})
it('should hide deep properties by push', async function () {
ctx.push({ bar: { bar: 'bar' } })
ctx.push({ bar: { foo: 'foo' } })
expect(ctx.get('bar.foo')).to.equal('foo')
expect(ctx.get('bar.bar')).to.equal(undefined)
expect(ctx.get(['bar', 'foo'])).to.equal('foo')
expect(ctx.get(['bar', 'bar'])).to.equal(undefined)
})
})
describe('.pop()', function () {
@@ -196,7 +122,7 @@ describe('Context', function () {
foo: 'foo'
})
ctx.pop()
expect(ctx.get('foo')).to.equal('zoo')
expect(ctx.get(['foo'])).to.equal('zoo')
})
})
})
+26
View File
@@ -0,0 +1,26 @@
import { expect } from 'chai'
import { matchOperator } from '../../../src/parser/match-operator'
describe('parser/matchOperator()', function () {
it('should match contains', () => {
expect(matchOperator('contains', 0)).to.equal(8)
})
it('should match comparision', () => {
expect(matchOperator('>', 0)).to.equal(1)
expect(matchOperator('>=', 0)).to.equal(2)
expect(matchOperator('<', 0)).to.equal(1)
expect(matchOperator('<=', 0)).to.equal(2)
})
it('should match binary logic', () => {
expect(matchOperator('and', 0)).to.equal(3)
expect(matchOperator('or', 0)).to.equal(2)
})
it('should not match if word not terminate', () => {
expect(matchOperator('true1', 0)).to.equal(-1)
expect(matchOperator('containsa', 0)).to.equal(-1)
})
it('should match if word boundary found', () => {
expect(matchOperator('>=1', 0)).to.equal(2)
expect(matchOperator('contains b', 0)).to.equal(8)
})
})
@@ -1,30 +1,5 @@
import { expect } from 'chai'
import { parseLiteral, parseStringLiteral } from '../../../src/parser/literal'
import { NullDrop } from '../../../src/drop/null-drop'
describe('parseLiteral()', function () {
it('should eval boolean literal', async function () {
expect(parseLiteral('true')).to.equal(true)
expect(parseLiteral('TrUE')).to.equal(undefined)
expect(parseLiteral('false')).to.equal(false)
})
it('should eval number literal', async function () {
expect(parseLiteral('2.3')).to.equal(2.3)
expect(parseLiteral('.32')).to.equal(0.32)
expect(parseLiteral('-23.')).to.equal(-23)
expect(parseLiteral('23')).to.equal(23)
})
it('should eval string literal', async function () {
expect(parseLiteral('"ab\'c"')).to.equal("ab'c")
expect(parseLiteral("'ab\"c'")).to.equal('ab"c')
})
it('should eval nil literal', async function () {
expect(parseLiteral('nil')).to.be.instanceOf(NullDrop)
})
it('should eval null literal', async function () {
expect(parseLiteral('null')).to.be.instanceOf(NullDrop)
})
})
import { parseStringLiteral } from '../../../src/parser/parse-string-literal'
describe('parseStringLiteral()', function () {
it('should parse octal escape', () => {
@@ -36,7 +11,7 @@ describe('parseStringLiteral()', function () {
it('should skip invalid octal escape', () => {
expect(parseStringLiteral(String.raw`"\9"`)).to.equal('9')
})
it('should parse \n, \t, \r', () => {
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')
+356 -135
View File
@@ -1,210 +1,431 @@
import { expect } from 'chai'
import { WordToken } from '../../../src/tokens/word-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/parser/tag-token'
import { OutputToken } from '../../../src/parser/output-token'
import { HTMLToken } from '../../../src/parser/html-token'
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'
describe('Tokenize', function () {
it('should read quoted', () => {
expect(new Tokenizer('"foo" ff').readQuoted().toString()).to.equal('"foo"')
expect(new Tokenizer(' "foo"ff').readQuoted().toString()).to.equal('"foo"')
})
it('should read property access', () => {
expect(new Tokenizer('a[ b][ "c d" ]').readPropertyAccess().toString()).to.equal('a[ b][ "c d" ]')
expect(new Tokenizer('a.b[c[d.e]]').readPropertyAccess().toString()).to.equal('a.b[c[d.e]]')
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('2.33.2').readValue().toString()).to.equal('2.33.2')
expect(new Tokenizer('"foo"a').readValue().toString()).to.equal('"foo"')
expect(new Tokenizer('a[b]["c d"]').readValue().toString()).to.equal('a[b]["c d"]')
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 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 hash', () => {
expect(new Tokenizer('foo: 3').readHash()).to.deep.equal(['foo', '3'])
expect(new Tokenizer(', foo: a[ "bar"]').readHash()).to.deep.equal(['foo', 'a[ "bar"]'])
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 hashs', () => {
expect(new Tokenizer(', limit: 3 reverse offset:off').readHashes())
.to.deep.equal([['limit', '3'], ['reverse', ''], ['offset', 'off']])
expect(new Tokenizer('cols: 2, rows: data["rows"]').readHashes())
.to.deep.equal([['cols', '2'], ['rows', 'data["rows"]']])
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"]')
})
it('should read HTML token', function () {
const html = '<html><body><p>Lorem Ipsum</p></body></html>'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTokens()
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(1)
expect(tokens[0].content).to.equal(html)
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.readTokens()
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(3)
expect(tokens[1]).instanceOf(TagToken)
expect(tokens[1].content).to.equal('for p in a[1]')
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 read value token', function () {
it('should read output token', function () {
const html = '<p>{{foo | date: "%Y-%m-%d"}}</p>'
const tokenizer = new Tokenizer(html)
const tokens = tokenizer.readTokens()
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(3)
expect(tokens[1]).instanceOf(OutputToken)
expect(tokens[1].content).to.equal('foo | date: "%Y-%m-%d"')
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.readTokens()
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(4)
expect(tokens[0]).instanceOf(OutputToken)
expect(tokens[2]).instanceOf(TagToken)
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(tokens[1].content).to.equal('bar')
expect(tokens[2].content).to.equal('foo')
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.readTokens()
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(5)
expect(tokens[1]).instanceOf(HTMLToken)
expect(tokens[1].raw).to.equal('\n')
expect(tokens[1].getText()).to.equal('\n')
expect(tokens[3]).instanceOf(HTMLToken)
expect(tokens[3].raw).to.equal(' \n ')
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.readTokens()
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].raw).to.equal('{%foo\na:a\nb:1.23\n%}')
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.readTokens()
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(1)
expect(tokens[0]).instanceOf(OutputToken)
expect(tokens[0].raw).to.equal('{{foo\n|date:\n"%Y-%m-%d"\n}}')
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.readTokens()
const tokens = tokenizer.readTopLevelTokens()
expect(tokens.length).to.equal(1)
expect(tokens[0]).instanceOf(OutputToken)
expect(tokens[0].content).to.equal('obj["my:property with anything"]')
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.readTokens()).to.throw(/tag "{% assign foo..." not closed/)
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.readTokens()).to.throw(/output "{{name}" not closed/)
expect(() => tokenizer.readTopLevelTokens()).to.throw(/output "{{name}" not closed/)
})
it('should read a simple filter', function () {
const tokenizer = new Tokenizer('| plus')
const token = tokenizer.readFilterToken()
expect(token).to.have.property('name', 'plus')
expect(token).to.have.property('args').to.deep.equal([])
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"])')
})
})
it('should read a filter with argument', function () {
const tokenizer = new Tokenizer(' | plus: 1')
const token = tokenizer.readFilterToken()
expect(token).to.have.property('name', 'plus')
expect(token).to.have.property('args').to.deep.equal(['1'])
})
it('should read a filter with colon but no argument', function () {
const tokenizer = new Tokenizer('| plus:')
const token = tokenizer.readFilterToken()
expect(token).to.have.property('name', 'plus')
expect(token).to.have.property('args').to.deep.equal([])
})
it('should read a filter with k/v argument', function () {
const tokenizer = new Tokenizer(' | plus: a:1')
const token = tokenizer.readFilterToken()
expect(token).to.have.property('name', 'plus')
expect(token).to.have.property('args').to.deep.equal([['a', '1']])
})
it('should read a filter with "arr[0]" argument', function () {
const tokenizer = new Tokenizer('| plus: arr[0]')
const token = tokenizer.readFilterToken()
expect(token).to.have.property('name', 'plus')
expect(token).to.have.property('args').to.deep.equal(['arr[0]'])
})
it('should read a filter with obj.foo argument', function () {
const tokenizer = new Tokenizer('| plus: obj.foo')
const token = tokenizer.readFilterToken()
expect(token).to.have.property('name', 'plus')
expect(token).to.have.property('args').to.deep.equal(['obj.foo'])
})
it('should read a filter with obj["foo"] argument', function () {
const tokenizer = new Tokenizer('| plus: obj["good luck"]')
const token = tokenizer.readFilterToken()
expect(token).to.have.property('name', 'plus')
expect(token).to.have.property('args').to.deep.equal(['obj["good luck"]'])
})
it('should read simple filters', function () {
const tokenizer = new Tokenizer('| plus: 3 | capitalize')
const tokens = tokenizer.readFilterTokens()
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)
expect(tokens).to.have.lengthOf(2)
expect(tokens[0]).to.have.property('name', 'plus')
expect(tokens[0]).to.have.property('args').to.deep.equal(['3'])
expect(tokens[1]).to.have.property('name', 'capitalize')
expect(tokens[1]).to.have.property('args').to.deep.equal([])
})
it('should read filters', function () {
const tokenizer = new Tokenizer('| plus: 3 | capitalize | append: foo[a.b["c d"]]')
const tokens = tokenizer.readFilterTokens()
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 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)
expect(tokens).to.have.lengthOf(3)
expect(tokens[0]).to.have.property('name', 'plus')
expect(tokens[0]).to.have.property('args').to.deep.equal(['3'])
expect(tokens[1]).to.have.property('name', 'capitalize')
expect(tokens[1]).to.have.property('args').to.deep.equal([])
expect(tokens[2]).to.have.property('name', 'append')
expect(tokens[2]).to.have.property('args').to.deep.equal(['foo[a.b["c d"]]'])
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.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.content).to.equal('obj')
expect(pa.props).to.have.lengthOf(1)
expect(pa.props[0]).to.be.instanceOf(WordToken)
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.content).to.equal('obj')
expect(pa.props[0].getText()).to.equal('"good luck"')
})
})
it('should read expression `a==b`', () => {
const exp = new Tokenizer('a==b').readExpression()
expect([...exp]).to.deep.equal(['a', '==', 'b'])
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"]')
})
})
it('should read expression `^`', () => {
const exp = new Tokenizer('^').readExpression()
expect([...exp]).to.deep.equal([])
})
it('should read expression `a == b`', () => {
const exp = new Tokenizer('a == b').readExpression()
expect([...exp]).to.deep.equal(['a', '==', 'b'])
})
it('should read expression `(1..3) contains 3`', () => {
const exp = new Tokenizer('(1..3) contains 3').readExpression()
expect([...exp]).to.deep.equal(['(1..3)', 'contains', '3'])
})
it('should read expression `a[b] = c`', () => {
const exp = new Tokenizer('a[b] = c').readExpression()
expect([...exp]).to.deep.equal(['a[b]', '=', 'c'])
})
it('should read expression `c[a["b"]] >= c`', () => {
const exp = new Tokenizer('c[a["b"]] >= c').readExpression()
expect([...exp]).to.deep.equal(['c[a["b"]]', '>=', 'c'])
})
it('should read expression `"][" == var`', () => {
const exp = new Tokenizer('"][" == var').readExpression()
expect([...exp]).to.deep.equal(['"]["', '==', 'var'])
})
it('should read expression `"\\\'" == "\\""`', () => {
const exp = new Tokenizer('"\\\'" == "\\""').readExpression()
expect([...exp]).to.deep.equal(['"\\\'"', '==', '"\\""'])
describe('#readExpression()', () => {
it('should read expression `a `', () => {
const exp = [...new Tokenizer('a ').readExpression()]
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]').readExpression()]
expect(exp).to.have.lengthOf(1)
const pa = exp[0] as PropertyAccessToken
expect(pa).to.be.instanceOf(PropertyAccessToken)
expect(pa.variable.content).to.deep.equal('a')
expect(pa.props).to.have.lengthOf(2)
const [p1, p2] = pa.props
expect(p1).to.be.instanceOf(WordToken)
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.').readExpression()]
expect(exp).to.have.lengthOf(1)
const pa = exp[0] as PropertyAccessToken
expect(pa).to.be.instanceOf(PropertyAccessToken)
expect(pa.variable.content).to.deep.equal('a')
expect(pa.props).to.have.lengthOf(0)
})
it('should read expression `a ==`', () => {
const exp = [...new Tokenizer('a ==').readExpression()]
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').readExpression()
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('^').readExpression()
expect([...exp]).to.deep.equal([])
})
it('should read expression `a == b`', () => {
const exp = new Tokenizer('a == b').readExpression()
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').readExpression()
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').readExpression()
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').readExpression()
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').readExpression()
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('"\\\'" == "\\""').readExpression()
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('"\\""')
})
})
})
+98 -42
View File
@@ -4,59 +4,115 @@ import { Context } from '../../../src/context/context'
import { toThenable } from '../../../src/util/async'
describe('Expression', function () {
let ctx: Context
beforeEach(function () {
ctx = new Context({
one: 1,
two: 2,
empty: '',
quote: '"',
space: ' ',
x: 'XXX',
y: undefined,
z: null,
obj: {
']': 'right bracket'
}
})
})
const ctx = new Context({})
it('should throw when context not defined', done => {
toThenable(new Expression().value(undefined!)).catch(err => {
expect(err.message).to.match(/context not defined/)
done()
return 0 as any
toThenable(new Expression('foo').value(undefined!))
.then(() => done(new Error('should not resolved')))
.catch(err => {
expect(err.message).to.match(/context not defined/)
done()
})
})
describe('single value', function () {
it('should eval literal', async function () {
expect(await toThenable(new Expression('2.4').value(ctx))).to.equal(2.4)
expect(await toThenable(new Expression('"foo"').value(ctx))).to.equal('foo')
expect(await toThenable(new Expression('false').value(ctx))).to.equal(false)
})
it('should eval range expression', async function () {
const ctx = new Context({ two: 2 })
expect(await toThenable(new Expression('(2..4)').value(ctx))).to.deep.equal([2, 3, 4])
expect(await toThenable(new Expression('(two..4)').value(ctx))).to.deep.equal([2, 3, 4])
})
it('should eval literal', async function () {
expect(await toThenable(new Expression('2.4').value(ctx))).to.equal(2.4)
expect(await toThenable(new Expression('"foo"').value(ctx))).to.equal('foo')
expect(await toThenable(new Expression('false').value(ctx))).to.equal(false)
})
it('should eval property access', async function () {
const ctx = new Context({
foo: { bar: 'BAR' },
coo: 'bar',
doo: { foo: 'bar', bar: { foo: 'bar' } }
})
expect(await toThenable(new Expression('foo.bar').value(ctx))).to.equal('BAR')
expect(await toThenable(new Expression('foo["bar"]').value(ctx))).to.equal('BAR')
expect(await toThenable(new Expression('foo[coo]').value(ctx))).to.equal('BAR')
expect(await toThenable(new Expression('foo[doo.foo]').value(ctx))).to.equal('BAR')
expect(await toThenable(new Expression('foo[doo["foo"]]').value(ctx))).to.equal('BAR')
expect(await toThenable(new Expression('doo[coo].foo').value(ctx))).to.equal('bar')
})
})
it('should eval simple expression', async function () {
expect(await toThenable(new Expression('1==2').value(ctx))).to.equal(false)
expect(await toThenable(new Expression('1<2').value(ctx))).to.equal(true)
expect(await toThenable(new Expression('1 < 2').value(ctx))).to.equal(true)
expect(await toThenable(new Expression('1 < 2').value(ctx))).to.equal(true)
expect(await toThenable(new Expression('2 <= 2').value(ctx))).to.equal(true)
expect(await toThenable(new Expression('one <= two').value(ctx))).to.equal(true)
expect(await toThenable(new Expression('x contains "x"').value(ctx))).to.equal(false)
expect(await toThenable(new Expression('x contains "X"').value(ctx))).to.equal(true)
expect(await toThenable(new Expression('1 contains "x"').value(ctx))).to.equal(false)
expect(await toThenable(new Expression('y contains "x"').value(ctx))).to.equal(false)
expect(await toThenable(new Expression('z contains "x"').value(ctx))).to.equal(false)
expect(await toThenable(new Expression('(1..5) contains 3').value(ctx))).to.equal(true)
expect(await toThenable(new Expression('(1..5) contains 6').value(ctx))).to.equal(false)
expect(await toThenable(new Expression('"<=" == "<="').value(ctx))).to.equal(true)
describe('simple expression', function () {
it('should return false for "1==2"', async () => {
expect(await toThenable(new Expression('1==2').value(ctx))).to.equal(false)
})
it('should return true for "1<2"', async () => {
expect(await toThenable(new Expression('1<2').value(ctx))).to.equal(true)
})
it('should return true for "1 < 2"', async () => {
expect(await toThenable(new Expression('1 < 2').value(ctx))).to.equal(true)
})
it('should return true for "1 < 2"', async () => {
expect(await toThenable(new Expression('1 < 2').value(ctx))).to.equal(true)
})
it('should return true for "2 <= 2"', async () => {
expect(await toThenable(new Expression('2 <= 2').value(ctx))).to.equal(true)
})
it('should return true for "one <= two"', async () => {
const ctx = new Context({ one: 1, two: 2 })
expect(await toThenable(new Expression('one <= two').value(ctx))).to.equal(true)
})
it('should return false for "x contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(new Expression('x contains "x"').value(ctx))).to.equal(false)
})
it('should return true for "x contains "X""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(new Expression('x contains "X"').value(ctx))).to.equal(true)
})
it('should return false for "1 contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(new Expression('1 contains "x"').value(ctx))).to.equal(false)
})
it('should return false for "y contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(new Expression('y contains "x"').value(ctx))).to.equal(false)
})
it('should return false for "z contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(new Expression('z contains "x"').value(ctx))).to.equal(false)
})
it('should return true for "(1..5) contains 3"', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(new Expression('(1..5) contains 3').value(ctx))).to.equal(true)
})
it('should return false for "(1..5) contains 6"', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(new Expression('(1..5) contains 6').value(ctx))).to.equal(false)
})
it('should return true for ""<=" == "<=""', async () => {
expect(await toThenable(new Expression('"<=" == "<="').value(ctx))).to.equal(true)
})
})
it('should allow space in quoted value', async function () {
const ctx = new Context({ space: ' ' })
expect(await toThenable(new Expression('" " == space').value(ctx))).to.equal(true)
})
describe('escape', () => {
it('should escape quote', async function () {
const ctx = new Context({ quote: '"' })
expect(await toThenable(new Expression('"\\"" == quote').value(ctx))).to.equal(true)
})
it('should escape square bracket', async function () {
expect(await toThenable(new Expression('obj["]"] == "right bracket"').value(ctx))).to.equal(true)
const ctx = new Context({ obj: { ']': 'bracket' } })
expect(await toThenable(new Expression('obj["]"] == "bracket"').value(ctx))).to.equal(true)
})
})
@@ -71,6 +127,7 @@ describe('Expression', function () {
expect(await toThenable(new Expression('1 < 2 or x contains "x"').value(ctx))).to.equal(true)
})
it('should support value and !=', async function () {
const ctx = new Context({ empty: '' })
expect(await toThenable(new Expression('empty and empty != ""').value(ctx))).to.equal(false)
})
it('should recognize quoted value', async function () {
@@ -84,10 +141,9 @@ describe('Expression', function () {
const ctx = new Context({ obj: { foo: true } })
expect(await toThenable(new Expression('obj["foo"] and true').value(ctx))).to.equal(true)
})
})
it('should eval range expression', async function () {
expect(await toThenable(new Expression('(2..4)').value(ctx))).to.deep.equal([2, 3, 4])
expect(await toThenable(new Expression('(two..4)').value(ctx))).to.deep.equal([2, 3, 4])
it('should allow nested property access', async function () {
const ctx = new Context({ obj: { foo: 'FOO' }, keys: { "what's this": 'foo' } })
expect(await toThenable(new Expression('obj[keys["what\'s this"]]').value(ctx))).to.equal('FOO')
})
})
})
+2 -2
View File
@@ -1,6 +1,6 @@
import { expect } from 'chai'
import { Context } from '../../../src/context/context'
import { Token } from '../../../src/parser/token'
import { HTMLToken } from '../../../src/tokens/html-token'
import { Render } from '../../../src/render/render'
import { HTML } from '../../../src/template/html'
import { toThenable } from '../../../src/util/async'
@@ -14,7 +14,7 @@ describe('render', function () {
describe('.renderTemplates()', function () {
it('should render html', async function () {
const scope = new Context()
const token = { content: '<p>' } as Token
const token = { getContent: () => '<p>' } as HTMLToken
const html = await toThenable(render.renderTemplates([new HTML(token)], scope))
return expect(html).to.equal('<p>')
})
+15 -6
View File
@@ -3,6 +3,9 @@ import * as sinon from 'sinon'
import * as sinonChai from 'sinon-chai'
import { Context } from '../../../../src/context/context'
import { toThenable } from '../../../../src/util/async'
import { NumberToken } from '../../../../src/tokens/number-token'
import { QuotedToken } from '../../../../src/tokens/quoted-token'
import { WordToken } from '../../../../src/tokens/word-token'
import { FilterMap } from '../../../../src/template/filter/filter-map'
chai.use(sinonChai)
@@ -27,13 +30,15 @@ describe('filter', function () {
it('should call filter impl with correct arguments', async function () {
const spy = sinon.spy()
filters.set('foo', spy)
await toThenable(filters.create('foo', ['33']).render('foo', ctx))
expect(spy).to.have.been.calledWith('foo', 33)
const thirty = new NumberToken(new WordToken('30', 0, 2), undefined)
await toThenable(filters.create('foo', [thirty]).render('foo', ctx))
expect(spy).to.have.been.calledWith('foo', 30)
})
it('should call filter impl with correct this arg', async function () {
const spy = sinon.spy()
filters.set('foo', spy)
await toThenable(filters.create('foo', ['33']).render('foo', ctx))
const thirty = new NumberToken(new WordToken('33', 0, 2), undefined)
await toThenable(filters.create('foo', [thirty]).render('foo', ctx))
expect(spy).to.have.been.calledOn(sinon.match.has('context', ctx))
})
it('should render a simple filter', async function () {
@@ -43,12 +48,15 @@ describe('filter', function () {
it('should render filters with argument', async function () {
filters.set('add', (a, b) => a + b)
expect(await toThenable(filters.create('add', ['2']).render(3, ctx))).to.equal(5)
const two = new NumberToken(new WordToken('2', 0, 1), undefined)
expect(await toThenable(filters.create('add', [two]).render(3, ctx))).to.equal(5)
})
it('should render filters with multiple arguments', async function () {
filters.set('add', (a, b, c) => a + b + c)
expect(await toThenable(filters.create('add', ['2', '"c"']).render(3, ctx))).to.equal('5c')
const two = new NumberToken(new WordToken('2', 0, 1), undefined)
const c = new QuotedToken('"c"', 0, 3)
expect(await toThenable(filters.create('add', [two, c]).render(3, ctx))).to.equal('5c')
})
it('should pass Objects/Drops as it is', async function () {
@@ -65,6 +73,7 @@ describe('filter', function () {
it('should support key value pairs', async function () {
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')
const two = new NumberToken(new WordToken('2', 0, 1), undefined)
expect(await toThenable((filters.create('add', [['num', two]]).render(3, ctx)))).to.equal('num:5')
})
})
+2 -1
View File
@@ -28,7 +28,8 @@ describe('Hash', function () {
expect(hash.num).to.equal(2.3)
})
it('should parse "num:bar.coo"', async function () {
const hash = await toThenable(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 toThenable(pending)
expect(hash.num).to.equal(3)
})
it('should parse "num1:2.3 reverse,num2:bar.coo\n num3: arr[0]"', async function () {
+1 -1
View File
@@ -2,7 +2,7 @@ import * as chai from 'chai'
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 { OutputToken } from '../../../src/tokens/output-token'
import { FilterMap } from '../../../src/template/filter/filter-map'
const expect = chai.expect
+1 -1
View File
@@ -3,7 +3,7 @@ import { Tag } from '../../../src/template/tag/tag'
import { Context } from '../../../src/context/context'
import * as sinon from 'sinon'
import * as sinonChai from 'sinon-chai'
import { TagToken } from '../../../src/parser/tag-token'
import { TagToken } from '../../../src/tokens/tag-token'
import { toThenable } from '../../../src/util/async'
chai.use(sinonChai)
+8 -61
View File
@@ -1,4 +1,5 @@
import * as chai from 'chai'
import { QuotedToken } from '../../../src/tokens/quoted-token'
import { toThenable } from '../../../src/util/async'
import { FilterMap } from '../../../src/template/filter/filter-map'
import * as sinonChai from 'sinon-chai'
@@ -15,71 +16,17 @@ describe('Value', function () {
const filterMap = new FilterMap(false)
it('should parse "foo', function () {
const tpl = new Value('foo', filterMap)
expect(tpl.initial).to.equal('foo')
expect(tpl.initial!.getText()).to.equal('foo')
expect(tpl.filters).to.deep.equal([])
})
it('should parse "foo | add"', function () {
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', 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"', 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', 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', 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', 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', 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', 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', 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', 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 () {
it('should parse filters in value content', function () {
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"']])
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"')
})
})
+2 -2
View File
@@ -5,11 +5,11 @@ const expect = chai.expect
describe('assert', 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()
})
it('should not throw if predicate is truthy', function () {
const fn = () => assert('', 'bar')
const fn = () => assert('', () => 'bar')
expect(fn).to.throw(/bar/)
})
it('should populate default message', function () {