[@kbn/handlebars] Improve types (#147800)

This commit is contained in:
Thomas Watson 2022-12-22 18:27:56 +01:00 committed by GitHub
parent b697704639
commit 41fd68b719
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 406 additions and 274 deletions

View file

@ -304,123 +304,141 @@
---
> describe('decorators', () => {
> it('should apply mustache decorators', () => {
299c203
299,300c203,204
< .withHelper('helper', function(options) {
< return options.fn.run;
---
> .withHelper('helper', function (options) {
302c206,207
> .withHelper('helper', function (options: Handlebars.HelperOptions) {
> return (options.fn as any).run;
302,303c206,207
< .withDecorator('decorator', function(fn) {
< fn.run = 'success';
---
> .withDecorator('decorator', function (fn) {
> // @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
309c214
> (fn as any).run = 'success';
309c213
< it('should apply allow undefined return', function() {
---
> it('should apply allow undefined return', () => {
311c216
311,312c215,216
< .withHelper('helper', function(options) {
< return options.fn() + options.fn.run;
---
> .withHelper('helper', function (options) {
314c219,220
> .withHelper('helper', function (options: Handlebars.HelperOptions) {
> return options.fn() + (options.fn as any).run;
314,315c218,219
< .withDecorator('decorator', function(fn) {
< fn.run = 'cess';
---
> .withDecorator('decorator', function (fn) {
> // @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
320,324c226,228
> (fn as any).run = 'cess';
320,325c224,227
< 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
> .withHelper('helper', function (options: Handlebars.HelperOptions) {
> return (options.fn as any).run;
327,328c229,230
< .withDecorator('decorator', function(fn, props, container, options) {
< fn.run = options.fn();
---
> .withDecorator('decorator', function (fn, props, container, options) {
> // @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
334c239
> (fn as any).run = options.fn();
334c236
< it('should support nested decorators', function() {
---
> it('should support nested decorators', () => {
338c243
338,339c240,241
< .withHelper('helper', function(options) {
< return options.fn.run;
---
> .withHelper('helper', function (options) {
342c247,248
> .withHelper('helper', function (options: Handlebars.HelperOptions) {
> return (options.fn as any).run;
342,343c244,245
< decorator: function(fn, props, container, options) {
< fn.run = options.fn.nested + options.fn();
---
> decorator(fn, props, container, options) {
> // @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
346c252
> (fn as any).run = options.fn.nested + options.fn();
346c248
< nested: function(fn, props, container, options) {
---
> nested(fn, props, container, options) {
348c254
348c250
< }
---
> },
353c259
353c255
< it('should apply multiple decorators', function() {
---
> it('should apply multiple decorators', () => {
357c263
357,358c259,260
< .withHelper('helper', function(options) {
< return options.fn.run;
---
> .withHelper('helper', function (options) {
360c266,267
> .withHelper('helper', function (options: Handlebars.HelperOptions) {
> return (options.fn as any).run;
360,361c262,263
< .withDecorator('decorator', function(fn, props, container, options) {
< fn.run = (fn.run || '') + options.fn();
---
> .withDecorator('decorator', function (fn, props, container, options) {
> // @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
367c274
> (fn as any).run = ((fn as any).run || '') + options.fn();
367c269
< it('should access parent variables', function() {
---
> it('should access parent variables', () => {
369c276
369,370c271,272
< .withHelper('helper', function(options) {
< return options.fn.run;
---
> .withHelper('helper', function (options) {
372c279,280
> .withHelper('helper', function (options: Handlebars.HelperOptions) {
> return (options.fn as any).run;
372,373c274,275
< .withDecorator('decorator', function(fn, props, container, options) {
< fn.run = options.args;
---
> .withDecorator('decorator', function (fn, props, container, options) {
> // @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
380,381c288,289
> (fn as any).run = options.args;
380,381c282,283
< it('should work with root program', function() {
< var run;
---
> it('should work with root program', () => {
> let run;
383,384c291,292
383,384c285,286
< .withDecorator('decorator', function(fn, props, container, options) {
< equals(options.args[0], 'success');
---
> .withDecorator('decorator', function (fn, props, container, options) {
> expect(options.args[0]).toEqual('success');
390c298
390c292
< equals(run, true);
---
> expect(run).toEqual(true);
393,394c301,302
393,394c295,296
< it('should fail when accessing variables from root', function() {
< var run;
---
> it('should fail when accessing variables from root', () => {
> let run;
396,397c304,305
396,397c298,299
< .withDecorator('decorator', function(fn, props, container, options) {
< equals(options.args[0], undefined);
---
> .withDecorator('decorator', function (fn, props, container, options) {
> expect(options.args[0]).toBeUndefined();
403c311
403c305
< equals(run, true);
---
> expect(run).toEqual(true);
406,408c314,317
406,408c308,311
< describe('registration', function() {
< it('unregisters', function() {
< handlebarsEnv.decorators = {};
@ -429,7 +447,7 @@
> beforeEach(() => {
> global.kbnHandlebarsEnv = Handlebars.create();
> });
410c319,327
410c313,321
< handlebarsEnv.registerDecorator('foo', function() {
---
> afterEach(() => {
@ -441,7 +459,7 @@
> kbnHandlebarsEnv!.decorators = {};
>
> kbnHandlebarsEnv!.registerDecorator('foo', function () {
414,416c331,333
414,416c325,327
< equals(!!handlebarsEnv.decorators.foo, true);
< handlebarsEnv.unregisterDecorator('foo');
< equals(handlebarsEnv.decorators.foo, undefined);
@ -449,14 +467,14 @@
> expect(!!kbnHandlebarsEnv!.decorators.foo).toEqual(true);
> kbnHandlebarsEnv!.unregisterDecorator('foo');
> expect(kbnHandlebarsEnv!.decorators.foo).toBeUndefined();
419,420c336,338
419,420c330,332
< it('allows multiple globals', function() {
< handlebarsEnv.decorators = {};
---
> it('allows multiple globals', () => {
> // @ts-expect-error: Cannot assign to 'decorators' because it is a read-only property.
> kbnHandlebarsEnv!.decorators = {};
422,424c340,343
422,424c334,337
< handlebarsEnv.registerDecorator({
< foo: function() {},
< bar: function() {}
@ -465,7 +483,7 @@
> kbnHandlebarsEnv!.registerDecorator({
> foo() {},
> bar() {},
427,432c346,351
427,432c340,345
< equals(!!handlebarsEnv.decorators.foo, true);
< equals(!!handlebarsEnv.decorators.bar, true);
< handlebarsEnv.unregisterDecorator('foo');
@ -479,7 +497,7 @@
> kbnHandlebarsEnv!.unregisterDecorator('bar');
> expect(kbnHandlebarsEnv!.decorators.foo).toBeUndefined();
> expect(kbnHandlebarsEnv!.decorators.bar).toBeUndefined();
435,445c354,360
435,445c348,354
< it('fails with multiple and args', function() {
< shouldThrow(
< function() {
@ -499,7 +517,7 @@
> {
> world() {
> return 'world!';
447,452c362,368
447,452c356,362
< {}
< );
< },

View file

@ -163,7 +163,7 @@
---
> it('should include the location in the error (row and column)', () => {
> try {
> compile(' \n {{#if}}\n{{/def}}')({});
> compile(' \n {{#if}}\n{{/def}}')();
> expect(true).toEqual(false);
> } catch (err) {
> expect(err.message).toEqual("if doesn't match def - 2:5");
@ -188,7 +188,7 @@
---
> it('should include the location as enumerable property', () => {
> try {
> compile(' \n {{#if}}\n{{/def}}')({});
> compile(' \n {{#if}}\n{{/def}}')();
> expect(true).toEqual(false);
> } catch (err) {
> expect(Object.prototype.propertyIsEnumerable.call(err, 'column')).toEqual(true);
@ -210,7 +210,7 @@
> compile({
> type: 'Program',
> body: [{ type: 'ContentStatement', value: 'Hello' }],
> })({})
> })()
> ).toEqual('Hello');
> });
154,170c64,66
@ -233,7 +233,7 @@
< });
---
> it('can pass through an empty string', () => {
> expect(compile('')({})).toEqual('');
> expect(compile('')()).toEqual('');
> });
172,182c68,75
< it('can utilize AST instance', function() {
@ -251,7 +251,7 @@
> it('should not modify the options.data property(GH-1327)', () => {
> // The `data` property is supposed to be a boolean, but in this test we want to ignore that
> const options = { data: [{ a: 'foo' }, { a: 'bar' }] as unknown as boolean };
> compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)({});
> compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
> expect(JSON.stringify(options, null, 2)).toEqual(
> JSON.stringify({ data: [{ a: 'foo' }, { a: 'bar' }] }, null, 2)
> );
@ -262,7 +262,7 @@
---
> it('should not modify the options.knownHelpers property(GH-1327)', () => {
> const options = { knownHelpers: {} };
> compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)({});
> compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
> expect(JSON.stringify(options, null, 2)).toEqual(
> JSON.stringify({ knownHelpers: {} }, null, 2)
> );

View file

@ -41,7 +41,7 @@
19c35
< .withHelper('raw', function(options) {
---
> .withHelper('raw', function (options) {
> .withHelper('raw', function (options: Handlebars.HelperOptions) {
22d37
< .withMessage('raw block helper gets raw content')
26c41
@ -52,7 +52,7 @@
< .withHelper('raw', function(a, b, c, options) {
< return options.fn() + a + b + c;
---
> .withHelper('raw', function (a, b, c, options) {
> .withHelper('raw', function (a, b, c, options: Handlebars.HelperOptions) {
> const ret = options.fn() + a + b + c;
> return ret;
32d47
@ -66,7 +66,7 @@
39c54
< .withHelper('identity', function(options) {
---
> .withHelper('identity', function (options) {
> .withHelper('identity', function (options: Handlebars.HelperOptions) {
45c60
< it('helper for nested raw block gets raw content', function() {
---
@ -99,7 +99,7 @@
< var byes = ['Goodbye', 'goodbye', 'GOODBYE'];
< for (var i = 0, j = byes.length; i < j; i++) {
---
> .withHelper('goodbyes', function (this: any, options) {
> .withHelper('goodbyes', function (this: any, options: Handlebars.HelperOptions) {
> let out = '';
> const byes = ['Goodbye', 'goodbye', 'GOODBYE'];
> for (let i = 0, j = byes.length; i < j; i++) {
@ -113,7 +113,7 @@
< var byes = ['Goodbye', 'goodbye', 'GOODBYE'];
< for (var i = 0, j = byes.length; i < j; i++) {
---
> .withHelper('goodbyes', function (options) {
> .withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
> let out = '';
> const byes = ['Goodbye', 'goodbye', 'GOODBYE'];
> for (let i = 0, j = byes.length; i < j; i++) {
@ -141,7 +141,7 @@
< '</a>'
< );
---
> .withHelper('link', function (this: any, prefix, options) {
> .withHelper('link', function (this: any, prefix, options: Handlebars.HelperOptions) {
> return '<a href="' + prefix + '/' + this.url + '">' + options.fn(this) + '</a>';
130,133c135,136
< it('helper with complex lookup and nested template in VM+Compiler', function() {
@ -167,7 +167,7 @@
< '</a>'
< );
---
> .withHelper('link', function (this: any, prefix, options) {
> .withHelper('link', function (this: any, prefix, options: Handlebars.HelperOptions) {
> return '<a href="' + prefix + '/' + this.url + '">' + options.fn(this) + '</a>';
152c147
< it('helper returning undefined value', function() {
@ -188,7 +188,7 @@
169c164
< .withHelper('goodbyes', function(options) {
---
> .withHelper('goodbyes', function (options) {
> .withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
172d166
< .withMessage('Block helper executed')
176c170
@ -198,7 +198,7 @@
179c173
< .withHelper('form', function(options) {
---
> .withHelper('form', function (this: any, options) {
> .withHelper('form', function (this: any, options: Handlebars.HelperOptions) {
182d175
< .withMessage('Block helper executed with current context')
186,187c179,180
@ -230,7 +230,7 @@
213c204
< .withHelper('form', function(context, options) {
---
> .withHelper('form', function (context, options) {
> .withHelper('form', function (context, options: Handlebars.HelperOptions) {
216d206
< .withMessage('Context variable resolved')
220c210
@ -240,7 +240,7 @@
223c213
< .withHelper('form', function(context, options) {
---
> .withHelper('form', function (context, options) {
> .withHelper('form', function (context, options: Handlebars.HelperOptions) {
226d215
< .withMessage('Complex path variable resolved')
230,233c219,220
@ -258,11 +258,11 @@
237c224
< .withHelper('link', function(options) {
---
> .withHelper('link', function (this: any, options) {
> .withHelper('link', function (this: any, options: Handlebars.HelperOptions) {
240c227
< .withHelper('form', function(context, options) {
---
> .withHelper('form', function (context, options) {
> .withHelper('form', function (context, options: Handlebars.HelperOptions) {
243d229
< .withMessage('Both blocks executed')
247,249c233,235
@ -598,7 +598,7 @@
539c480
< .withHelper('goodbye', function(cruel, world, options) {
---
> .withHelper('goodbye', function (cruel, world, options) {
> .withHelper('goodbye', function (cruel, world, options: Handlebars.HelperOptions) {
542d482
< .withMessage('block helpers with multiple params')
547,548c487,488
@ -610,7 +610,7 @@
550c490
< .withHelper('goodbye', function(options) {
---
> .withHelper('goodbye', function (options) {
> .withHelper('goodbye', function (options: Handlebars.HelperOptions) {
561d500
< .withMessage('Helper output hash')
565,566c504,505
@ -630,7 +630,7 @@
589c526
< .withHelper('goodbye', function(options) {
---
> .withHelper('goodbye', function (this: any, options) {
> .withHelper('goodbye', function (this: any, options: Handlebars.HelperOptions) {
600d536
< .withMessage('Hash parameters output')
604c540
@ -640,7 +640,7 @@
606c542
< .withHelper('goodbye', function(options) {
---
> .withHelper('goodbye', function (this: any, options) {
> .withHelper('goodbye', function (this: any, options: Handlebars.HelperOptions) {
617d552
< .withMessage('Hash parameters output')
621,622c556,557
@ -670,7 +670,7 @@
654c585
< .withHelper('helperMissing', function(mesg, options) {
---
> .withHelper('helperMissing', function (mesg, options) {
> .withHelper('helperMissing', function (mesg, options: Handlebars.HelperOptions) {
662c593
< it('if a value is not found, custom helperMissing is used', function() {
---
@ -678,7 +678,7 @@
665c596
< .withHelper('helperMissing', function(options) {
---
> .withHelper('helperMissing', function (options) {
> .withHelper('helperMissing', function (options: Handlebars.HelperOptions) {
674,675c605,606
< describe('knownHelpers', function() {
< it('Known helper should render helper', function() {
@ -893,7 +893,7 @@
869c791
< .withHelper('goodbye', function(options) {
---
> .withHelper('goodbye', function (this: any, options) {
> .withHelper('goodbye', function (this: any, options: Handlebars.HelperOptions) {
872c794
< .withHelper('cruel', function(world) {
---
@ -931,7 +931,7 @@
---
> it('Scoped names take precedence over block helpers', () => {
> expectTemplate('{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}')
> .withHelper('goodbye', function (this: any, options) {
> .withHelper('goodbye', function (this: any, options: Handlebars.HelperOptions) {
906c824
< .withHelper('cruel', function(world) {
---
@ -952,7 +952,7 @@
< .withHelper('goodbyes', function(options) {
< equals(options.fn.blockParams, 1);
---
> .withHelper('goodbyes', function (options) {
> .withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
> expect(options.fn.blockParams).toEqual(1);
929c846
< it('should take presedence over helper values', function() {
@ -966,7 +966,7 @@
< .withHelper('goodbyes', function(options) {
< equals(options.fn.blockParams, 1);
---
> .withHelper('goodbyes', function (options) {
> .withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
> expect(options.fn.blockParams).toEqual(1);
941,944c858,859
< it('should not take presedence over pathed values', function() {
@ -984,7 +984,7 @@
< .withHelper('goodbyes', function(options) {
< equals(options.fn.blockParams, 1);
---
> .withHelper('goodbyes', function (this: any, options) {
> .withHelper('goodbyes', function (this: any, options: Handlebars.HelperOptions) {
> expect(options.fn.blockParams).toEqual(1);
956,957c871,872
< it('should take presednece over parent block params', function() {
@ -1004,7 +1004,7 @@
962c882
< .withHelper('goodbyes', function(options) {
---
> .withHelper('goodbyes', function (options) {
> .withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
966,967c886
< blockParams:
< options.fn.blockParams === 1 ? [value++, value++] : undefined
@ -1022,7 +1022,7 @@
< .withHelper('goodbyes', function(options) {
< equals(options.fn.blockParams, 1);
---
> .withHelper('goodbyes', function (options) {
> .withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
> expect(options.fn.blockParams).toEqual(1);
987,991c904,906
< describe('built-in helpers malformed arguments ', function() {
@ -1088,11 +1088,18 @@
---
> describe('the lookupProperty-option', () => {
> it('should be passed to custom helpers', () => {
1040c945
1040,1042c945,950
< .withHelper('testHelper', function testHelper(options) {
< return options.lookupProperty(this, 'testProperty');
< })
---
> .withHelper('testHelper', function testHelper(this: any, options) {
1047a953,958
> .withHelper(
> 'testHelper',
> function testHelper(this: any, options: Handlebars.HelperOptions) {
> return options.lookupProperty(this, 'testProperty');
> }
> )
1047a956,961
>
> function deleteAllKeys(obj: { [key: string]: any }) {
> for (const key of Object.keys(obj)) {

View file

@ -1,4 +1,4 @@
1,2c1,11
1,2c1,12
< describe('Regressions', function() {
< it('GH-94: Cannot read property of undefined', function() {
---
@ -9,17 +9,18 @@
> * See `packages/kbn-handlebars/LICENSE` for more information.
> */
>
> import Handlebars from '../..';
> import { expectTemplate } from '../__jest__/test_bench';
>
> describe('Regressions', () => {
> it('GH-94: Cannot read property of undefined', () => {
9,10c18,19
9,10c19,20
< name: 'Charles Darwin'
< }
---
> name: 'Charles Darwin',
> },
13,15c22,24
13,15c23,25
< title: 'Lazarillo de Tormes'
< }
< ]
@ -27,9 +28,9 @@
> title: 'Lazarillo de Tormes',
> },
> ],
17d25
17d26
< .withMessage('Renders without an undefined property error')
21,43c29,34
21,43c30,35
< it("GH-150: Inverted sections print when they shouldn't", function() {
< var string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}';
<
@ -60,47 +61,47 @@
> expectTemplate(string).withInput({ set: undefined }).toCompileTo('not set :: ');
> expectTemplate(string).withInput({ set: false }).toCompileTo('not set :: ');
> expectTemplate(string).withInput({ set: true }).toCompileTo(' :: set');
46c37
46c38
< it('GH-158: Using array index twice, breaks the template', function() {
---
> it('GH-158: Using array index twice, breaks the template', () => {
49d39
49d40
< .withMessage('it works as expected')
53,54c43,44
53,54c44,45
< it("bug reported by @fat where lambdas weren't being properly resolved", function() {
< var string =
---
> it("bug reported by @fat where lambdas weren't being properly resolved", () => {
> const string =
69,70c59,60
69,70c60,61
< var data = {
< thing: function() {
---
> const data = {
> thing() {
76c66
76c67
< { className: 'three', word: '@sayrer' }
---
> { className: 'three', word: '@sayrer' },
78c68
78c69
< hasThings: function() {
---
> hasThings() {
80c70
80c71
< }
---
> },
83c73
83c74
< var output =
---
> const output =
92,94c82
92,94c83
< expectTemplate(string)
< .withInput(data)
< .toCompileTo(output);
---
> expectTemplate(string).withInput(data).toCompileTo(output);
97,100c85,86
97,100c86,87
< it('GH-408: Multiple loops fail', function() {
< expectTemplate(
< '{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}'
@ -108,37 +109,37 @@
---
> it('GH-408: Multiple loops fail', () => {
> expectTemplate('{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}')
103c89
103c90
< { name: 'Jane Doe', location: { city: 'New York' } }
---
> { name: 'Jane Doe', location: { city: 'New York' } },
105d90
105d91
< .withMessage('It should output multiple times')
109,110c94,95
109,110c95,96
< it('GS-428: Nested if else rendering', function() {
< var succeedingTemplate =
---
> it('GS-428: Nested if else rendering', () => {
> const succeedingTemplate =
112c97
112c98
< var failingTemplate =
---
> const failingTemplate =
115,116c100,101
115,116c101,102
< var helpers = {
< blk: function(block) {
---
> const helpers = {
> blk(block: Handlebars.HelperOptions) {
119c104
119c105
< inverse: function(block) {
---
> inverse(block: Handlebars.HelperOptions) {
121c106
121c107
< }
---
> },
124,130c109,110
124,130c110,111
< expectTemplate(succeedingTemplate)
< .withHelpers(helpers)
< .toCompileTo(' Expected ');
@ -149,7 +150,7 @@
---
> expectTemplate(succeedingTemplate).withHelpers(helpers).toCompileTo(' Expected ');
> expectTemplate(failingTemplate).withHelpers(helpers).toCompileTo(' Expected ');
133,136c113,114
133,136c114,115
< it('GH-458: Scoped this identifier', function() {
< expectTemplate('{{./foo}}')
< .withInput({ foo: 'bar' })
@ -157,25 +158,25 @@
---
> it('GH-458: Scoped this identifier', () => {
> expectTemplate('{{./foo}}').withInput({ foo: 'bar' }).toCompileTo('bar');
139c117
139c118
< it('GH-375: Unicode line terminators', function() {
---
> it('GH-375: Unicode line terminators', () => {
143c121
143c122
< it('GH-534: Object prototype aliases', function() {
---
> it('GH-534: Object prototype aliases', () => {
144a123
144a124
> // @ts-expect-error
147,149c126
147,149c127
< expectTemplate('{{foo}}')
< .withInput({ foo: 'bar' })
< .toCompileTo('bar');
---
> expectTemplate('{{foo}}').withInput({ foo: 'bar' }).toCompileTo('bar');
150a128
150a129
> // @ts-expect-error
155,157c133,135
155,157c134,136
< it('GH-437: Matching escaping', function() {
< expectTemplate('{{{a}}').toThrow(Error, /Parse error on/);
< expectTemplate('{{a}}}').toThrow(Error, /Parse error on/);
@ -183,7 +184,7 @@
> it('GH-437: Matching escaping', () => {
> expectTemplate('{{{a}}').toThrow(/Parse error on/);
> expectTemplate('{{a}}}').toThrow(/Parse error on/);
160,166c138,140
160,166c139,141
< it('GH-676: Using array in escaping mustache fails', function() {
< var data = { arr: [1, 2] };
<
@ -195,30 +196,30 @@
> it('GH-676: Using array in escaping mustache fails', () => {
> const data = { arr: [1, 2] };
> expectTemplate('{{arr}}').withInput(data).toCompileTo(data.arr.toString());
169c143
169c144
< it('Mustache man page', function() {
---
> it('Mustache man page', () => {
177c151
177c152
< in_ca: true
---
> in_ca: true,
179,182c153
179,182c154
< .withMessage('the hello world mustache example works')
< .toCompileTo(
< 'Hello Chris. You have just won $10000! Well, $6000, after taxes.'
< );
---
> .toCompileTo('Hello Chris. You have just won $10000! Well, $6000, after taxes.');
185c156
185c157
< it('GH-731: zero context rendering', function() {
---
> it('GH-731: zero context rendering', () => {
189c160
189c161
< bar: 'OK'
---
> bar: 'OK',
194,197c165,166
194,197c166,167
< it('GH-820: zero pathed rendering', function() {
< expectTemplate('{{foo.bar}}')
< .withInput({ foo: 0 })
@ -226,37 +227,37 @@
---
> it('GH-820: zero pathed rendering', () => {
> expectTemplate('{{foo.bar}}').withInput({ foo: 0 }).toCompileTo('');
200c169
200c170
< it('GH-837: undefined values for helpers', function() {
---
> it('GH-837: undefined values for helpers', () => {
203c172
203c173
< str: function(value) {
---
> str(value) {
205c174
205c175
< }
---
> },
210c179
210c180
< it('GH-926: Depths and de-dupe', function() {
---
> it('GH-926: Depths and de-dupe', () => {
217c186
217c187
< notData: [1]
---
> notData: [1],
222c191
222c192
< it('GH-1021: Each empty string key', function() {
---
> it('GH-1021: Each empty string key', () => {
228,229c197,198
228,229c198,199
< value: 10000
< }
---
> value: 10000,
> },
234,248c203,204
234,248c204,205
< it('GH-1054: Should handle simple safe string responses', function() {
< expectTemplate('{{#wrap}}{{>partial}}{{/wrap}}')
< .withHelpers({
@ -275,27 +276,27 @@
---
> it('GH-1065: Sparse arrays', () => {
> const array = [];
252c208
252c209
< .withInput({ array: array })
---
> .withInput({ array })
256c212
256c213
< it('GH-1093: Undefined helper context', function() {
---
> it('GH-1093: Undefined helper context', () => {
260c216
260c217
< helper: function() {
---
> helper(this: any) {
263c219
263c220
< for (var name in this) {
---
> for (const name in this) {
270c226
270c227
< }
---
> },
275,306c231
275,306c232
< it('should support multiple levels of inline partials', function() {
< expectTemplate(
< '{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}'
@ -330,15 +331,15 @@
< it('GH-1135 : Context handling within each iteration', function() {
---
> it('GH-1135 : Context handling within each iteration', () => {
315c240
315c241
< myif: function(conditional, options) {
---
> myif(conditional, options) {
321c246
> myif(conditional, options: Handlebars.HelperOptions) {
321c247
< }
---
> },
326,343c251,252
326,343c252,253
< it('GH-1186: Support block params for existing programs', function() {
< expectTemplate(
< '{{#*inline "test"}}{{> @partial-block }}{{/inline}}' +
@ -360,13 +361,13 @@
---
> it('GH-1319: "unless" breaks when "each" value equals "null"', () => {
> expectTemplate('{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}')
346c255
346c256
< list: [null, 'a']
---
> list: [null, 'a'],
348d256
348d257
< .withMessage('')
352,457c260
352,457c261
< it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function() {
< expectTemplate('template {{>partial}} template')
< .withPartials({
@ -475,15 +476,15 @@
< it('should allow hash with protected array names', function() {
---
> it('should allow hash with protected array names', () => {
461c264
461c265
< helpa: function(options) {
---
> helpa(options) {
463c266
> helpa(options: Handlebars.HelperOptions) {
463c267
< }
---
> },
468,496c271,272
468,496c272,273
< describe('GH-1598: Performance degradation for partials since v4.3.0', function() {
< // Do not run test for runs without compiler
< if (!Handlebars.compile) {

View file

@ -98,9 +98,17 @@ describe('helpers', () => {
expect(toHaveProperties.calls).toEqual(blockTemplates.length * 2 * factor);
});
it('should pass expected "this" and arguments to helper functions', () => {
it('should pass expected "this" to helper functions (without input)', () => {
expectTemplate('{{hello "world" 12 true false}}')
.withHelper('hello', function (this: any, ...args) {
.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 [
@ -115,6 +123,19 @@ describe('helpers', () => {
],
}
`);
})
.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",
@ -161,6 +182,58 @@ describe('helpers', () => {
})
.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
@ -190,11 +263,11 @@ describe('blocks', () => {
const render = compile('{{*decorator}}');
let calls = 0;
expect(render({})).toEqual('');
expect(render()).toEqual('');
expect(calls).toEqual(1);
calls = 0;
expect(render({})).toEqual('');
expect(render()).toEqual('');
expect(calls).toEqual(1);
global.kbnHandlebarsEnv = null;

View file

@ -3,8 +3,8 @@
* See `packages/kbn-handlebars/LICENSE` for more information.
*/
// The handlebars module uses `export =`, so we should technically use `import OriginalHandlebars = require('handlebars')`, but Babel will not allow this.
import OriginalHandlebars from 'handlebars';
// The handlebars module uses `export =`, so we should technically use `import Handlebars = require('handlebars')`, but Babel will not allow this.
import Handlebars from 'handlebars';
import {
createProtoAccessControl,
resultIsAllowed,
@ -17,20 +17,59 @@ import { indexOf, createFrame } from 'handlebars/dist/cjs/handlebars/utils';
// @ts-expect-error: Could not find a declaration file for module
import { moveHelperToHooks } from 'handlebars/dist/cjs/handlebars/helpers';
const originalCreate = OriginalHandlebars.create;
const originalCreate = Handlebars.create;
/**
* A custom version of the Handlesbars module with an extra `compileAST` function.
* A custom version of the Handlesbars module with an extra `compileAST` function and fixed typings.
*/
const Handlebars: typeof ExtendedHandlebars & typeof OriginalHandlebars = OriginalHandlebars as any;
declare module 'handlebars' {
export function compileAST(
input: string | hbs.AST.Program,
options?: ExtendedCompileOptions
): (context?: any, options?: ExtendedRuntimeOptions) => string;
// --------------------------------------------------------
// Override/Extend inherited types below that are incorrect
// --------------------------------------------------------
export interface TemplateDelegate<T = any> {
(context?: T, options?: RuntimeOptions): string; // Override to ensure `context` is optional
blockParams?: number; // TODO: Can this really be optional?
}
export interface HelperOptions {
name: string;
loc: { start: hbs.AST.SourceLocation['start']; end: hbs.AST.SourceLocation['end'] };
lookupProperty: LookupProperty;
}
export interface HelperDelegate {
// eslint-disable-next-line @typescript-eslint/prefer-function-type
(...params: any[]): any;
}
}
const kHelper = Symbol('helper');
const kAmbiguous = Symbol('ambiguous');
const kSimple = Symbol('simple');
type NodeType = typeof kHelper | typeof kAmbiguous | typeof kSimple;
type ProcessableNode = hbs.AST.MustacheStatement | hbs.AST.BlockStatement | hbs.AST.SubExpression;
type LookupProperty = <T = any>(parent: { [name: string]: any }, propertyName: string) => T;
type ProcessableStatementNode = hbs.AST.MustacheStatement | hbs.AST.SubExpression;
type ProcessableBlockStatementNode = hbs.AST.BlockStatement | hbs.AST.PartialBlockStatement;
type ProcessableNode = ProcessableStatementNode | ProcessableBlockStatementNode;
type ProcessableNodeWithPathParts = ProcessableNode & { path: hbs.AST.PathExpression };
type ProcessableNodeWithPathPartsOrLiteral = ProcessableNode & {
path: hbs.AST.PathExpression | hbs.AST.Literal;
};
export type NonBlockHelperOptions = Omit<Handlebars.HelperOptions, 'fn' | 'inverse'>;
export type AmbiguousHelperOptions = Handlebars.HelperOptions | NonBlockHelperOptions;
export interface DecoratorOptions extends Omit<Handlebars.HelperOptions, 'lookupProperties'> {
args?: any[];
}
/**
* If the `unsafe-eval` CSP is set, this string constant will be `compile`,
@ -85,19 +124,6 @@ export interface DecoratorsHash {
[name: string]: DecoratorFunction;
}
/**
* Normally this namespace isn't used directly. It's required to be present by
* TypeScript when calling the `Handlebars.create()` function.
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
export declare namespace ExtendedHandlebars {
export function compileAST(
input: string | hbs.AST.Program,
options?: ExtendedCompileOptions
): (context: any, options?: ExtendedRuntimeOptions) => string;
export function create(): typeof Handlebars; // eslint-disable-line @typescript-eslint/no-shadow
}
// The handlebars module uses `export =`, so it can't be re-exported using `export *`.
// However, because of Babel, we're not allowed to use `export =` ourselves.
// So we have to resort to using `exports default` even though eslint doesn't like it.
@ -151,7 +177,7 @@ interface Container {
helpers: HelpersHash;
decorators: DecoratorsHash;
strict: (obj: { [name: string]: any }, name: string, loc: hbs.AST.SourceLocation) => any;
lookupProperty: <T = any>(parent: { [name: string]: any }, propertyName: string) => T;
lookupProperty: LookupProperty;
lambda: (current: any, context: any) => any;
data: (value: any, depth: number) => any;
hooks: {
@ -172,8 +198,7 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
private blockParamValues: any[][] = [];
private ast?: hbs.AST.Program;
private container: Container;
// @ts-expect-error
private defaultHelperOptions: Handlebars.HelperOptions = {};
private defaultHelperOptions: Pick<NonBlockHelperOptions, 'lookupProperty'>;
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
@ -257,8 +282,9 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
hooks: {},
});
// @ts-expect-error
this.defaultHelperOptions.lookupProperty = container.lookupProperty;
this.defaultHelperOptions = {
lookupProperty: container.lookupProperty,
};
}
render(context: any, options: ExtendedRuntimeOptions = {}): string {
@ -397,16 +423,15 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
const result = this.container.lookupProperty<DecoratorFunction>(
this.container.decorators,
// @ts-expect-error: Property 'name' does not exist on type 'HelperOptions' - The types are wrong
options.name
)(prog, props, this.container, options);
Object.assign(result || prog, props);
}
private processStatementOrExpression(node: ProcessableNode) {
private processStatementOrExpression(node: ProcessableNodeWithPathPartsOrLiteral) {
// Calling `transformLiteralToPath` has side-effects!
// It converts a node from type `ProcessableNode` to `ProcessableNodeWithPathParts`
// It converts a node from type `ProcessableNodeWithPathPartsOrLiteral` to `ProcessableNodeWithPathParts`
transformLiteralToPath(node);
switch (this.classifyNode(node as ProcessableNodeWithPathParts)) {
@ -534,7 +559,7 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
const name = node.path.parts[0];
const helper = this.setupHelper(node, name);
// TypeScript: `helper.fn` might be `undefined` at this point, but to match the upstream behavior we call it without any guards
const result = helper.fn.apply(helper.context, helper.params);
const result = helper.fn!.call(helper.context, ...helper.params, helper.options);
this.output.push(result);
}
@ -560,7 +585,7 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
}
// TypeScript: `helper.fn` might be `undefined` at this point, but to match the upstream behavior we call it without any guards
const result = helper.fn.apply(helper.context, helper.params);
const result = helper.fn!.call(helper.context, ...helper.params, helper.options);
this.output.push(result);
}
@ -613,7 +638,7 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
}
return typeof helper.fn === 'function'
? helper.fn.apply(helper.context, helper.params)
? helper.fn.call(helper.context, ...helper.params, helper.options)
: helper.fn;
}
@ -623,70 +648,74 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
if (!helper.fn) {
const context = this.scopes[0];
const options = helper.params[helper.params.length - 1];
value = this.container.hooks.blockHelperMissing!.call(context, value, options);
value = this.container.hooks.blockHelperMissing!.call(context, value, helper.options);
}
return value;
}
private setupHelper(node: ProcessableNodeWithPathParts, helperName: string) {
private setupHelper(
node: ProcessableNode,
helperName: string
): {
fn?: Handlebars.HelperDelegate;
context: any[];
params: any[];
options: AmbiguousHelperOptions;
} {
return {
fn: this.container.lookupProperty(this.container.helpers, helperName),
context: this.scopes[0],
params: [...this.resolveNodes(node.params), this.setupParams(node, helperName)],
params: this.resolveNodes(node.params),
options: this.setupParams(node, helperName),
};
}
private setupDecoratorOptions(
decorator: hbs.AST.Decorator | hbs.AST.DecoratorBlock
): Handlebars.HelperOptions {
private setupDecoratorOptions(decorator: hbs.AST.Decorator | hbs.AST.DecoratorBlock) {
// 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 options = this.setupParams(decorator as hbs.AST.DecoratorBlock, name);
const options = toDecoratorOptions(this.setupParams(decorator, name));
if (decorator.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(decorator.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(decorator.params);
}
}
// @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
return options;
}
private setupParams(node: ProcessableBlockStatementNode, name: string): Handlebars.HelperOptions;
private setupParams(node: ProcessableStatementNode, name: string): NonBlockHelperOptions;
private setupParams(node: ProcessableNode, name: string): AmbiguousHelperOptions;
private setupParams(node: ProcessableNode, name: string): AmbiguousHelperOptions {
const options = {
name,
hash: this.getHash(node),
data: this.runtimeOptions!.data,
loc: { start: node.loc.start, end: node.loc.end },
...this.defaultHelperOptions,
};
if (isBlock(node)) {
// TODO: Is there a way in TypeScript to infer that `options` is `Handlebars.HelperOptions` inside this if-statement. If not, is there a way to just cast once?
(options as Handlebars.HelperOptions).fn = this.generateProgramFunction(node.program);
if (node.program)
this.processDecorators(node.program, (options as Handlebars.HelperOptions).fn);
(options as Handlebars.HelperOptions).inverse = this.generateProgramFunction(node.inverse);
if (node.inverse)
this.processDecorators(node.inverse, (options as Handlebars.HelperOptions).inverse);
}
return options;
}
private setupParams(
node: ProcessableNodeWithPathParts,
helperName: string
): Handlebars.HelperOptions {
const options: Handlebars.HelperOptions = {
// @ts-expect-error: Name should be on there, but the offical types doesn't know this
name: helperName,
hash: this.getHash(node),
data: this.runtimeOptions!.data,
loc: { start: node.loc.start, end: node.loc.end },
};
if (isBlock(node)) {
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) {
private generateProgramFunction(program: hbs.AST.Program) {
if (!program) return noop;
const prog: Handlebars.TemplateDelegate = (
@ -722,7 +751,6 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor {
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;
}
@ -795,6 +823,13 @@ function isDecorator(node: hbs.AST.Node): node is hbs.AST.Decorator | hbs.AST.De
return node.type === 'Decorator' || node.type === 'DecoratorBlock';
}
function toDecoratorOptions(options: AmbiguousHelperOptions) {
// There's really no tests/documentation on this, but to match the upstream codebase we'll remove `lookupProperty` from the decorator context
delete (options as any).lookupProperty;
return options as DecoratorOptions;
}
function noop() {
return '';
}

View file

@ -64,12 +64,12 @@ class HandlebarsTestBench {
return this;
}
withHelper(name: string, helper?: Handlebars.HelperDelegate) {
withHelper<F extends Handlebars.HelperDelegate>(name: string, helper?: F) {
this.helpers[name] = helper;
return this;
}
withHelpers(helperFunctions: { [name: string]: Handlebars.HelperDelegate }) {
withHelpers<F extends Handlebars.HelperDelegate>(helperFunctions: { [name: string]: F }) {
for (const [name, helper] of Object.entries(helperFunctions)) {
this.withHelper(name, helper);
}

View file

@ -200,12 +200,11 @@ describe('blocks', () => {
describe('decorators', () => {
it('should apply mustache decorators', () => {
expectTemplate('{{#helper}}{{*decorator}}{{/helper}}')
.withHelper('helper', function (options) {
return options.fn.run;
.withHelper('helper', function (options: Handlebars.HelperOptions) {
return (options.fn as any).run;
})
.withDecorator('decorator', function (fn) {
// @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
fn.run = 'success';
(fn as any).run = 'success';
return fn;
})
.toCompileTo('success');
@ -213,24 +212,22 @@ describe('blocks', () => {
it('should apply allow undefined return', () => {
expectTemplate('{{#helper}}{{*decorator}}suc{{/helper}}')
.withHelper('helper', function (options) {
return options.fn() + options.fn.run;
.withHelper('helper', function (options: Handlebars.HelperOptions) {
return options.fn() + (options.fn as any).run;
})
.withDecorator('decorator', function (fn) {
// @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
fn.run = 'cess';
(fn as any).run = 'cess';
})
.toCompileTo('success');
});
it('should apply block decorators', () => {
expectTemplate('{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}')
.withHelper('helper', function (options) {
return options.fn.run;
.withHelper('helper', function (options: Handlebars.HelperOptions) {
return (options.fn as any).run;
})
.withDecorator('decorator', function (fn, props, container, options) {
// @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
fn.run = options.fn();
(fn as any).run = options.fn();
return fn;
})
.toCompileTo('success');
@ -240,13 +237,12 @@ describe('blocks', () => {
expectTemplate(
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}'
)
.withHelper('helper', function (options) {
return options.fn.run;
.withHelper('helper', function (options: Handlebars.HelperOptions) {
return (options.fn as any).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();
(fn as any).run = options.fn.nested + options.fn();
return fn;
},
nested(fn, props, container, options) {
@ -260,12 +256,11 @@ describe('blocks', () => {
expectTemplate(
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}'
)
.withHelper('helper', function (options) {
return options.fn.run;
.withHelper('helper', function (options: Handlebars.HelperOptions) {
return (options.fn as any).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();
(fn as any).run = ((fn as any).run || '') + options.fn();
return fn;
})
.toCompileTo('success');
@ -273,12 +268,11 @@ describe('blocks', () => {
it('should access parent variables', () => {
expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}')
.withHelper('helper', function (options) {
return options.fn.run;
.withHelper('helper', function (options: Handlebars.HelperOptions) {
return (options.fn as any).run;
})
.withDecorator('decorator', function (fn, props, container, options) {
// @ts-expect-error: Property 'run' does not exist on type 'TemplateDelegate<any>'
fn.run = options.args;
(fn as any).run = options.args;
return fn;
})
.withInput({ foo: 'success' })

View file

@ -29,7 +29,7 @@ describe('compiler', () => {
it('should include the location in the error (row and column)', () => {
try {
compile(' \n {{#if}}\n{{/def}}')({});
compile(' \n {{#if}}\n{{/def}}')();
expect(true).toEqual(false);
} catch (err) {
expect(err.message).toEqual("if doesn't match def - 2:5");
@ -45,7 +45,7 @@ describe('compiler', () => {
it('should include the location as enumerable property', () => {
try {
compile(' \n {{#if}}\n{{/def}}')({});
compile(' \n {{#if}}\n{{/def}}')();
expect(true).toEqual(false);
} catch (err) {
expect(Object.prototype.propertyIsEnumerable.call(err, 'column')).toEqual(true);
@ -57,18 +57,18 @@ describe('compiler', () => {
compile({
type: 'Program',
body: [{ type: 'ContentStatement', value: 'Hello' }],
})({})
})()
).toEqual('Hello');
});
it('can pass through an empty string', () => {
expect(compile('')({})).toEqual('');
expect(compile('')()).toEqual('');
});
it('should not modify the options.data property(GH-1327)', () => {
// The `data` property is supposed to be a boolean, but in this test we want to ignore that
const options = { data: [{ a: 'foo' }, { a: 'bar' }] as unknown as boolean };
compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)({});
compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
expect(JSON.stringify(options, null, 2)).toEqual(
JSON.stringify({ data: [{ a: 'foo' }, { a: 'bar' }] }, null, 2)
);
@ -76,7 +76,7 @@ describe('compiler', () => {
it('should not modify the options.knownHelpers property(GH-1327)', () => {
const options = { knownHelpers: {} };
compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)({});
compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
expect(JSON.stringify(options, null, 2)).toEqual(
JSON.stringify({ knownHelpers: {} }, null, 2)
);

View file

@ -32,7 +32,7 @@ describe('helpers', () => {
it('helper for raw block gets raw content', () => {
expectTemplate('{{{{raw}}}} {{test}} {{{{/raw}}}}')
.withInput({ test: 'hello' })
.withHelper('raw', function (options) {
.withHelper('raw', function (options: Handlebars.HelperOptions) {
return options.fn();
})
.toCompileTo(' {{test}} ');
@ -41,7 +41,7 @@ describe('helpers', () => {
it('helper for raw block gets parameters', () => {
expectTemplate('{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}')
.withInput({ test: 'hello' })
.withHelper('raw', function (a, b, c, options) {
.withHelper('raw', function (a, b, c, options: Handlebars.HelperOptions) {
const ret = options.fn() + a + b + c;
return ret;
})
@ -51,7 +51,7 @@ describe('helpers', () => {
describe('raw block parsing (with identity helper-function)', () => {
function runWithIdentityHelper(template: string, expected: string) {
expectTemplate(template)
.withHelper('identity', function (options) {
.withHelper('identity', function (options: Handlebars.HelperOptions) {
return options.fn();
})
.toCompileTo(expected);
@ -95,7 +95,7 @@ describe('helpers', () => {
it('helper block with identical context', () => {
expectTemplate('{{#goodbyes}}{{name}}{{/goodbyes}}')
.withInput({ name: 'Alan' })
.withHelper('goodbyes', function (this: any, options) {
.withHelper('goodbyes', function (this: any, options: Handlebars.HelperOptions) {
let out = '';
const byes = ['Goodbye', 'goodbye', 'GOODBYE'];
for (let i = 0, j = byes.length; i < j; i++) {
@ -109,7 +109,7 @@ describe('helpers', () => {
it('helper block with complex lookup expression', () => {
expectTemplate('{{#goodbyes}}{{../name}}{{/goodbyes}}')
.withInput({ name: 'Alan' })
.withHelper('goodbyes', function (options) {
.withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
let out = '';
const byes = ['Goodbye', 'goodbye', 'GOODBYE'];
for (let i = 0, j = byes.length; i < j; i++) {
@ -126,7 +126,7 @@ describe('helpers', () => {
prefix: '/root',
goodbyes: [{ text: 'Goodbye', url: 'goodbye' }],
})
.withHelper('link', function (this: any, prefix, options) {
.withHelper('link', function (this: any, prefix, options: Handlebars.HelperOptions) {
return '<a href="' + prefix + '/' + this.url + '">' + options.fn(this) + '</a>';
})
.toCompileTo('<a href="/root/goodbye">Goodbye</a>');
@ -138,7 +138,7 @@ describe('helpers', () => {
prefix: '/root',
goodbyes: [{ text: 'Goodbye', url: 'goodbye' }],
})
.withHelper('link', function (this: any, prefix, options) {
.withHelper('link', function (this: any, prefix, options: Handlebars.HelperOptions) {
return '<a href="' + prefix + '/' + this.url + '">' + options.fn(this) + '</a>';
})
.toCompileTo('<a href="/root/goodbye">Goodbye</a>');
@ -161,7 +161,7 @@ describe('helpers', () => {
it('block helper', () => {
expectTemplate('{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!')
.withInput({ world: 'world' })
.withHelper('goodbyes', function (options) {
.withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
return options.fn({ text: 'GOODBYE' });
})
.toCompileTo('GOODBYE! cruel world!');
@ -170,7 +170,7 @@ describe('helpers', () => {
it('block helper staying in the same context', () => {
expectTemplate('{{#form}}<p>{{name}}</p>{{/form}}')
.withInput({ name: 'Yehuda' })
.withHelper('form', function (this: any, options) {
.withHelper('form', function (this: any, options: Handlebars.HelperOptions) {
return '<form>' + options.fn(this) + '</form>';
})
.toCompileTo('<form><p>Yehuda</p></form>');
@ -201,7 +201,7 @@ describe('helpers', () => {
it('block helper passing a new context', () => {
expectTemplate('{{#form yehuda}}<p>{{name}}</p>{{/form}}')
.withInput({ yehuda: { name: 'Yehuda' } })
.withHelper('form', function (context, options) {
.withHelper('form', function (context, options: Handlebars.HelperOptions) {
return '<form>' + options.fn(context) + '</form>';
})
.toCompileTo('<form><p>Yehuda</p></form>');
@ -210,7 +210,7 @@ describe('helpers', () => {
it('block helper passing a complex path context', () => {
expectTemplate('{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}')
.withInput({ yehuda: { name: 'Yehuda', cat: { name: 'Harold' } } })
.withHelper('form', function (context, options) {
.withHelper('form', function (context, options: Handlebars.HelperOptions) {
return '<form>' + options.fn(context) + '</form>';
})
.toCompileTo('<form><p>Harold</p></form>');
@ -221,10 +221,10 @@ describe('helpers', () => {
.withInput({
yehuda: { name: 'Yehuda' },
})
.withHelper('link', function (this: any, options) {
.withHelper('link', function (this: any, options: Handlebars.HelperOptions) {
return '<a href="' + this.name + '">' + options.fn(this) + '</a>';
})
.withHelper('form', function (context, options) {
.withHelper('form', function (context, options: Handlebars.HelperOptions) {
return '<form>' + options.fn(context) + '</form>';
})
.toCompileTo('<form><p>Yehuda</p><a href="Yehuda">Hello</a></form>');
@ -477,7 +477,7 @@ describe('helpers', () => {
it('block multi-params work', () => {
expectTemplate('Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}')
.withInput({ cruel: 'cruel', world: 'world' })
.withHelper('goodbye', function (cruel, world, options) {
.withHelper('goodbye', function (cruel, world, options: Handlebars.HelperOptions) {
return options.fn({ greeting: 'Goodbye', adj: cruel, noun: world });
})
.toCompileTo('Message: Goodbye cruel world');
@ -487,7 +487,7 @@ describe('helpers', () => {
describe('hash', () => {
it('helpers can take an optional hash', () => {
expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" times=12}}')
.withHelper('goodbye', function (options) {
.withHelper('goodbye', function (options: Handlebars.HelperOptions) {
return (
'GOODBYE ' +
options.hash.cruel +
@ -523,7 +523,7 @@ describe('helpers', () => {
it('block helpers can take an optional hash', () => {
expectTemplate('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}')
.withHelper('goodbye', function (this: any, options) {
.withHelper('goodbye', function (this: any, options: Handlebars.HelperOptions) {
return (
'GOODBYE ' +
options.hash.cruel +
@ -539,7 +539,7 @@ describe('helpers', () => {
it('block helpers can take an optional hash with single quoted stings', () => {
expectTemplate('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}')
.withHelper('goodbye', function (this: any, options) {
.withHelper('goodbye', function (this: any, options: Handlebars.HelperOptions) {
return (
'GOODBYE ' +
options.hash.cruel +
@ -582,7 +582,7 @@ describe('helpers', () => {
it('if a context is not found, custom helperMissing is used', () => {
expectTemplate('{{hello}} {{link_to world}}')
.withInput({ hello: 'Hello', world: 'world' })
.withHelper('helperMissing', function (mesg, options) {
.withHelper('helperMissing', function (mesg, options: Handlebars.HelperOptions) {
if (options.name === 'link_to') {
return new Handlebars.SafeString('<a>' + mesg + '</a>');
}
@ -593,7 +593,7 @@ describe('helpers', () => {
it('if a value is not found, custom helperMissing is used', () => {
expectTemplate('{{hello}} {{link_to}}')
.withInput({ hello: 'Hello', world: 'world' })
.withHelper('helperMissing', function (options) {
.withHelper('helperMissing', function (options: Handlebars.HelperOptions) {
if (options.name === 'link_to') {
return new Handlebars.SafeString('<a>winning</a>');
}
@ -788,7 +788,7 @@ describe('helpers', () => {
it('helpers take precedence over same-named context properties$', () => {
expectTemplate('{{#goodbye}} {{cruel world}}{{/goodbye}}')
.withHelper('goodbye', function (this: any, options) {
.withHelper('goodbye', function (this: any, options: Handlebars.HelperOptions) {
return this.goodbye.toUpperCase() + options.fn(this);
})
.withHelper('cruel', function (world) {
@ -818,7 +818,7 @@ describe('helpers', () => {
it('Scoped names take precedence over block helpers', () => {
expectTemplate('{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}')
.withHelper('goodbye', function (this: any, options) {
.withHelper('goodbye', function (this: any, options: Handlebars.HelperOptions) {
return this.goodbye.toUpperCase() + options.fn(this);
})
.withHelper('cruel', function (world) {
@ -836,7 +836,7 @@ describe('helpers', () => {
it('should take presedence over context values', () => {
expectTemplate('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}')
.withInput({ value: 'foo' })
.withHelper('goodbyes', function (options) {
.withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
expect(options.fn.blockParams).toEqual(1);
return options.fn({ value: 'bar' }, { blockParams: [1, 2] });
})
@ -848,7 +848,7 @@ describe('helpers', () => {
.withHelper('value', function () {
return 'foo';
})
.withHelper('goodbyes', function (options) {
.withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
expect(options.fn.blockParams).toEqual(1);
return options.fn({}, { blockParams: [1, 2] });
})
@ -861,7 +861,7 @@ describe('helpers', () => {
.withHelper('value', function () {
return 'foo';
})
.withHelper('goodbyes', function (this: any, options) {
.withHelper('goodbyes', function (this: any, options: Handlebars.HelperOptions) {
expect(options.fn.blockParams).toEqual(1);
return options.fn(this, { blockParams: [1, 2] });
})
@ -879,7 +879,7 @@ describe('helpers', () => {
}
)
.withInput({ value: 'foo' })
.withHelper('goodbyes', function (options) {
.withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
return options.fn(
{ value: 'bar' },
{
@ -893,7 +893,7 @@ describe('helpers', () => {
it('should allow block params on chained helpers', () => {
expectTemplate('{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}')
.withInput({ value: 'foo' })
.withHelper('goodbyes', function (options) {
.withHelper('goodbyes', function (options: Handlebars.HelperOptions) {
expect(options.fn.blockParams).toEqual(1);
return options.fn({ value: 'bar' }, { blockParams: [1, 2] });
})
@ -942,9 +942,12 @@ describe('helpers', () => {
describe('the lookupProperty-option', () => {
it('should be passed to custom helpers', () => {
expectTemplate('{{testHelper}}')
.withHelper('testHelper', function testHelper(this: any, options) {
return options.lookupProperty(this, 'testProperty');
})
.withHelper(
'testHelper',
function testHelper(this: any, options: Handlebars.HelperOptions) {
return options.lookupProperty(this, 'testProperty');
}
)
.withInput({ testProperty: 'abc' })
.toCompileTo('abc');
});

View file

@ -5,6 +5,7 @@
* See `packages/kbn-handlebars/LICENSE` for more information.
*/
import Handlebars from '../..';
import { expectTemplate } from '../__jest__/test_bench';
describe('Regressions', () => {
@ -237,7 +238,7 @@ describe('Regressions', () => {
)
.withInput({ array: [1], name: 'John' })
.withHelpers({
myif(conditional, options) {
myif(conditional, options: Handlebars.HelperOptions) {
if (conditional) {
return options.fn(this);
} else {
@ -261,7 +262,7 @@ describe('Regressions', () => {
expectTemplate('{{helpa length="foo"}}')
.withInput({ array: [1], name: 'John' })
.withHelpers({
helpa(options) {
helpa(options: Handlebars.HelperOptions) {
return options.hash.length;
},
})