diff options
Diffstat (limited to 'dev/lib/handlebars/src/spec/index.blocks.test.ts')
-rw-r--r-- | dev/lib/handlebars/src/spec/index.blocks.test.ts | 366 |
1 files changed, 366 insertions, 0 deletions
diff --git a/dev/lib/handlebars/src/spec/index.blocks.test.ts b/dev/lib/handlebars/src/spec/index.blocks.test.ts new file mode 100644 index 00000000..2d9a8707 --- /dev/null +++ b/dev/lib/handlebars/src/spec/index.blocks.test.ts @@ -0,0 +1,366 @@ +/* + * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), + * and may include modifications made by Elasticsearch B.V. + * Elasticsearch B.V. licenses this file to you under the MIT License. + * See `packages/kbn-handlebars/LICENSE` for more information. + */ + +import Handlebars, { type HelperOptions } from '../..'; +import { expectTemplate } from '../__jest__/test_bench'; + +describe('blocks', () => { + it('array', () => { + const string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!'; + + expectTemplate(string) + .withInput({ + goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], + world: 'world', + }) + .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); + + expectTemplate(string) + .withInput({ + goodbyes: [], + world: 'world', + }) + .toCompileTo('cruel world!'); + }); + + it('array without data', () => { + expectTemplate('{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}') + .withInput({ + goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], + world: 'world', + }) + .toCompileTo('goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE'); + }); + + it('array with @index', () => { + expectTemplate('{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!') + .withInput({ + goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], + world: 'world', + }) + .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!'); + }); + + it('empty block', () => { + const string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!'; + + expectTemplate(string) + .withInput({ + goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], + world: 'world', + }) + .toCompileTo('cruel world!'); + + expectTemplate(string) + .withInput({ + goodbyes: [], + world: 'world', + }) + .toCompileTo('cruel world!'); + }); + + it('block with complex lookup', () => { + expectTemplate('{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}') + .withInput({ + name: 'Alan', + goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], + }) + .toCompileTo('goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! '); + }); + + it('multiple blocks with complex lookup', () => { + expectTemplate('{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}') + .withInput({ + name: 'Alan', + goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], + }) + .toCompileTo('AlanAlanAlanAlanAlanAlan'); + }); + + it('block with complex lookup using nested context', () => { + expectTemplate('{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}').toThrow(Error); + }); + + it('block with deep nested complex lookup', () => { + expectTemplate( + '{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}' + ) + .withInput({ + omg: 'OMG!', + outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }], + }) + .toCompileTo('Goodbye cruel sad OMG!'); + }); + + it('works with cached blocks', () => { + expectTemplate('{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}') + .withCompileOptions({ data: false }) + .withInput({ + person: [ + { first: 'Alan', last: 'Johnson' }, + { first: 'Alan', last: 'Johnson' }, + ], + }) + .toCompileTo('Alan JohnsonAlan Johnson'); + }); + + describe('inverted sections', () => { + it('inverted sections with unset value', () => { + expectTemplate( + '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}' + ).toCompileTo('Right On!'); + }); + + it('inverted section with false value', () => { + expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}') + .withInput({ goodbyes: false }) + .toCompileTo('Right On!'); + }); + + it('inverted section with empty set', () => { + expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}') + .withInput({ goodbyes: [] }) + .toCompileTo('Right On!'); + }); + + it('block inverted sections', () => { + expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}') + .withInput({ none: 'No people' }) + .toCompileTo('No people'); + }); + + it('chained inverted sections', () => { + expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{/people}}') + .withInput({ none: 'No people' }) + .toCompileTo('No people'); + + expectTemplate( + '{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}' + ) + .withInput({ none: 'No people' }) + .toCompileTo('No people'); + + expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}') + .withInput({ none: 'No people' }) + .toCompileTo('No people'); + }); + + it('chained inverted sections with mismatch', () => { + expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{/if}}').toThrow(Error); + }); + + it('block inverted sections with empty arrays', () => { + expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}') + .withInput({ + none: 'No people', + people: [], + }) + .toCompileTo('No people'); + }); + }); + + describe('standalone sections', () => { + it('block standalone else sections', () => { + expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n') + .withInput({ none: 'No people' }) + .toCompileTo('No people\n'); + + expectTemplate('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n') + .withInput({ none: 'No people' }) + .toCompileTo('No people\n'); + + expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n') + .withInput({ none: 'No people' }) + .toCompileTo('No people\n'); + }); + + it('block standalone chained else sections', () => { + expectTemplate('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n') + .withInput({ none: 'No people' }) + .toCompileTo('No people\n'); + + expectTemplate('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n') + .withInput({ none: 'No people' }) + .toCompileTo('No people\n'); + }); + + it('should handle nesting', () => { + expectTemplate('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.') + .withInput({ + data: [1, 3, 5], + }) + .toCompileTo('1\n3\n5\nOK.'); + }); + }); + + describe('decorators', () => { + it('should apply mustache decorators', () => { + expectTemplate('{{#helper}}{{*decorator}}{{/helper}}') + .withHelper('helper', function (options: HelperOptions) { + return (options.fn as any).run; + }) + .withDecorator('decorator', function (fn) { + (fn as any).run = 'success'; + return fn; + }) + .toCompileTo('success'); + }); + + it('should apply allow undefined return', () => { + expectTemplate('{{#helper}}{{*decorator}}suc{{/helper}}') + .withHelper('helper', function (options: HelperOptions) { + return options.fn() + (options.fn as any).run; + }) + .withDecorator('decorator', function (fn) { + (fn as any).run = 'cess'; + }) + .toCompileTo('success'); + }); + + it('should apply block decorators', () => { + expectTemplate('{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}') + .withHelper('helper', function (options: HelperOptions) { + return (options.fn as any).run; + }) + .withDecorator('decorator', function (fn, props, container, options) { + (fn as any).run = options.fn(); + return fn; + }) + .toCompileTo('success'); + }); + + it('should support nested decorators', () => { + expectTemplate( + '{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}' + ) + .withHelper('helper', function (options: HelperOptions) { + return (options.fn as any).run; + }) + .withDecorators({ + decorator(fn, props, container, options) { + (fn as any).run = options.fn.nested + options.fn(); + return fn; + }, + nested(fn, props, container, options) { + props.nested = options.fn(); + }, + }) + .toCompileTo('success'); + }); + + it('should apply multiple decorators', () => { + expectTemplate( + '{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}' + ) + .withHelper('helper', function (options: HelperOptions) { + return (options.fn as any).run; + }) + .withDecorator('decorator', function (fn, props, container, options) { + (fn as any).run = ((fn as any).run || '') + options.fn(); + return fn; + }) + .toCompileTo('success'); + }); + + it('should access parent variables', () => { + expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}') + .withHelper('helper', function (options: HelperOptions) { + return (options.fn as any).run; + }) + .withDecorator('decorator', function (fn, props, container, options) { + (fn as any).run = options.args; + return fn; + }) + .withInput({ foo: 'success' }) + .toCompileTo('success'); + }); + + it('should work with root program', () => { + let run; + expectTemplate('{{*decorator "success"}}') + .withDecorator('decorator', function (fn, props, container, options) { + expect(options.args[0]).toEqual('success'); + run = true; + return fn; + }) + .withInput({ foo: 'success' }) + .toCompileTo(''); + expect(run).toEqual(true); + }); + + it('should fail when accessing variables from root', () => { + let run; + expectTemplate('{{*decorator foo}}') + .withDecorator('decorator', function (fn, props, container, options) { + expect(options.args[0]).toBeUndefined(); + run = true; + return fn; + }) + .withInput({ foo: 'fail' }) + .toCompileTo(''); + expect(run).toEqual(true); + }); + + describe('registration', () => { + beforeEach(() => { + global.kbnHandlebarsEnv = Handlebars.create(); + }); + + afterEach(() => { + global.kbnHandlebarsEnv = null; + }); + + it('unregisters', () => { + // @ts-expect-error: Cannot assign to 'decorators' because it is a read-only property. + kbnHandlebarsEnv!.decorators = {}; + + kbnHandlebarsEnv!.registerDecorator('foo', function () { + return 'fail'; + }); + + expect(!!kbnHandlebarsEnv!.decorators.foo).toEqual(true); + kbnHandlebarsEnv!.unregisterDecorator('foo'); + expect(kbnHandlebarsEnv!.decorators.foo).toBeUndefined(); + }); + + it('allows multiple globals', () => { + // @ts-expect-error: Cannot assign to 'decorators' because it is a read-only property. + kbnHandlebarsEnv!.decorators = {}; + + // @ts-expect-error: Expected 2 arguments, but got 1. + kbnHandlebarsEnv!.registerDecorator({ + foo() {}, + bar() {}, + }); + + expect(!!kbnHandlebarsEnv!.decorators.foo).toEqual(true); + expect(!!kbnHandlebarsEnv!.decorators.bar).toEqual(true); + kbnHandlebarsEnv!.unregisterDecorator('foo'); + kbnHandlebarsEnv!.unregisterDecorator('bar'); + expect(kbnHandlebarsEnv!.decorators.foo).toBeUndefined(); + expect(kbnHandlebarsEnv!.decorators.bar).toBeUndefined(); + }); + + it('fails with multiple and args', () => { + expect(() => { + kbnHandlebarsEnv!.registerDecorator( + // @ts-expect-error: Argument of type '{ world(): string; testHelper(): string; }' is not assignable to parameter of type 'string'. + { + world() { + return 'world!'; + }, + testHelper() { + return 'found it!'; + }, + }, + {} + ); + }).toThrow('Arg not supported with multiple decorators'); + }); + }); + }); +}); |