diff --git a/src/context/context.ts b/src/context/context.ts index 64ab56f44..abf9cf58f 100644 --- a/src/context/context.ts +++ b/src/context/context.ts @@ -76,11 +76,11 @@ export class Context { public getFromScope (scope: unknown, paths: PropertyKey[] | string): IterableIterator { return toValueSync(this._getFromScope(scope, paths)) } - public * _getFromScope (scope: unknown, paths: PropertyKey[] | string): IterableIterator { + public * _getFromScope (scope: unknown, paths: PropertyKey[] | string, strictVariables = this.strictVariables): IterableIterator { if (isString(paths)) paths = paths.split('.') for (let i = 0; i < paths.length; i++) { scope = yield readProperty(scope as object, paths[i], this.ownPropertyOnly) - if (this.strictVariables && isUndefined(scope)) { + if (strictVariables && isUndefined(scope)) { throw new InternalUndefinedVariableError((paths as string[]).slice(0, i + 1).join!('.')) } } diff --git a/src/filters/array.ts b/src/filters/array.ts index d77f135ee..9c3b8ac33 100644 --- a/src/filters/array.ts +++ b/src/filters/array.ts @@ -38,7 +38,7 @@ export const size = (v: string | any[]) => (v && v.length) || 0 export function * map (this: FilterImpl, arr: Scope[], property: string): IterableIterator { const results = [] for (const item of toArray(toValue(arr))) { - results.push(yield this.context._getFromScope(item, stringify(property).split('.'))) + results.push(yield this.context._getFromScope(item, stringify(property), false)) } return results } diff --git a/src/template/filter.spec.ts b/src/template/filter.spec.ts index 057e76a76..e79a1ad68 100644 --- a/src/template/filter.spec.ts +++ b/src/template/filter.spec.ts @@ -31,7 +31,10 @@ describe('filter', function () { it('should render a simple filter', async function () { expect(await toPromise(new Filter('upcase', (x: string) => x.toUpperCase(), [], liquid).render('foo', ctx))).toBe('FOO') }) - + it('should reject promise when filter throws', async function () { + const filter = new Filter('foo', function * () { throw new Error('intended') }, [], liquid) + expect(toPromise(filter.render('foo', ctx))).rejects.toMatch('intended') + }) it('should render filters with argument', async function () { const two = new NumberToken('2', 0, 1, undefined) expect(await toPromise(new Filter('add', (a: number, b: number) => a + b, [two], liquid).render(3, ctx))).toBe(5) diff --git a/src/template/filter.ts b/src/template/filter.ts index 6ab4b0e81..5d60197b1 100644 --- a/src/template/filter.ts +++ b/src/template/filter.ts @@ -27,6 +27,6 @@ export class Filter { if (isKeyValuePair(arg)) argv.push([arg[0], yield evalToken(arg[1], context)]) else argv.push(yield evalToken(arg, context)) } - return this.handler.apply({ context, liquid: this.liquid }, [value, ...argv]) + return yield this.handler.apply({ context, liquid: this.liquid }, [value, ...argv]) } } diff --git a/test/integration/filters/array.spec.ts b/test/integration/filters/array.spec.ts index 297c01040..7ca5ada23 100644 --- a/test/integration/filters/array.spec.ts +++ b/test/integration/filters/array.spec.ts @@ -58,6 +58,14 @@ describe('filters/array', function () { const post = { category: 'foo' } return test('{{post | map: "category"}}', { post }, 'foo') }) + it('should allow nil results in strictVariables mode', function () { + const engine = new Liquid({ strictVariables: true }) + const ctx = { + posts: [{ category: 'foo' }, { title: 'bar' }] + } + const result = engine.parseAndRenderSync('{{posts | map: "category" | json}}', ctx) + expect(result).toEqual('["foo",null]') + }) it('should support nested property', function () { const tpl = '{{ arr | map: "name.first" | join }}' const a = { name: { first: 'Alice' } }