aboutsummaryrefslogtreecommitdiff
path: root/dev/lib/handlebars/src/spec/index.blocks.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dev/lib/handlebars/src/spec/index.blocks.test.ts')
-rw-r--r--dev/lib/handlebars/src/spec/index.blocks.test.ts366
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');
+ });
+ });
+ });
+});