fix: stack overflow on large number of templates, #513

This commit is contained in:
Harttle
2022-07-08 01:46:51 +08:00
parent 2f87708989
commit 3dc4290b56
16 changed files with 172 additions and 180 deletions
+10
View File
@@ -246,4 +246,14 @@ describe('Issues', function () {
const html = await engine.parseAndRender(`{% if template contains "product" %}contains{%endif%}`, ctx)
expect(html).to.equal('contains')
})
it('#513 should support large number of templates [async]', async () => {
const engine = new Liquid()
const html = await engine.parseAndRender(`{% for i in (1..10000) %}{{ i }}{% endfor %}`)
expect(html).to.have.lengthOf(38894)
})
it('#513 should support large number of templates [sync]', () => {
const engine = new Liquid()
const html = engine.parseAndRenderSync(`{% for i in (1..10000) %}{{ i }}{% endfor %}`)
expect(html).to.have.lengthOf(38894)
})
})
+5 -3
View File
@@ -126,11 +126,13 @@ describe('LiquidOptions#cache', function () {
extname: '.html',
cache: true
})
try { await engine.renderFile('foo') } catch (err) {}
try {
await engine.renderFile('foo')
} catch (err) {}
mock({ '/root/foo.html': 'foo' })
const y = await engine.renderFile('foo')
expect(y).to.equal('foo')
const html = await engine.renderFile('foo')
expect(html).to.equal('foo')
})
})
+43 -43
View File
@@ -2,7 +2,7 @@ import { Tokenizer } from '../../../src/parser/tokenizer'
import { expect } from 'chai'
import { Drop } from '../../../src/drop/drop'
import { Context } from '../../../src/context/context'
import { toThenable } from '../../../src/util/async'
import { toPromise } from '../../../src/util/async'
import { defaultOperators } from '../../../src/render/operator'
import { createTrie } from '../../../src/util/operator-trie'
@@ -12,7 +12,7 @@ describe('Expression', function () {
const create = (str: string) => new Tokenizer(str, trie).readExpression()
it('should throw when context not defined', done => {
toThenable(create('foo').evaluate(undefined!, false))
toPromise(create('foo').evaluate(undefined!, false))
.then(() => done(new Error('should not resolved')))
.catch(err => {
expect(err.message).to.match(/context not defined/)
@@ -22,19 +22,19 @@ describe('Expression', function () {
describe('single value', function () {
it('should eval literal', async function () {
expect(await toThenable(create('2.4').evaluate(ctx, false))).to.equal(2.4)
expect(await toThenable(create('"foo"').evaluate(ctx, false))).to.equal('foo')
expect(await toThenable(create('false').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('2.4').evaluate(ctx, false))).to.equal(2.4)
expect(await toPromise(create('"foo"').evaluate(ctx, false))).to.equal('foo')
expect(await toPromise(create('false').evaluate(ctx, false))).to.equal(false)
})
it('should eval range expression', async function () {
const ctx = new Context({ two: 2 })
expect(await toThenable(create('(2..4)').evaluate(ctx, false))).to.deep.equal([2, 3, 4])
expect(await toThenable(create('(two..4)').evaluate(ctx, false))).to.deep.equal([2, 3, 4])
expect(await toPromise(create('(2..4)').evaluate(ctx, false))).to.deep.equal([2, 3, 4])
expect(await toPromise(create('(two..4)').evaluate(ctx, false))).to.deep.equal([2, 3, 4])
})
it('should eval literal', async function () {
expect(await toThenable(create('2.4').evaluate(ctx, false))).to.equal(2.4)
expect(await toThenable(create('"foo"').evaluate(ctx, false))).to.equal('foo')
expect(await toThenable(create('false').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('2.4').evaluate(ctx, false))).to.equal(2.4)
expect(await toPromise(create('"foo"').evaluate(ctx, false))).to.equal('foo')
expect(await toPromise(create('false').evaluate(ctx, false))).to.equal(false)
})
it('should eval property access', async function () {
@@ -43,119 +43,119 @@ describe('Expression', function () {
coo: 'bar',
doo: { foo: 'bar', bar: { foo: 'bar' } }
})
expect(await toThenable(create('foo.bar').evaluate(ctx, false))).to.equal('BAR')
expect(await toThenable(create('foo["bar"]').evaluate(ctx, false))).to.equal('BAR')
expect(await toThenable(create('foo[coo]').evaluate(ctx, false))).to.equal('BAR')
expect(await toThenable(create('foo[doo.foo]').evaluate(ctx, false))).to.equal('BAR')
expect(await toThenable(create('foo[doo["foo"]]').evaluate(ctx, false))).to.equal('BAR')
expect(await toThenable(create('doo[coo].foo').evaluate(ctx, false))).to.equal('bar')
expect(await toPromise(create('foo.bar').evaluate(ctx, false))).to.equal('BAR')
expect(await toPromise(create('foo["bar"]').evaluate(ctx, false))).to.equal('BAR')
expect(await toPromise(create('foo[coo]').evaluate(ctx, false))).to.equal('BAR')
expect(await toPromise(create('foo[doo.foo]').evaluate(ctx, false))).to.equal('BAR')
expect(await toPromise(create('foo[doo["foo"]]').evaluate(ctx, false))).to.equal('BAR')
expect(await toPromise(create('doo[coo].foo').evaluate(ctx, false))).to.equal('bar')
})
})
describe('simple expression', function () {
it('should return false for "1==2"', async () => {
expect(await toThenable(create('1==2').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('1==2').evaluate(ctx, false))).to.equal(false)
})
it('should return true for "1<2"', async () => {
expect(await toThenable(create('1<2').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('1<2').evaluate(ctx, false))).to.equal(true)
})
it('should return true for "1 < 2"', async () => {
expect(await toThenable(create('1 < 2').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('1 < 2').evaluate(ctx, false))).to.equal(true)
})
it('should return true for "1 < 2"', async () => {
expect(await toThenable(create('1 < 2').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('1 < 2').evaluate(ctx, false))).to.equal(true)
})
it('should return true for "2 <= 2"', async () => {
expect(await toThenable(create('2 <= 2').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('2 <= 2').evaluate(ctx, false))).to.equal(true)
})
it('should return true for "one <= two"', async () => {
const ctx = new Context({ one: 1, two: 2 })
expect(await toThenable(create('one <= two').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('one <= two').evaluate(ctx, false))).to.equal(true)
})
it('should return false for "x contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(create('x contains "x"').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('x contains "x"').evaluate(ctx, false))).to.equal(false)
})
it('should return true for "x contains "X""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(create('x contains "X"').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('x contains "X"').evaluate(ctx, false))).to.equal(true)
})
it('should return false for "1 contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(create('1 contains "x"').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('1 contains "x"').evaluate(ctx, false))).to.equal(false)
})
it('should return false for "y contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(create('y contains "x"').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('y contains "x"').evaluate(ctx, false))).to.equal(false)
})
it('should return false for "z contains "x""', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(create('z contains "x"').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('z contains "x"').evaluate(ctx, false))).to.equal(false)
})
it('should return true for "(1..5) contains 3"', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(create('(1..5) contains 3').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('(1..5) contains 3').evaluate(ctx, false))).to.equal(true)
})
it('should return false for "(1..5) contains 6"', async () => {
const ctx = new Context({ x: 'XXX' })
expect(await toThenable(create('(1..5) contains 6').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('(1..5) contains 6').evaluate(ctx, false))).to.equal(false)
})
it('should return true for ""<=" == "<=""', async () => {
expect(await toThenable(create('"<=" == "<="').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('"<=" == "<="').evaluate(ctx, false))).to.equal(true)
})
})
it('should allow space in quoted value', async function () {
const ctx = new Context({ space: ' ' })
expect(await toThenable(create('" " == space').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('" " == space').evaluate(ctx, false))).to.equal(true)
})
describe('escape', () => {
it('should escape quote', async function () {
const ctx = new Context({ quote: '"' })
expect(await toThenable(create('"\\"" == quote').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('"\\"" == quote').evaluate(ctx, false))).to.equal(true)
})
it('should escape square bracket', async function () {
const ctx = new Context({ obj: { ']': 'bracket' } })
expect(await toThenable(create('obj["]"] == "bracket"').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('obj["]"] == "bracket"').evaluate(ctx, false))).to.equal(true)
})
})
describe('complex expression', function () {
it('should support value or value', async function () {
expect(await toThenable(create('false or true').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('false or true').evaluate(ctx, false))).to.equal(true)
})
it('should support < and contains', async function () {
expect(await toThenable(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))).to.equal(false)
})
it('should support < or contains', async function () {
expect(await toThenable(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))).to.equal(true)
})
it('should support Drops for "x contains "x""', async () => {
class TemplateDrop extends Drop {
valueOf () { return 'X' }
}
const ctx = new Context({ x: 'XXX', X: new TemplateDrop() })
expect(await toThenable(create('x contains X').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('x contains X').evaluate(ctx, false))).to.equal(true)
})
it('should support value and !=', async function () {
const ctx = new Context({ empty: '' })
expect(await toThenable(create('empty and empty != ""').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('empty and empty != ""').evaluate(ctx, false))).to.equal(false)
})
it('should recognize quoted value', async function () {
expect(await toThenable(create('">"').evaluate(ctx, false))).to.equal('>')
expect(await toPromise(create('">"').evaluate(ctx, false))).to.equal('>')
})
it('should evaluate from right to left', async function () {
expect(await toThenable(create('true or false and false').evaluate(ctx, false))).to.equal(true)
expect(await toThenable(create('true and false and false or true').evaluate(ctx, false))).to.equal(false)
expect(await toPromise(create('true or false and false').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('true and false and false or true').evaluate(ctx, false))).to.equal(false)
})
it('should recognize property access', async function () {
const ctx = new Context({ obj: { foo: true } })
expect(await toThenable(create('obj["foo"] and true').evaluate(ctx, false))).to.equal(true)
expect(await toPromise(create('obj["foo"] and true').evaluate(ctx, false))).to.equal(true)
})
it('should allow nested property access', async function () {
const ctx = new Context({ obj: { foo: 'FOO' }, keys: { "what's this": 'foo' } })
expect(await toThenable(create('obj[keys["what\'s this"]]').evaluate(ctx, false))).to.equal('FOO')
expect(await toPromise(create('obj[keys["what\'s this"]]').evaluate(ctx, false))).to.equal('FOO')
})
})
})
+2 -2
View File
@@ -4,7 +4,7 @@ import { HTMLToken } from '../../../src/tokens/html-token'
import { Render } from '../../../src/render/render'
import { HTML } from '../../../src/template/html'
import { SimpleEmitter } from '../../../src/emitters/simple-emitter'
import { toThenable } from '../../../src/util/async'
import { toPromise } from '../../../src/util/async'
import { Tag } from '../../../src/template/tag/tag'
import { TagToken } from '../../../src/types'
@@ -18,7 +18,7 @@ describe('render', function () {
it('should render html', async function () {
const scope = new Context()
const token = { getContent: () => '<p>' } as HTMLToken
const html = await toThenable(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>')
})
})
+9 -9
View File
@@ -2,7 +2,7 @@ import * as chai from 'chai'
import * as sinon from 'sinon'
import * as sinonChai from 'sinon-chai'
import { Context } from '../../../../src/context/context'
import { toThenable } from '../../../../src/util/async'
import { toPromise } from '../../../../src/util/async'
import { NumberToken } from '../../../../src/tokens/number-token'
import { QuotedToken } from '../../../../src/tokens/quoted-token'
import { IdentifierToken } from '../../../../src/tokens/identifier-token'
@@ -25,46 +25,46 @@ describe('filter', function () {
})
it('should render input if filter not registered', async function () {
expect(await toThenable(filters.create('undefined', []).render('foo', ctx))).to.equal('foo')
expect(await toPromise(filters.create('undefined', []).render('foo', ctx))).to.equal('foo')
})
it('should call filter impl with correct arguments', async function () {
const spy = sinon.spy()
filters.set('foo', spy)
const thirty = new NumberToken(new IdentifierToken('30', 0, 2), undefined)
await toThenable(filters.create('foo', [thirty]).render('foo', ctx))
await toPromise(filters.create('foo', [thirty]).render('foo', ctx))
expect(spy).to.have.been.calledWith('foo', 30)
})
it('should call filter impl with correct this', async function () {
const spy = sinon.spy()
filters.set('foo', spy)
const thirty = new NumberToken(new IdentifierToken('33', 0, 2), undefined)
await toThenable(filters.create('foo', [thirty]).render('foo', ctx))
await toPromise(filters.create('foo', [thirty]).render('foo', ctx))
expect(spy).to.have.been.calledOn(sinon.match.has('context', ctx))
expect(spy).to.have.been.calledOn(sinon.match.has('liquid', liquid))
})
it('should render a simple filter', async function () {
filters.set('upcase', x => x.toUpperCase())
expect(await toThenable(filters.create('upcase', []).render('foo', ctx))).to.equal('FOO')
expect(await toPromise(filters.create('upcase', []).render('foo', ctx))).to.equal('FOO')
})
it('should render filters with argument', async function () {
filters.set('add', (a, b) => a + b)
const two = new NumberToken(new IdentifierToken('2', 0, 1), undefined)
expect(await toThenable(filters.create('add', [two]).render(3, ctx))).to.equal(5)
expect(await toPromise(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)
const two = new NumberToken(new IdentifierToken('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')
expect(await toPromise(filters.create('add', [two, c]).render(3, ctx))).to.equal('5c')
})
it('should pass Objects/Drops as it is', async function () {
filters.set('name', a => a.constructor.name)
class Foo {}
expect(await toThenable(filters.create('name', []).render(new Foo(), ctx))).to.equal('Foo')
expect(await toPromise(filters.create('name', []).render(new Foo(), ctx))).to.equal('Foo')
})
it('should not throw when filter name illegal', function () {
@@ -76,6 +76,6 @@ describe('filter', function () {
it('should support key value pairs', async function () {
filters.set('add', (a, b) => b[0] + ':' + (a + b[1]))
const two = new NumberToken(new IdentifierToken('2', 0, 1), undefined)
expect(await toThenable((filters.create('add', [['num', two]]).render(3, ctx)))).to.equal('num:5')
expect(await toPromise((filters.create('add', [['num', two]]).render(3, ctx)))).to.equal('num:5')
})
})
+8 -8
View File
@@ -1,5 +1,5 @@
import * as chai from 'chai'
import { toThenable } from '../../../src/util/async'
import { toPromise } from '../../../src/util/async'
import { Hash } from '../../../src/template/tag/hash'
import { Context } from '../../../src/context/context'
@@ -7,34 +7,34 @@ const expect = chai.expect
describe('Hash', function () {
it('should parse "reverse"', async function () {
const hash = await toThenable(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.reverse).to.be.true
})
it('should parse "num:foo"', async function () {
const hash = await toThenable(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)
})
it('should parse "num:3"', async function () {
const hash = await toThenable(new Hash('num:3').render(new Context()))
const hash = await toPromise(new Hash('num:3').render(new Context()))
expect(hash.num).to.equal(3)
})
it('should parse "num: arr[0]"', async function () {
const hash = await toThenable(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)
})
it('should parse "num: 2.3"', async function () {
const hash = await toThenable(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)
})
it('should parse "num:bar.coo"', async function () {
const pending = new Hash('num:bar.coo').render(new Context({ bar: { coo: 3 } }))
const hash = await toThenable(pending)
const hash = await toPromise(pending)
expect(hash.num).to.equal(3)
})
it('should parse "num1:2.3 reverse,num2:bar.coo\n num3: arr[0]"', async function () {
const ctx = new Context({ bar: { coo: 3 }, arr: [4] })
const hash = await toThenable(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({
num1: 2.3,
reverse: true,
+9 -9
View File
@@ -1,5 +1,5 @@
import * as chai from 'chai'
import { toThenable } from '../../../src/util/async'
import { toPromise } from '../../../src/util/async'
import { Context } from '../../../src/context/context'
import { Output } from '../../../src/template/output'
import { OutputToken } from '../../../src/tokens/output-token'
@@ -21,25 +21,25 @@ describe('Output', function () {
foo: { obj: { arr: ['a', 2] } }
})
const output = new Output({ content: 'foo' } as OutputToken, liquid)
await toThenable(output.render(scope, emitter))
await toPromise(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({ content: 'obj' } as OutputToken, liquid)
await toThenable(output.render(scope, emitter))
await toPromise(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({ content: 'obj' } as OutputToken, liquid)
await toThenable(output.render(scope, emitter))
await toPromise(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({ content: 'obj' } as OutputToken, liquid)
await toThenable(output.render(scope, emitter))
await toPromise(output.render(scope, emitter))
return expect(emitter.html).to.equal('FOO')
})
context('when keepOutputType is enabled', () => {
@@ -62,7 +62,7 @@ describe('Output', function () {
foo: 42
}, { ...defaultOptions, keepOutputType: true })
const output = new Output({ content: 'foo' } as OutputToken, liquid)
await toThenable(output.render(scope, emitter))
await toPromise(output.render(scope, emitter))
return expect(emitter.html).to.equal(42)
})
it('should respect output variable boolean type', async () => {
@@ -70,7 +70,7 @@ describe('Output', function () {
foo: true
}, { ...defaultOptions, keepOutputType: true })
const output = new Output({ content: 'foo' } as OutputToken, liquid)
await toThenable(output.render(scope, emitter))
await toPromise(output.render(scope, emitter))
return expect(emitter.html).to.equal(true)
})
it('should respect output variable object type', async () => {
@@ -78,7 +78,7 @@ describe('Output', function () {
foo: 'test'
}, { ...defaultOptions, keepOutputType: true })
const output = new Output({ content: 'foo' } as OutputToken, liquid)
await toThenable(output.render(scope, emitter))
await toPromise(output.render(scope, emitter))
return expect(emitter.html).to.equal('test')
})
it('should respect output variable string type', async () => {
@@ -86,7 +86,7 @@ describe('Output', function () {
foo: { a: { b: 42 } }
}, { ...defaultOptions, keepOutputType: true })
const output = new Output({ content: 'foo' } as OutputToken, liquid)
await toThenable(output.render(scope, emitter))
await toPromise(output.render(scope, emitter))
return expect(emitter.html).to.deep.equal({ a: { b: 42 } })
})
})
+2 -2
View File
@@ -4,7 +4,7 @@ import { Context } from '../../../src/context/context'
import * as sinon from 'sinon'
import * as sinonChai from 'sinon-chai'
import { TagToken } from '../../../src/tokens/tag-token'
import { toThenable } from '../../../src/util/async'
import { toPromise } from '../../../src/util/async'
chai.use(sinonChai)
const expect = chai.expect
@@ -20,7 +20,7 @@ describe('Tag', function () {
args: '',
name: 'foo'
} as TagToken
await toThenable(new Tag(token, [], {
await toPromise(new Tag(token, [], {
tags: {
get: () => ({ render: spy })
}
+2 -2
View File
@@ -1,7 +1,7 @@
import * as chai from 'chai'
import { Liquid } from '../../../src/liquid'
import { QuotedToken } from '../../../src/tokens/quoted-token'
import { toThenable } from '../../../src/util/async'
import { toPromise } from '../../../src/util/async'
import * as sinonChai from 'sinon-chai'
import * as sinon from 'sinon'
import { Context } from '../../../src/context/context'
@@ -36,7 +36,7 @@ describe('Value', function () {
const scope = new Context({
foo: { bar: 'bar' }
})
await toThenable(tpl.value(scope, false))
await toPromise(tpl.value(scope, false))
expect(date).to.have.been.calledWith('bar', 'b')
expect(time).to.have.been.calledWith('y', 2)
})
+19 -21
View File
@@ -1,4 +1,4 @@
import { toThenable, toPromise, toValue } from '../../../src/util/async'
import { toPromise, toValue } from '../../../src/util/async'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
@@ -13,47 +13,45 @@ describe('utils/async', () => {
const result = await toPromise(foo())
expect(result).to.equal('foo')
})
})
describe('#toThenable()', function () {
it('should support iterable with single return statement', async () => {
function * foo () {
return 'foo'
}
const result = await toThenable(foo())
const result = await toPromise(foo())
expect(result).to.equal('foo')
})
it('should support promise', async () => {
function foo () {
return Promise.resolve('foo')
}
const result = await toThenable(foo())
const result = await toPromise(foo())
expect(result).to.equal('foo')
})
it('should resolve dependency', async () => {
function * foo () {
function * foo (): Generator<Generator<string>> {
return yield bar()
}
function * bar () {
function * bar (): Generator<string> {
return 'bar'
}
const result = await toThenable(foo())
const result = await toPromise(foo())
expect(result).to.equal('bar')
})
it('should support promise dependency', async () => {
function * foo () {
function * foo (): Generator<Promise<string>> {
return yield Promise.resolve('foo')
}
const result = await toThenable(foo())
const result = await toPromise(foo())
expect(result).to.equal('foo')
})
it('should reject Promise if dependency throws syncly', done => {
function * foo () {
function * foo (): Generator<Generator<never>> {
return yield bar()
}
function * bar (): IterableIterator<any> {
function * bar (): Generator<never> {
throw new Error('bar')
}
toThenable(foo()).catch(err => {
toPromise(foo()).catch(err => {
expect(err.message).to.equal('bar')
done()
return 0 as any
@@ -70,43 +68,43 @@ describe('utils/async', () => {
ret += 'foo'
return ret
}
function * bar (): IterableIterator<any> {
function * bar (): Generator<never> {
throw new Error('bar')
}
const ret = await toThenable(foo())
const ret = await toPromise(foo())
expect(ret).to.equal('barfoo')
})
})
describe('#toValue()', function () {
it('should throw Error if dependency throws syncly', () => {
function * foo () {
function * foo (): Generator<Generator<never>> {
return yield bar()
}
function * bar (): IterableIterator<any> {
function * bar (): Generator<never> {
throw new Error('bar')
}
expect(() => toValue(foo())).to.throw('bar')
})
it('should resume yield after catch', () => {
function * foo () {
function * foo (): Generator<unknown, never, never> {
try {
yield bar()
} catch (e) {}
return yield 'foo'
}
function * bar (): IterableIterator<any> {
function * bar (): Generator<never> {
throw new Error('bar')
}
expect(toValue(foo())).to.equal('foo')
})
it('should resume return after catch', () => {
function * foo () {
function * foo (): Generator<Generator<never>, string> {
try {
yield bar()
} catch (e) {}
return 'foo'
}
function * bar (): IterableIterator<any> {
function * bar (): Generator<never> {
throw new Error('bar')
}
expect(toValue(foo())).to.equal('foo')