mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
parent
c3ce5f7fab
commit
aa344928d8
6 changed files with 678 additions and 177 deletions
|
@ -1,4 +1,4 @@
|
|||
1,3c1,12
|
||||
1,3c1,13
|
||||
< describe('blocks', function() {
|
||||
< it('array', function() {
|
||||
< var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!';
|
||||
|
@ -10,12 +10,13 @@
|
|||
> * See `packages/kbn-handlebars/LICENSE` for more information.
|
||||
> */
|
||||
>
|
||||
> import Handlebars from '../..';
|
||||
> import { expectTemplate } from '../__jest__/test_bench';
|
||||
>
|
||||
> describe('blocks', () => {
|
||||
> it('array', () => {
|
||||
> const string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!';
|
||||
7,12c16,17
|
||||
7,12c17,18
|
||||
< goodbyes: [
|
||||
< { text: 'goodbye' },
|
||||
< { text: 'Goodbye' },
|
||||
|
@ -25,15 +26,15 @@
|
|||
---
|
||||
> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
|
||||
> world: 'world',
|
||||
14d18
|
||||
14d19
|
||||
< .withMessage('Arrays iterate over the contents when not empty')
|
||||
20c24
|
||||
20c25
|
||||
< world: 'world'
|
||||
---
|
||||
> world: 'world',
|
||||
22d25
|
||||
22d26
|
||||
< .withMessage('Arrays ignore the contents when empty')
|
||||
26,29c29,30
|
||||
26,29c30,31
|
||||
< it('array without data', function() {
|
||||
< expectTemplate(
|
||||
< '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}'
|
||||
|
@ -41,7 +42,7 @@
|
|||
---
|
||||
> it('array without data', () => {
|
||||
> expectTemplate('{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}')
|
||||
31,36c32,33
|
||||
31,36c33,34
|
||||
< goodbyes: [
|
||||
< { text: 'goodbye' },
|
||||
< { text: 'Goodbye' },
|
||||
|
@ -51,9 +52,9 @@
|
|||
---
|
||||
> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
|
||||
> world: 'world',
|
||||
38d34
|
||||
38d35
|
||||
< .withCompileOptions({ compat: false })
|
||||
42,45c38,39
|
||||
42,45c39,40
|
||||
< it('array with @index', function() {
|
||||
< expectTemplate(
|
||||
< '{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!'
|
||||
|
@ -61,7 +62,7 @@
|
|||
---
|
||||
> it('array with @index', () => {
|
||||
> expectTemplate('{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!')
|
||||
47,52c41,42
|
||||
47,52c42,43
|
||||
< goodbyes: [
|
||||
< { text: 'goodbye' },
|
||||
< { text: 'Goodbye' },
|
||||
|
@ -71,15 +72,15 @@
|
|||
---
|
||||
> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
|
||||
> world: 'world',
|
||||
54d43
|
||||
54d44
|
||||
< .withMessage('The @index variable is used')
|
||||
58,59c47,48
|
||||
58,59c48,49
|
||||
< it('empty block', function() {
|
||||
< var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!';
|
||||
---
|
||||
> it('empty block', () => {
|
||||
> const string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!';
|
||||
63,68c52,53
|
||||
63,68c53,54
|
||||
< goodbyes: [
|
||||
< { text: 'goodbye' },
|
||||
< { text: 'Goodbye' },
|
||||
|
@ -89,19 +90,19 @@
|
|||
---
|
||||
> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
|
||||
> world: 'world',
|
||||
70d54
|
||||
70d55
|
||||
< .withMessage('Arrays iterate over the contents when not empty')
|
||||
76c60
|
||||
76c61
|
||||
< world: 'world'
|
||||
---
|
||||
> world: 'world',
|
||||
78d61
|
||||
78d62
|
||||
< .withMessage('Arrays ignore the contents when empty')
|
||||
82c65
|
||||
82c66
|
||||
< it('block with complex lookup', function() {
|
||||
---
|
||||
> it('block with complex lookup', () => {
|
||||
86,90c69
|
||||
86,90c70
|
||||
< goodbyes: [
|
||||
< { text: 'goodbye' },
|
||||
< { text: 'Goodbye' },
|
||||
|
@ -109,7 +110,7 @@
|
|||
< ]
|
||||
---
|
||||
> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
|
||||
92,97c71
|
||||
92,97c72
|
||||
< .withMessage(
|
||||
< 'Templates can access variables in contexts up the stack with relative path syntax'
|
||||
< )
|
||||
|
@ -118,11 +119,11 @@
|
|||
< );
|
||||
---
|
||||
> .toCompileTo('goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ');
|
||||
100c74
|
||||
100c75
|
||||
< it('multiple blocks with complex lookup', function() {
|
||||
---
|
||||
> it('multiple blocks with complex lookup', () => {
|
||||
104,108c78
|
||||
104,108c79
|
||||
< goodbyes: [
|
||||
< { text: 'goodbye' },
|
||||
< { text: 'Goodbye' },
|
||||
|
@ -130,7 +131,7 @@
|
|||
< ]
|
||||
---
|
||||
> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
|
||||
113,116c83,84
|
||||
113,116c84,85
|
||||
< it('block with complex lookup using nested context', function() {
|
||||
< expectTemplate(
|
||||
< '{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}'
|
||||
|
@ -138,15 +139,15 @@
|
|||
---
|
||||
> it('block with complex lookup using nested context', () => {
|
||||
> expectTemplate('{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}').toThrow(Error);
|
||||
119c87
|
||||
119c88
|
||||
< it('block with deep nested complex lookup', function() {
|
||||
---
|
||||
> it('block with deep nested complex lookup', () => {
|
||||
125c93
|
||||
125c94
|
||||
< outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }]
|
||||
---
|
||||
> outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }],
|
||||
130,133c98,99
|
||||
130,133c99,100
|
||||
< it('works with cached blocks', function() {
|
||||
< expectTemplate(
|
||||
< '{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}'
|
||||
|
@ -154,25 +155,25 @@
|
|||
---
|
||||
> it('works with cached blocks', () => {
|
||||
> expectTemplate('{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}')
|
||||
138,139c104,105
|
||||
138,139c105,106
|
||||
< { first: 'Alan', last: 'Johnson' }
|
||||
< ]
|
||||
---
|
||||
> { first: 'Alan', last: 'Johnson' },
|
||||
> ],
|
||||
144,145c110,111
|
||||
144,145c111,112
|
||||
< describe('inverted sections', function() {
|
||||
< it('inverted sections with unset value', function() {
|
||||
---
|
||||
> describe('inverted sections', () => {
|
||||
> it('inverted sections with unset value', () => {
|
||||
148,150c114
|
||||
148,150c115
|
||||
< )
|
||||
< .withMessage("Inverted section rendered when value isn't set.")
|
||||
< .toCompileTo('Right On!');
|
||||
---
|
||||
> ).toCompileTo('Right On!');
|
||||
153,156c117,118
|
||||
153,156c118,119
|
||||
< it('inverted section with false value', function() {
|
||||
< expectTemplate(
|
||||
< '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
|
||||
|
@ -180,9 +181,9 @@
|
|||
---
|
||||
> it('inverted section with false value', () => {
|
||||
> expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}')
|
||||
158d119
|
||||
158d120
|
||||
< .withMessage('Inverted section rendered when value is false.')
|
||||
162,165c123,124
|
||||
162,165c124,125
|
||||
< it('inverted section with empty set', function() {
|
||||
< expectTemplate(
|
||||
< '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
|
||||
|
@ -190,23 +191,23 @@
|
|||
---
|
||||
> it('inverted section with empty set', () => {
|
||||
> expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}')
|
||||
167d125
|
||||
167d126
|
||||
< .withMessage('Inverted section rendered when value is empty set.')
|
||||
171c129
|
||||
171c130
|
||||
< it('block inverted sections', function() {
|
||||
---
|
||||
> it('block inverted sections', () => {
|
||||
177c135
|
||||
177c136
|
||||
< it('chained inverted sections', function() {
|
||||
---
|
||||
> it('chained inverted sections', () => {
|
||||
188,190c146
|
||||
188,190c147
|
||||
< expectTemplate(
|
||||
< '{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}'
|
||||
< )
|
||||
---
|
||||
> expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}')
|
||||
195,198c151,152
|
||||
195,198c152,153
|
||||
< it('chained inverted sections with mismatch', function() {
|
||||
< expectTemplate(
|
||||
< '{{#people}}{{name}}{{else if none}}{{none}}{{/if}}'
|
||||
|
@ -214,21 +215,21 @@
|
|||
---
|
||||
> it('chained inverted sections with mismatch', () => {
|
||||
> expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{/if}}').toThrow(Error);
|
||||
201c155
|
||||
201c156
|
||||
< it('block inverted sections with empty arrays', function() {
|
||||
---
|
||||
> it('block inverted sections with empty arrays', () => {
|
||||
205c159
|
||||
205c160
|
||||
< people: []
|
||||
---
|
||||
> people: [],
|
||||
211,212c165,166
|
||||
211,212c166,167
|
||||
< describe('standalone sections', function() {
|
||||
< it('block standalone else sections', function() {
|
||||
---
|
||||
> describe('standalone sections', () => {
|
||||
> it('block standalone else sections', () => {
|
||||
226,241c180,181
|
||||
226,241c181,182
|
||||
< it('block standalone else sections can be disabled', function() {
|
||||
< expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n')
|
||||
< .withInput({ none: 'No people' })
|
||||
|
@ -248,22 +249,21 @@
|
|||
---
|
||||
> it('block standalone chained else sections', () => {
|
||||
> expectTemplate('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n')
|
||||
245,247c185
|
||||
245,247c186
|
||||
< expectTemplate(
|
||||
< '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n'
|
||||
< )
|
||||
---
|
||||
> expectTemplate('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n')
|
||||
252c190
|
||||
252c191
|
||||
< it('should handle nesting', function() {
|
||||
---
|
||||
> it('should handle nesting', () => {
|
||||
255c193
|
||||
255c194
|
||||
< data: [1, 3, 5]
|
||||
---
|
||||
> data: [1, 3, 5],
|
||||
260,455d197
|
||||
<
|
||||
261,297c200,201
|
||||
< describe('compat mode', function() {
|
||||
< it('block with deep recursive lookup lookup', function() {
|
||||
< expectTemplate(
|
||||
|
@ -301,143 +301,181 @@
|
|||
<
|
||||
< describe('decorators', function() {
|
||||
< it('should apply mustache decorators', function() {
|
||||
< expectTemplate('{{#helper}}{{*decorator}}{{/helper}}')
|
||||
---
|
||||
> describe('decorators', () => {
|
||||
> it('should apply mustache decorators', () => {
|
||||
299c203
|
||||
< .withHelper('helper', function(options) {
|
||||
< return options.fn.run;
|
||||
< })
|
||||
---
|
||||
> .withHelper('helper', function (options) {
|
||||
302c206,207
|
||||
< .withDecorator('decorator', function(fn) {
|
||||
< fn.run = 'success';
|
||||
< return fn;
|
||||
< })
|
||||
< .toCompileTo('success');
|
||||
< });
|
||||
<
|
||||
---
|
||||
> .withDecorator('decorator', function (fn) {
|
||||
> // @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
|
||||
309c214
|
||||
< it('should apply allow undefined return', function() {
|
||||
< expectTemplate('{{#helper}}{{*decorator}}suc{{/helper}}')
|
||||
---
|
||||
> it('should apply allow undefined return', () => {
|
||||
311c216
|
||||
< .withHelper('helper', function(options) {
|
||||
< return options.fn() + options.fn.run;
|
||||
< })
|
||||
---
|
||||
> .withHelper('helper', function (options) {
|
||||
314c219,220
|
||||
< .withDecorator('decorator', function(fn) {
|
||||
< fn.run = 'cess';
|
||||
< })
|
||||
< .toCompileTo('success');
|
||||
< });
|
||||
<
|
||||
---
|
||||
> .withDecorator('decorator', function (fn) {
|
||||
> // @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
|
||||
320,324c226,228
|
||||
< it('should apply block decorators', function() {
|
||||
< expectTemplate(
|
||||
< '{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}'
|
||||
< )
|
||||
< .withHelper('helper', function(options) {
|
||||
< return options.fn.run;
|
||||
< })
|
||||
---
|
||||
> it('should apply block decorators', () => {
|
||||
> expectTemplate('{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}')
|
||||
> .withHelper('helper', function (options) {
|
||||
327c231,232
|
||||
< .withDecorator('decorator', function(fn, props, container, options) {
|
||||
< fn.run = options.fn();
|
||||
< return fn;
|
||||
< })
|
||||
< .toCompileTo('success');
|
||||
< });
|
||||
<
|
||||
---
|
||||
> .withDecorator('decorator', function (fn, props, container, options) {
|
||||
> // @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
|
||||
334c239
|
||||
< it('should support nested decorators', function() {
|
||||
< expectTemplate(
|
||||
< '{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}'
|
||||
< )
|
||||
---
|
||||
> it('should support nested decorators', () => {
|
||||
338c243
|
||||
< .withHelper('helper', function(options) {
|
||||
< return options.fn.run;
|
||||
< })
|
||||
< .withDecorators({
|
||||
---
|
||||
> .withHelper('helper', function (options) {
|
||||
342c247,248
|
||||
< decorator: function(fn, props, container, options) {
|
||||
< fn.run = options.fn.nested + options.fn();
|
||||
< return fn;
|
||||
< },
|
||||
---
|
||||
> decorator(fn, props, container, options) {
|
||||
> // @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
|
||||
346c252
|
||||
< nested: function(fn, props, container, options) {
|
||||
< props.nested = options.fn();
|
||||
---
|
||||
> nested(fn, props, container, options) {
|
||||
348c254
|
||||
< }
|
||||
< })
|
||||
< .toCompileTo('success');
|
||||
< });
|
||||
<
|
||||
---
|
||||
> },
|
||||
353c259
|
||||
< it('should apply multiple decorators', function() {
|
||||
< expectTemplate(
|
||||
< '{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}'
|
||||
< )
|
||||
---
|
||||
> it('should apply multiple decorators', () => {
|
||||
357c263
|
||||
< .withHelper('helper', function(options) {
|
||||
< return options.fn.run;
|
||||
< })
|
||||
---
|
||||
> .withHelper('helper', function (options) {
|
||||
360c266,267
|
||||
< .withDecorator('decorator', function(fn, props, container, options) {
|
||||
< fn.run = (fn.run || '') + options.fn();
|
||||
< return fn;
|
||||
< })
|
||||
< .toCompileTo('success');
|
||||
< });
|
||||
<
|
||||
---
|
||||
> .withDecorator('decorator', function (fn, props, container, options) {
|
||||
> // @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
|
||||
367c274
|
||||
< it('should access parent variables', function() {
|
||||
< expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}')
|
||||
---
|
||||
> it('should access parent variables', () => {
|
||||
369c276
|
||||
< .withHelper('helper', function(options) {
|
||||
< return options.fn.run;
|
||||
< })
|
||||
---
|
||||
> .withHelper('helper', function (options) {
|
||||
372c279,280
|
||||
< .withDecorator('decorator', function(fn, props, container, options) {
|
||||
< fn.run = options.args;
|
||||
< return fn;
|
||||
< })
|
||||
< .withInput({ foo: 'success' })
|
||||
< .toCompileTo('success');
|
||||
< });
|
||||
<
|
||||
---
|
||||
> .withDecorator('decorator', function (fn, props, container, options) {
|
||||
> // @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
|
||||
380,381c288,289
|
||||
< it('should work with root program', function() {
|
||||
< var run;
|
||||
< expectTemplate('{{*decorator "success"}}')
|
||||
---
|
||||
> it('should work with root program', () => {
|
||||
> let run;
|
||||
383,384c291,292
|
||||
< .withDecorator('decorator', function(fn, props, container, options) {
|
||||
< equals(options.args[0], 'success');
|
||||
< run = true;
|
||||
< return fn;
|
||||
< })
|
||||
< .withInput({ foo: 'success' })
|
||||
< .toCompileTo('');
|
||||
---
|
||||
> .withDecorator('decorator', function (fn, props, container, options) {
|
||||
> expect(options.args[0]).toEqual('success');
|
||||
390c298
|
||||
< equals(run, true);
|
||||
< });
|
||||
<
|
||||
---
|
||||
> expect(run).toEqual(true);
|
||||
393,394c301,302
|
||||
< it('should fail when accessing variables from root', function() {
|
||||
< var run;
|
||||
< expectTemplate('{{*decorator foo}}')
|
||||
---
|
||||
> it('should fail when accessing variables from root', () => {
|
||||
> let run;
|
||||
396,397c304,305
|
||||
< .withDecorator('decorator', function(fn, props, container, options) {
|
||||
< equals(options.args[0], undefined);
|
||||
< run = true;
|
||||
< return fn;
|
||||
< })
|
||||
< .withInput({ foo: 'fail' })
|
||||
< .toCompileTo('');
|
||||
---
|
||||
> .withDecorator('decorator', function (fn, props, container, options) {
|
||||
> expect(options.args[0]).toBeUndefined();
|
||||
403c311
|
||||
< equals(run, true);
|
||||
< });
|
||||
<
|
||||
---
|
||||
> expect(run).toEqual(true);
|
||||
406,408c314,321
|
||||
< describe('registration', function() {
|
||||
< it('unregisters', function() {
|
||||
< handlebarsEnv.decorators = {};
|
||||
<
|
||||
---
|
||||
> describe('registration', () => {
|
||||
> beforeEach(() => {
|
||||
> global.kbnHandlebarsEnv = Handlebars.create();
|
||||
> });
|
||||
>
|
||||
> it('unregisters', () => {
|
||||
> // @ts-expect-error: Cannot assign to 'decorators' because it is a read-only property.
|
||||
> kbnHandlebarsEnv!.decorators = {};
|
||||
410c323
|
||||
< handlebarsEnv.registerDecorator('foo', function() {
|
||||
< return 'fail';
|
||||
< });
|
||||
<
|
||||
---
|
||||
> kbnHandlebarsEnv!.registerDecorator('foo', function () {
|
||||
414,416c327,329
|
||||
< equals(!!handlebarsEnv.decorators.foo, true);
|
||||
< handlebarsEnv.unregisterDecorator('foo');
|
||||
< equals(handlebarsEnv.decorators.foo, undefined);
|
||||
< });
|
||||
<
|
||||
---
|
||||
> expect(!!kbnHandlebarsEnv!.decorators.foo).toEqual(true);
|
||||
> kbnHandlebarsEnv!.unregisterDecorator('foo');
|
||||
> expect(kbnHandlebarsEnv!.decorators.foo).toBeUndefined();
|
||||
419,424c332,339
|
||||
< it('allows multiple globals', function() {
|
||||
< handlebarsEnv.decorators = {};
|
||||
<
|
||||
< handlebarsEnv.registerDecorator({
|
||||
< foo: function() {},
|
||||
< bar: function() {}
|
||||
< });
|
||||
<
|
||||
---
|
||||
> 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() {},
|
||||
427,432c342,347
|
||||
< equals(!!handlebarsEnv.decorators.foo, true);
|
||||
< equals(!!handlebarsEnv.decorators.bar, true);
|
||||
< handlebarsEnv.unregisterDecorator('foo');
|
||||
< handlebarsEnv.unregisterDecorator('bar');
|
||||
< equals(handlebarsEnv.decorators.foo, undefined);
|
||||
< equals(handlebarsEnv.decorators.bar, undefined);
|
||||
< });
|
||||
<
|
||||
---
|
||||
> 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();
|
||||
435,445c350,356
|
||||
< it('fails with multiple and args', function() {
|
||||
< shouldThrow(
|
||||
< function() {
|
||||
|
@ -449,13 +487,26 @@
|
|||
< testHelper: function() {
|
||||
< return 'found it!';
|
||||
< }
|
||||
< },
|
||||
---
|
||||
> 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!';
|
||||
447,452c358,364
|
||||
< {}
|
||||
< );
|
||||
< },
|
||||
< Error,
|
||||
< 'Arg not supported with multiple decorators'
|
||||
< );
|
||||
< });
|
||||
< });
|
||||
< });
|
||||
---
|
||||
> testHelper() {
|
||||
> return 'found it!';
|
||||
> },
|
||||
> },
|
||||
> {}
|
||||
> );
|
||||
> }).toThrow('Arg not supported with multiple decorators');
|
||||
|
|
|
@ -103,3 +103,7 @@ HandlebarsEnvironment {
|
|||
"template": [Function],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`blocks decorators registration should not be able to call decorators unregistered using the \`unregisterDecorator\` function 1`] = `"lookupProperty(...) is not a function"`;
|
||||
|
||||
exports[`blocks decorators registration should not be able to call decorators unregistered using the \`unregisterDecorator\` function 2`] = `"decoratorFn is not a function"`;
|
||||
|
|
|
@ -92,3 +92,145 @@ it('Only provide options.fn/inverse to block helpers', () => {
|
|||
expect(toNotHaveProperties.calls).toEqual(nonBlockTemplates.length * 2 * factor);
|
||||
expect(toHaveProperties.calls).toEqual(blockTemplates.length * 2 * factor);
|
||||
});
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
it('should call decorator again if render function is called again', () => {
|
||||
const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2;
|
||||
|
||||
global.kbnHandlebarsEnv = Handlebars.create();
|
||||
|
||||
kbnHandlebarsEnv!.registerDecorator('decorator', () => {
|
||||
calls++;
|
||||
});
|
||||
|
||||
let renderAST;
|
||||
let renderEval;
|
||||
if (process.env.AST || !process.env.EVAL) {
|
||||
renderAST = kbnHandlebarsEnv!.compileAST('{{*decorator}}');
|
||||
}
|
||||
if (process.env.EVAL || !process.env.AST) {
|
||||
renderEval = kbnHandlebarsEnv!.compile('{{*decorator}}');
|
||||
}
|
||||
|
||||
let calls = 0;
|
||||
if (renderAST) expect(renderAST({})).toEqual('');
|
||||
if (renderEval) expect(renderEval({})).toEqual('');
|
||||
expect(calls).toEqual(callsExpected);
|
||||
|
||||
calls = 0;
|
||||
if (renderAST) expect(renderAST({})).toEqual('');
|
||||
if (renderEval) expect(renderEval({})).toEqual('');
|
||||
expect(calls).toEqual(callsExpected);
|
||||
});
|
||||
|
||||
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', () => {
|
||||
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('registration', () => {
|
||||
beforeEach(() => {
|
||||
global.kbnHandlebarsEnv = Handlebars.create();
|
||||
});
|
||||
|
||||
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}}').toThrowErrorMatchingSnapshot();
|
||||
expect(calls).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -59,7 +59,31 @@ export type ExtendedCompileOptions = Pick<
|
|||
* This is a subset of all the runtime options supported by the upstream
|
||||
* Handlebars module.
|
||||
*/
|
||||
export type ExtendedRuntimeOptions = Pick<RuntimeOptions, 'helpers' | 'blockParams' | 'data'>;
|
||||
export type ExtendedRuntimeOptions = Pick<
|
||||
RuntimeOptions,
|
||||
'helpers' | 'blockParams' | 'data' | 'decorators'
|
||||
>;
|
||||
|
||||
/**
|
||||
* According to the [decorator docs]{@link https://github.com/handlebars-lang/handlebars.js/blob/4.x/docs/decorators-api.md},
|
||||
* a decorator will be called with a different set of arugments than what's actually happening in the upstream code.
|
||||
* So here I assume that the docs are wrong and that the upstream code is correct. In reality, `context` is the last 4
|
||||
* documented arguments rolled into one object.
|
||||
*/
|
||||
export type DecoratorFunction = (
|
||||
prog: Handlebars.TemplateDelegate,
|
||||
props: Record<string, any>,
|
||||
container: Container,
|
||||
options: any
|
||||
) => any;
|
||||
|
||||
export interface DecoratorsHash {
|
||||
[name: string]: DecoratorFunction;
|
||||
}
|
||||
|
||||
export interface HelpersHash {
|
||||
[name: string]: Handlebars.HelperDelegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normally this namespace isn't used directly. It's required to be present by
|
||||
|
@ -116,16 +140,18 @@ Handlebars.compileAST = function (
|
|||
|
||||
// If `Handlebars.compileAST` is reassigned, `this` will be undefined.
|
||||
const helpers = (this ?? Handlebars).helpers;
|
||||
const decorators = (this ?? Handlebars).decorators as DecoratorsHash;
|
||||
|
||||
const visitor = new ElasticHandlebarsVisitor(input, options, helpers);
|
||||
const visitor = new ElasticHandlebarsVisitor(input, options, helpers, decorators);
|
||||
return (context: any, runtimeOptions?: ExtendedRuntimeOptions) =>
|
||||
visitor.render(context, runtimeOptions);
|
||||
};
|
||||
|
||||
interface Container {
|
||||
helpers: { [name: string]: Handlebars.HelperDelegate };
|
||||
helpers: HelpersHash;
|
||||
decorators: DecoratorsHash;
|
||||
strict: (obj: { [name: string]: any }, name: string, loc: hbs.AST.SourceLocation) => any;
|
||||
lookupProperty: (parent: { [name: string]: any }, propertyName: string) => any;
|
||||
lookupProperty: <T = any>(parent: { [name: string]: any }, propertyName: string) => T;
|
||||
lambda: (current: any, context: any) => any;
|
||||
data: (value: any, depth: number) => any;
|
||||
hooks: {
|
||||
|
@ -140,18 +166,22 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
|
|||
private template?: string;
|
||||
private compileOptions: ExtendedCompileOptions;
|
||||
private runtimeOptions?: ExtendedRuntimeOptions;
|
||||
private initialHelpers: { [name: string]: Handlebars.HelperDelegate };
|
||||
private initialHelpers: HelpersHash;
|
||||
private initialDecorators: DecoratorsHash;
|
||||
private blockParamNames: any[][] = [];
|
||||
private blockParamValues: any[][] = [];
|
||||
private ast?: hbs.AST.Program;
|
||||
private container: Container;
|
||||
// @ts-expect-error
|
||||
private defaultHelperOptions: Handlebars.HelperOptions = {};
|
||||
private processedRootDecorators = false; // Root decorators should not have access to input arguments. This flag helps us detect them.
|
||||
private processedDecoratorsForProgram = new Set(); // It's important that a given program node only has its decorators run once, we use this Map to keep track of them
|
||||
|
||||
constructor(
|
||||
input: string | hbs.AST.Program,
|
||||
options: ExtendedCompileOptions = {},
|
||||
helpers: { [name: string]: Handlebars.HelperDelegate }
|
||||
helpers: HelpersHash,
|
||||
decorators: DecoratorsHash
|
||||
) {
|
||||
super();
|
||||
|
||||
|
@ -184,11 +214,13 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
|
|||
);
|
||||
|
||||
this.initialHelpers = Object.assign({}, helpers);
|
||||
this.initialDecorators = Object.assign({}, decorators);
|
||||
|
||||
const protoAccessControl = createProtoAccessControl({});
|
||||
|
||||
const container: Container = (this.container = {
|
||||
helpers: {},
|
||||
decorators: {},
|
||||
strict(obj, name, loc) {
|
||||
if (!obj || !(name in obj)) {
|
||||
throw new Handlebars.Exception('"' + name + '" not defined in ' + obj, {
|
||||
|
@ -234,7 +266,13 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
|
|||
this.output = [];
|
||||
this.runtimeOptions = options;
|
||||
this.container.helpers = Object.assign(this.initialHelpers, options.helpers);
|
||||
this.container.decorators = Object.assign(
|
||||
this.initialDecorators,
|
||||
options.decorators as DecoratorsHash
|
||||
);
|
||||
this.container.hooks = {};
|
||||
this.processedRootDecorators = false;
|
||||
this.processedDecoratorsForProgram.clear();
|
||||
|
||||
if (this.compileOptions.data) {
|
||||
this.runtimeOptions.data = initData(context, this.runtimeOptions.data);
|
||||
|
@ -259,6 +297,10 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
|
|||
|
||||
Program(program: hbs.AST.Program) {
|
||||
this.blockParamNames.unshift(program.blockParams);
|
||||
|
||||
this.processDecorators(program, this.generateProgramFunction(program));
|
||||
this.processedRootDecorators = true;
|
||||
|
||||
super.Program(program);
|
||||
this.blockParamNames.shift();
|
||||
}
|
||||
|
@ -271,6 +313,16 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
|
|||
this.processStatementOrExpression(block);
|
||||
}
|
||||
|
||||
// This space intentionally left blank: We want to override the Visitor class implementation
|
||||
// of `DecoratorBlock`, but since we handle decorators separately before traversing the
|
||||
// nodes, we just want to make this a no-op.
|
||||
DecoratorBlock(decorator: hbs.AST.DecoratorBlock) {}
|
||||
|
||||
// This space intentionally left blank: We want to override the Visitor class implementation
|
||||
// of `DecoratorBlock`, but since we handle decorators separately before traversing the
|
||||
// nodes, we just want to make this a no-op.
|
||||
Decorator(decorator: hbs.AST.Decorator) {}
|
||||
|
||||
SubExpression(sexpr: hbs.AST.SubExpression) {
|
||||
this.processStatementOrExpression(sexpr);
|
||||
}
|
||||
|
@ -319,6 +371,41 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
|
|||
// *** Visitor AST Helper Functions *** //
|
||||
// ********************************************** //
|
||||
|
||||
/**
|
||||
* Special code for decorators, since they have to be executed ahead of time (before the wrapping program).
|
||||
* So we have to look into the program AST body and see if it contains any decorators that we have to process
|
||||
* before we can finish processing of the wrapping program.
|
||||
*/
|
||||
private processDecorators(program: hbs.AST.Program, prog: Handlebars.TemplateDelegate) {
|
||||
if (!this.processedDecoratorsForProgram.has(program)) {
|
||||
for (const node of program.body) {
|
||||
if (isDecorator(node)) {
|
||||
this.processDecorator(node, prog);
|
||||
}
|
||||
}
|
||||
this.processedDecoratorsForProgram.add(program);
|
||||
}
|
||||
}
|
||||
|
||||
private processDecorator(
|
||||
decorator: hbs.AST.DecoratorBlock | hbs.AST.Decorator,
|
||||
prog: Handlebars.TemplateDelegate
|
||||
) {
|
||||
// TypeScript: The types indicate that `decorator.path` technically can be an `hbs.AST.Literal`. However, the upstream codebase always treats it as an `hbs.AST.PathExpression`, so we do too.
|
||||
const name = (decorator.path as hbs.AST.PathExpression).original;
|
||||
const decoratorFn = this.container.lookupProperty<DecoratorFunction>(
|
||||
this.container.decorators,
|
||||
name
|
||||
);
|
||||
const props = {};
|
||||
// TypeScript: Because `decorator` can be of type `hbs.AST.Decorator`, TS indicates that `decorator.path` technically can be an `hbs.AST.Literal`. However, the upstream codebase always treats it as an `hbs.AST.PathExpression`, so we do too.
|
||||
const options = this.setupParams(decorator as hbs.AST.DecoratorBlock, name);
|
||||
// @ts-expect-error: Property 'lookupProperty' does not exist on type 'HelperOptions'
|
||||
delete options.lookupProperty; // There's really no tests/documentation on this, but to match the upstream codebase we'll remove `lookupProperty` from the decorator context
|
||||
|
||||
Object.assign(decoratorFn(prog, props, this.container, options) || prog, props);
|
||||
}
|
||||
|
||||
private processStatementOrExpression(node: ProcessableNode) {
|
||||
transformLiteralToPath(node);
|
||||
|
||||
|
@ -559,49 +646,72 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
|
|||
name: helperName,
|
||||
hash: this.getHash(node),
|
||||
data: this.runtimeOptions!.data,
|
||||
loc: { start: node.loc.start, end: node.loc.end },
|
||||
};
|
||||
|
||||
if (node.params.length > 0) {
|
||||
if (!this.processedRootDecorators) {
|
||||
// When processing the root decorators, temporarily remove the root context so it's not accessible to the decorator
|
||||
const context = this.scopes.shift();
|
||||
// @ts-expect-error: Property 'args' does not exist on type 'HelperOptions'. The 'args' property is expected in decorators
|
||||
options.args = this.resolveNodes(node.params);
|
||||
this.scopes.unshift(context);
|
||||
} else {
|
||||
// @ts-expect-error: Property 'args' does not exist on type 'HelperOptions'. The 'args' property is expected in decorators
|
||||
options.args = this.resolveNodes(node.params);
|
||||
}
|
||||
}
|
||||
|
||||
if (isBlock(node)) {
|
||||
const generateProgramFunction = (program: hbs.AST.Program) => {
|
||||
const prog = (nextContext: any, runtimeOptions: ExtendedRuntimeOptions = {}) => {
|
||||
// inherit data an blockParams from parent program
|
||||
runtimeOptions = Object.assign({}, runtimeOptions);
|
||||
runtimeOptions.data = runtimeOptions.data || this.runtimeOptions!.data;
|
||||
if (runtimeOptions.blockParams) {
|
||||
runtimeOptions.blockParams = runtimeOptions.blockParams.concat(
|
||||
this.runtimeOptions!.blockParams
|
||||
);
|
||||
}
|
||||
|
||||
// stash parent program data
|
||||
const tmpRuntimeOptions = this.runtimeOptions;
|
||||
this.runtimeOptions = runtimeOptions;
|
||||
const shiftContext = nextContext !== this.scopes[0];
|
||||
if (shiftContext) this.scopes.unshift(nextContext);
|
||||
this.blockParamValues.unshift(runtimeOptions.blockParams || []);
|
||||
|
||||
// execute child program
|
||||
const result = this.resolveNodes(program).join('');
|
||||
|
||||
// unstash parent program data
|
||||
this.blockParamValues.shift();
|
||||
if (shiftContext) this.scopes.shift();
|
||||
this.runtimeOptions = tmpRuntimeOptions;
|
||||
|
||||
// return result of child program
|
||||
return result;
|
||||
};
|
||||
prog.blockParams = node.program?.blockParams?.length ?? 0;
|
||||
return prog;
|
||||
};
|
||||
|
||||
options.fn = node.program ? generateProgramFunction(node.program) : noop;
|
||||
options.inverse = node.inverse ? generateProgramFunction(node.inverse) : noop;
|
||||
options.fn = this.generateProgramFunction(node.program);
|
||||
if (node.program) this.processDecorators(node.program, options.fn);
|
||||
options.inverse = this.generateProgramFunction(node.inverse);
|
||||
if (node.inverse) this.processDecorators(node.inverse, options.inverse);
|
||||
}
|
||||
|
||||
return Object.assign(options, this.defaultHelperOptions);
|
||||
}
|
||||
|
||||
private generateProgramFunction(program?: hbs.AST.Program) {
|
||||
if (!program) return noop;
|
||||
|
||||
const prog: Handlebars.TemplateDelegate = (
|
||||
nextContext: any,
|
||||
runtimeOptions: ExtendedRuntimeOptions = {}
|
||||
) => {
|
||||
// inherit data in blockParams from parent program
|
||||
runtimeOptions = Object.assign({}, runtimeOptions);
|
||||
runtimeOptions.data = runtimeOptions.data || this.runtimeOptions!.data;
|
||||
if (runtimeOptions.blockParams) {
|
||||
runtimeOptions.blockParams = runtimeOptions.blockParams.concat(
|
||||
this.runtimeOptions!.blockParams
|
||||
);
|
||||
}
|
||||
|
||||
// stash parent program data
|
||||
const tmpRuntimeOptions = this.runtimeOptions;
|
||||
this.runtimeOptions = runtimeOptions;
|
||||
const shiftContext = nextContext !== this.scopes[0];
|
||||
if (shiftContext) this.scopes.unshift(nextContext);
|
||||
this.blockParamValues.unshift(runtimeOptions.blockParams || []);
|
||||
|
||||
// execute child program
|
||||
const result = this.resolveNodes(program).join('');
|
||||
|
||||
// unstash parent program data
|
||||
this.blockParamValues.shift();
|
||||
if (shiftContext) this.scopes.shift();
|
||||
this.runtimeOptions = tmpRuntimeOptions;
|
||||
|
||||
// return result of child program
|
||||
return result;
|
||||
};
|
||||
|
||||
// @ts-expect-error: Property 'blockParams' does not exist on type 'TemplateDelegate<any>' - The types are too strict
|
||||
prog.blockParams = program.blockParams?.length ?? 0;
|
||||
return prog;
|
||||
}
|
||||
|
||||
private getHash(statement: { hash?: hbs.AST.Hash }) {
|
||||
const result: { [key: string]: any } = {};
|
||||
if (!statement.hash) return result;
|
||||
|
@ -666,6 +776,10 @@ function isBlock(node: hbs.AST.Node): node is hbs.AST.BlockStatement {
|
|||
return 'program' in node || 'inverse' in node;
|
||||
}
|
||||
|
||||
function isDecorator(node: hbs.AST.Node): node is hbs.AST.Decorator | hbs.AST.DecoratorBlock {
|
||||
return node.type === 'Decorator' || node.type === 'DecoratorBlock';
|
||||
}
|
||||
|
||||
function noop() {
|
||||
return '';
|
||||
}
|
||||
|
|
|
@ -3,7 +3,12 @@
|
|||
* See `packages/kbn-handlebars/LICENSE` for more information.
|
||||
*/
|
||||
|
||||
import Handlebars, { ExtendedCompileOptions, ExtendedRuntimeOptions } from '../..';
|
||||
import Handlebars, {
|
||||
type DecoratorFunction,
|
||||
type DecoratorsHash,
|
||||
type ExtendedCompileOptions,
|
||||
type ExtendedRuntimeOptions,
|
||||
} from '../..';
|
||||
|
||||
declare global {
|
||||
var kbnHandlebarsEnv: typeof Handlebars | null; // eslint-disable-line no-var
|
||||
|
@ -25,6 +30,7 @@ class HandlebarsTestBench {
|
|||
private compileOptions?: ExtendedCompileOptions;
|
||||
private runtimeOptions?: ExtendedRuntimeOptions;
|
||||
private helpers: { [key: string]: Handlebars.HelperDelegate | undefined } = {};
|
||||
private decorators: DecoratorsHash = {};
|
||||
private input: any = {};
|
||||
|
||||
constructor(template: string, options: TestOptions = {}) {
|
||||
|
@ -59,6 +65,18 @@ class HandlebarsTestBench {
|
|||
return this;
|
||||
}
|
||||
|
||||
withDecorator(name: string, decoratorFunction: DecoratorFunction) {
|
||||
this.decorators[name] = decoratorFunction;
|
||||
return this;
|
||||
}
|
||||
|
||||
withDecorators(decoratorFunctions: { [key: string]: DecoratorFunction }) {
|
||||
for (const [name, decoratorFunction] of Object.entries(decoratorFunctions)) {
|
||||
this.withDecorator(name, decoratorFunction);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
toCompileTo(outputExpected: string) {
|
||||
const { outputEval, outputAST } = this.compileAndExecute();
|
||||
if (process.env.EVAL) {
|
||||
|
@ -119,6 +137,7 @@ class HandlebarsTestBench {
|
|||
const runtimeOptions: ExtendedRuntimeOptions = Object.assign(
|
||||
{
|
||||
helpers: this.helpers,
|
||||
decorators: this.decorators,
|
||||
},
|
||||
this.runtimeOptions
|
||||
);
|
||||
|
@ -132,6 +151,7 @@ class HandlebarsTestBench {
|
|||
const runtimeOptions: ExtendedRuntimeOptions = Object.assign(
|
||||
{
|
||||
helpers: this.helpers,
|
||||
decorators: this.decorators,
|
||||
},
|
||||
this.runtimeOptions
|
||||
);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* See `packages/kbn-handlebars/LICENSE` for more information.
|
||||
*/
|
||||
|
||||
import Handlebars from '../..';
|
||||
import { expectTemplate } from '../__jest__/test_bench';
|
||||
|
||||
describe('blocks', () => {
|
||||
|
@ -195,4 +196,173 @@ describe('blocks', () => {
|
|||
.toCompileTo('1\n3\n5\nOK.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('decorators', () => {
|
||||
it('should apply mustache decorators', () => {
|
||||
expectTemplate('{{#helper}}{{*decorator}}{{/helper}}')
|
||||
.withHelper('helper', function (options) {
|
||||
return options.fn.run;
|
||||
})
|
||||
.withDecorator('decorator', function (fn) {
|
||||
// @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
|
||||
fn.run = 'success';
|
||||
return fn;
|
||||
})
|
||||
.toCompileTo('success');
|
||||
});
|
||||
|
||||
it('should apply allow undefined return', () => {
|
||||
expectTemplate('{{#helper}}{{*decorator}}suc{{/helper}}')
|
||||
.withHelper('helper', function (options) {
|
||||
return options.fn() + options.fn.run;
|
||||
})
|
||||
.withDecorator('decorator', function (fn) {
|
||||
// @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
|
||||
fn.run = 'cess';
|
||||
})
|
||||
.toCompileTo('success');
|
||||
});
|
||||
|
||||
it('should apply block decorators', () => {
|
||||
expectTemplate('{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}')
|
||||
.withHelper('helper', function (options) {
|
||||
return options.fn.run;
|
||||
})
|
||||
.withDecorator('decorator', function (fn, props, container, options) {
|
||||
// @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
|
||||
fn.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) {
|
||||
return options.fn.run;
|
||||
})
|
||||
.withDecorators({
|
||||
decorator(fn, props, container, options) {
|
||||
// @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
|
||||
fn.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) {
|
||||
return options.fn.run;
|
||||
})
|
||||
.withDecorator('decorator', function (fn, props, container, options) {
|
||||
// @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
|
||||
fn.run = (fn.run || '') + options.fn();
|
||||
return fn;
|
||||
})
|
||||
.toCompileTo('success');
|
||||
});
|
||||
|
||||
it('should access parent variables', () => {
|
||||
expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}')
|
||||
.withHelper('helper', function (options) {
|
||||
return options.fn.run;
|
||||
})
|
||||
.withDecorator('decorator', function (fn, props, container, options) {
|
||||
// @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
|
||||
fn.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();
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue