diff options
Diffstat (limited to 'dev/lib/handlebars/index.test.ts')
-rw-r--r-- | dev/lib/handlebars/index.test.ts | 567 |
1 files changed, 0 insertions, 567 deletions
diff --git a/dev/lib/handlebars/index.test.ts b/dev/lib/handlebars/index.test.ts deleted file mode 100644 index ed607db1..00000000 --- a/dev/lib/handlebars/index.test.ts +++ /dev/null @@ -1,567 +0,0 @@ -/* - * Elasticsearch B.V licenses this file to you under the MIT License. - * See `packages/kbn-handlebars/LICENSE` for more information. - */ - -/** - * ABOUT THIS FILE: - * - * This file is for tests not copied from the upstream handlebars project, but - * tests that we feel are needed in order to fully cover our use-cases. - */ - -import Handlebars from '.'; -import type { HelperOptions, TemplateDelegate } from './src/types'; -import { expectTemplate, forEachCompileFunctionName } from './src/__jest__/test_bench'; - -it('Handlebars.create', () => { - expect(Handlebars.create()).toMatchSnapshot(); -}); - -describe('Handlebars.compileAST', () => { - describe('compiler options', () => { - it('noEscape', () => { - expectTemplate('{{value}}').withInput({ value: '<foo>' }).toCompileTo('<foo>'); - - expectTemplate('{{value}}') - .withCompileOptions({ noEscape: false }) - .withInput({ value: '<foo>' }) - .toCompileTo('<foo>'); - - expectTemplate('{{value}}') - .withCompileOptions({ noEscape: true }) - .withInput({ value: '<foo>' }) - .toCompileTo('<foo>'); - }); - }); - - it('invalid template', () => { - expectTemplate('{{value').withInput({ value: 42 }).toThrow(`Parse error on line 1: -{{value ---^ -Expecting 'ID', 'STRING', 'NUMBER', 'BOOLEAN', 'UNDEFINED', 'NULL', 'DATA', got 'INVALID'`); - }); - - if (!process.env.EVAL) { - it('reassign', () => { - const fn = Handlebars.compileAST; - expect(fn('{{value}}')({ value: 42 })).toEqual('42'); - }); - } -}); - -// Extra "helpers" tests -describe('helpers', () => { - it('Only provide options.fn/inverse to block helpers', () => { - function toHaveProperties(...args: any[]) { - toHaveProperties.calls++; - const options = args[args.length - 1]; - expect(options).toHaveProperty('fn'); - expect(options).toHaveProperty('inverse'); - return 42; - } - toHaveProperties.calls = 0; - - function toNotHaveProperties(...args: any[]) { - toNotHaveProperties.calls++; - const options = args[args.length - 1]; - expect(options).not.toHaveProperty('fn'); - expect(options).not.toHaveProperty('inverse'); - return 42; - } - toNotHaveProperties.calls = 0; - - const nonBlockTemplates = ['{{foo}}', '{{foo 1 2}}']; - const blockTemplates = ['{{#foo}}42{{/foo}}', '{{#foo 1 2}}42{{/foo}}']; - - for (const template of nonBlockTemplates) { - expectTemplate(template) - .withInput({ - foo: toNotHaveProperties, - }) - .toCompileTo('42'); - - expectTemplate(template).withHelper('foo', toNotHaveProperties).toCompileTo('42'); - } - - for (const template of blockTemplates) { - expectTemplate(template) - .withInput({ - foo: toHaveProperties, - }) - .toCompileTo('42'); - - expectTemplate(template).withHelper('foo', toHaveProperties).toCompileTo('42'); - } - - const factor = process.env.AST || process.env.EVAL ? 1 : 2; - expect(toNotHaveProperties.calls).toEqual(nonBlockTemplates.length * 2 * factor); - expect(toHaveProperties.calls).toEqual(blockTemplates.length * 2 * factor); - }); - - it('should pass expected "this" to helper functions (without input)', () => { - expectTemplate('{{hello "world" 12 true false}}') - .withHelper('hello', function (this: any, ...args: any[]) { - expect(this).toMatchInlineSnapshot(`Object {}`); - }) - .toCompileTo(''); - }); - - it('should pass expected "this" to helper functions (with input)', () => { - expectTemplate('{{hello "world" 12 true false}}') - .withHelper('hello', function (this: any, ...args: any[]) { - expect(this).toMatchInlineSnapshot(` - Object { - "people": Array [ - Object { - "id": 1, - "name": "Alan", - }, - Object { - "id": 2, - "name": "Yehuda", - }, - ], - } - `); - }) - .withInput({ - people: [ - { name: 'Alan', id: 1 }, - { name: 'Yehuda', id: 2 }, - ], - }) - .toCompileTo(''); - }); - - it('should pass expected "this" and arguments to helper functions (non-block helper)', () => { - expectTemplate('{{hello "world" 12 true false}}') - .withHelper('hello', function (this: any, ...args: any[]) { - expect(args).toMatchInlineSnapshot(` - Array [ - "world", - 12, - true, - false, - Object { - "data": Object { - "root": Object { - "people": Array [ - Object { - "id": 1, - "name": "Alan", - }, - Object { - "id": 2, - "name": "Yehuda", - }, - ], - }, - }, - "hash": Object {}, - "loc": Object { - "end": Object { - "column": 31, - "line": 1, - }, - "start": Object { - "column": 0, - "line": 1, - }, - }, - "lookupProperty": [Function], - "name": "hello", - }, - ] - `); - }) - .withInput({ - people: [ - { name: 'Alan', id: 1 }, - { name: 'Yehuda', id: 2 }, - ], - }) - .toCompileTo(''); - }); - - it('should pass expected "this" and arguments to helper functions (block helper)', () => { - expectTemplate('{{#hello "world" 12 true false}}{{/hello}}') - .withHelper('hello', function (this: any, ...args: any[]) { - expect(args).toMatchInlineSnapshot(` - Array [ - "world", - 12, - true, - false, - Object { - "data": Object { - "root": Object { - "people": Array [ - Object { - "id": 1, - "name": "Alan", - }, - Object { - "id": 2, - "name": "Yehuda", - }, - ], - }, - }, - "fn": [Function], - "hash": Object {}, - "inverse": [Function], - "loc": Object { - "end": Object { - "column": 42, - "line": 1, - }, - "start": Object { - "column": 0, - "line": 1, - }, - }, - "lookupProperty": [Function], - "name": "hello", - }, - ] - `); - }) - .withInput({ - people: [ - { name: 'Alan', id: 1 }, - { name: 'Yehuda', id: 2 }, - ], - }) - .toCompileTo(''); - }); -}); - -// Extra "blocks" tests -describe('blocks', () => { - describe('decorators', () => { - it('should only call decorator once', () => { - let calls = 0; - const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; - expectTemplate('{{#helper}}{{*decorator}}{{/helper}}') - .withHelper('helper', () => {}) - .withDecorator('decorator', () => { - calls++; - }) - .toCompileTo(''); - expect(calls).toEqual(callsExpected); - }); - - forEachCompileFunctionName((compileName) => { - it(`should call decorator again if render function is called again for #${compileName}`, () => { - global.kbnHandlebarsEnv = Handlebars.create(); - - kbnHandlebarsEnv!.registerDecorator('decorator', () => { - calls++; - }); - - const compile = kbnHandlebarsEnv![compileName].bind(kbnHandlebarsEnv); - const render = compile('{{*decorator}}'); - - let calls = 0; - expect(render()).toEqual(''); - expect(calls).toEqual(1); - - calls = 0; - expect(render()).toEqual(''); - expect(calls).toEqual(1); - - global.kbnHandlebarsEnv = null; - }); - }); - - it('should pass expected options to nested decorator', () => { - expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}') - .withHelper('helper', () => {}) - .withDecorator('decorator', function (fn, props, container, options) { - expect(options).toMatchInlineSnapshot(` - Object { - "args": Array [ - "bar", - ], - "data": Object { - "root": Object { - "foo": "bar", - }, - }, - "hash": Object {}, - "loc": Object { - "end": Object { - "column": 29, - "line": 1, - }, - "start": Object { - "column": 11, - "line": 1, - }, - }, - "name": "decorator", - } - `); - }) - .withInput({ foo: 'bar' }) - .toCompileTo(''); - }); - - it('should pass expected options to root decorator with no args', () => { - expectTemplate('{{*decorator}}') - .withDecorator('decorator', function (fn, props, container, options) { - expect(options).toMatchInlineSnapshot(` - Object { - "args": Array [], - "data": Object { - "root": Object { - "foo": "bar", - }, - }, - "hash": Object {}, - "loc": Object { - "end": Object { - "column": 14, - "line": 1, - }, - "start": Object { - "column": 0, - "line": 1, - }, - }, - "name": "decorator", - } - `); - }) - .withInput({ foo: 'bar' }) - .toCompileTo(''); - }); - - it('should pass expected options to root decorator with one arg', () => { - expectTemplate('{{*decorator foo}}') - .withDecorator('decorator', function (fn, props, container, options) { - expect(options).toMatchInlineSnapshot(` - Object { - "args": Array [ - undefined, - ], - "data": Object { - "root": Object { - "foo": "bar", - }, - }, - "hash": Object {}, - "loc": Object { - "end": Object { - "column": 18, - "line": 1, - }, - "start": Object { - "column": 0, - "line": 1, - }, - }, - "name": "decorator", - } - `); - }) - .withInput({ foo: 'bar' }) - .toCompileTo(''); - }); - - describe('return values', () => { - for (const [desc, template, result] of [ - ['non-block', '{{*decorator}}cont{{*decorator}}ent', 'content'], - ['block', '{{#*decorator}}con{{/decorator}}tent', 'tent'], - ]) { - describe(desc, () => { - const falsy = [undefined, null, false, 0, '']; - const truthy = [true, 42, 'foo', {}]; - - // Falsy return values from decorators are simply ignored and the - // execution falls back to default behavior which is to render the - // other parts of the template. - for (const value of falsy) { - it(`falsy value (type ${typeof value}): ${JSON.stringify(value)}`, () => { - expectTemplate(template) - .withDecorator('decorator', () => value) - .toCompileTo(result); - }); - } - - // Truthy return values from decorators are expected to be functions - // and the program will attempt to call them. We expect an error to - // be thrown in this case. - for (const value of truthy) { - it(`non-falsy value (type ${typeof value}): ${JSON.stringify(value)}`, () => { - expectTemplate(template) - .withDecorator('decorator', () => value) - .toThrow('is not a function'); - }); - } - - // If the decorator return value is a custom function, its return - // value will be the final content of the template. - for (const value of [...falsy, ...truthy]) { - it(`function returning ${typeof value}: ${JSON.stringify(value)}`, () => { - expectTemplate(template) - .withDecorator('decorator', () => () => value) - .toCompileTo(value as string); - }); - } - }); - } - }); - - describe('custom return function should be called with expected arguments and its return value should be rendered in the template', () => { - it('root decorator', () => { - expectTemplate('{{*decorator}}world') - .withInput({ me: 'my' }) - .withDecorator( - 'decorator', - (fn): TemplateDelegate => - (context, options) => { - expect(context).toMatchInlineSnapshot(` - Object { - "me": "my", - } - `); - expect(options).toMatchInlineSnapshot(` - Object { - "decorators": Object { - "decorator": [Function], - }, - "helpers": Object {}, - "partials": Object {}, - } - `); - return `hello ${context.me} ${fn()}!`; - } - ) - .toCompileTo('hello my world!'); - }); - - it('decorator nested inside of array-helper', () => { - expectTemplate('{{#arr}}{{*decorator}}world{{/arr}}') - .withInput({ arr: ['my'] }) - .withDecorator( - 'decorator', - (fn): TemplateDelegate => - (context, options) => { - expect(context).toMatchInlineSnapshot(`"my"`); - expect(options).toMatchInlineSnapshot(` - Object { - "blockParams": Array [ - "my", - 0, - ], - "data": Object { - "_parent": Object { - "root": Object { - "arr": Array [ - "my", - ], - }, - }, - "first": true, - "index": 0, - "key": 0, - "last": true, - "root": Object { - "arr": Array [ - "my", - ], - }, - }, - } - `); - return `hello ${context} ${fn()}!`; - } - ) - .toCompileTo('hello my world!'); - }); - - it('decorator nested inside of custom helper', () => { - expectTemplate('{{#helper}}{{*decorator}}world{{/helper}}') - .withHelper('helper', function (options: HelperOptions) { - return options.fn('my', { foo: 'bar' } as any); - }) - .withDecorator( - 'decorator', - (fn): TemplateDelegate => - (context, options) => { - expect(context).toMatchInlineSnapshot(`"my"`); - expect(options).toMatchInlineSnapshot(` - Object { - "foo": "bar", - } - `); - return `hello ${context} ${fn()}!`; - } - ) - .toCompileTo('hello my world!'); - }); - }); - - it('should call multiple decorators in the same program body in the expected order and get the expected output', () => { - let decoratorCall = 0; - let progCall = 0; - expectTemplate('{{*decorator}}con{{*decorator}}tent', { - beforeRender() { - // ensure the counters are reset between EVAL/AST render calls - decoratorCall = 0; - progCall = 0; - }, - }) - .withInput({ - decoratorCall: 0, - progCall: 0, - }) - .withDecorator('decorator', (fn) => { - const decoratorCallOrder = ++decoratorCall; - const ret: TemplateDelegate = () => { - const progCallOrder = ++progCall; - return `(decorator: ${decoratorCallOrder}, prog: ${progCallOrder}, fn: "${fn()}")`; - }; - return ret; - }) - .toCompileTo('(decorator: 2, prog: 1, fn: "(decorator: 1, prog: 2, fn: "content")")'); - }); - - describe('registration', () => { - beforeEach(() => { - global.kbnHandlebarsEnv = Handlebars.create(); - }); - - afterEach(() => { - global.kbnHandlebarsEnv = null; - }); - - it('should be able to call decorators registered using the `registerDecorator` function', () => { - let calls = 0; - const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; - - kbnHandlebarsEnv!.registerDecorator('decorator', () => { - calls++; - }); - - expectTemplate('{{*decorator}}').toCompileTo(''); - expect(calls).toEqual(callsExpected); - }); - - it('should not be able to call decorators unregistered using the `unregisterDecorator` function', () => { - let calls = 0; - - kbnHandlebarsEnv!.registerDecorator('decorator', () => { - calls++; - }); - - kbnHandlebarsEnv!.unregisterDecorator('decorator'); - - expectTemplate('{{*decorator}}').toThrow('lookupProperty(...) is not a function'); - expect(calls).toEqual(0); - }); - }); - }); -}); |