/* * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), * and may include modifications made by Elasticsearch B.V. * Elasticsearch B.V. licenses this file to you under the MIT License. * See `packages/kbn-handlebars/LICENSE` for more information. */ import Handlebars from '../..'; import { expectTemplate } from '../__jest__/test_bench'; describe('basic context', () => { it('most basic', () => { expectTemplate('{{foo}}').withInput({ foo: 'foo' }).toCompileTo('foo'); }); it('escaping', () => { expectTemplate('\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('{{foo}}'); expectTemplate('content \\{{foo}}').withInput({ foo: 'food' }).toCompileTo('content {{foo}}'); expectTemplate('\\\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('\\food'); expectTemplate('content \\\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('content \\food'); expectTemplate('\\\\ {{foo}}').withInput({ foo: 'food' }).toCompileTo('\\\\ food'); }); it('compiling with a basic context', () => { expectTemplate('Goodbye\n{{cruel}}\n{{world}}!') .withInput({ cruel: 'cruel', world: 'world', }) .toCompileTo('Goodbye\ncruel\nworld!'); }); it('compiling with a string context', () => { expectTemplate('{{.}}{{length}}').withInput('bye').toCompileTo('bye3'); }); it('compiling with an undefined context', () => { expectTemplate('Goodbye\n{{cruel}}\n{{world.bar}}!') .withInput(undefined) .toCompileTo('Goodbye\n\n!'); expectTemplate('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}') .withInput(undefined) .toCompileTo('Goodbye'); }); it('comments', () => { expectTemplate('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!') .withInput({ cruel: 'cruel', world: 'world', }) .toCompileTo('Goodbye\ncruel\nworld!'); expectTemplate(' {{~! comment ~}} blah').toCompileTo('blah'); expectTemplate(' {{~!-- long-comment --~}} blah').toCompileTo('blah'); expectTemplate(' {{! comment ~}} blah').toCompileTo(' blah'); expectTemplate(' {{!-- long-comment --~}} blah').toCompileTo(' blah'); expectTemplate(' {{~! comment}} blah').toCompileTo(' blah'); expectTemplate(' {{~!-- long-comment --}} blah').toCompileTo(' blah'); }); it('boolean', () => { const string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!'; expectTemplate(string) .withInput({ goodbye: true, world: 'world', }) .toCompileTo('GOODBYE cruel world!'); expectTemplate(string) .withInput({ goodbye: false, world: 'world', }) .toCompileTo('cruel world!'); }); it('zeros', () => { expectTemplate('num1: {{num1}}, num2: {{num2}}') .withInput({ num1: 42, num2: 0, }) .toCompileTo('num1: 42, num2: 0'); expectTemplate('num: {{.}}').withInput(0).toCompileTo('num: 0'); expectTemplate('num: {{num1/num2}}') .withInput({ num1: { num2: 0 } }) .toCompileTo('num: 0'); }); it('false', () => { /* eslint-disable no-new-wrappers */ expectTemplate('val1: {{val1}}, val2: {{val2}}') .withInput({ val1: false, val2: new Boolean(false), }) .toCompileTo('val1: false, val2: false'); expectTemplate('val: {{.}}').withInput(false).toCompileTo('val: false'); expectTemplate('val: {{val1/val2}}') .withInput({ val1: { val2: false } }) .toCompileTo('val: false'); expectTemplate('val1: {{{val1}}}, val2: {{{val2}}}') .withInput({ val1: false, val2: new Boolean(false), }) .toCompileTo('val1: false, val2: false'); expectTemplate('val: {{{val1/val2}}}') .withInput({ val1: { val2: false } }) .toCompileTo('val: false'); /* eslint-enable */ }); it('should handle undefined and null', () => { expectTemplate('{{awesome undefined null}}') .withInput({ awesome(_undefined: any, _null: any, options: any) { return (_undefined === undefined) + ' ' + (_null === null) + ' ' + typeof options; }, }) .toCompileTo('true true object'); expectTemplate('{{undefined}}') .withInput({ undefined() { return 'undefined!'; }, }) .toCompileTo('undefined!'); expectTemplate('{{null}}') .withInput({ null() { return 'null!'; }, }) .toCompileTo('null!'); }); it('newlines', () => { expectTemplate("Alan's\nTest").toCompileTo("Alan's\nTest"); expectTemplate("Alan's\rTest").toCompileTo("Alan's\rTest"); }); it('escaping text', () => { expectTemplate("Awesome's").toCompileTo("Awesome's"); expectTemplate('Awesome\\').toCompileTo('Awesome\\'); expectTemplate('Awesome\\\\ foo').toCompileTo('Awesome\\\\ foo'); expectTemplate('Awesome {{foo}}').withInput({ foo: '\\' }).toCompileTo('Awesome \\'); expectTemplate(" ' ' ").toCompileTo(" ' ' "); }); it('escaping expressions', () => { expectTemplate('{{{awesome}}}').withInput({ awesome: "&'\\<>" }).toCompileTo("&'\\<>"); expectTemplate('{{&awesome}}').withInput({ awesome: "&'\\<>" }).toCompileTo("&'\\<>"); expectTemplate('{{awesome}}') .withInput({ awesome: '&"\'`\\<>' }) .toCompileTo('&"'`\\<>'); expectTemplate('{{awesome}}') .withInput({ awesome: 'Escaped, looks like: <b>' }) .toCompileTo('Escaped, <b> looks like: &lt;b&gt;'); }); it("functions returning safestrings shouldn't be escaped", () => { expectTemplate('{{awesome}}') .withInput({ awesome() { return new Handlebars.SafeString("&'\\<>"); }, }) .toCompileTo("&'\\<>"); }); it('functions', () => { expectTemplate('{{awesome}}') .withInput({ awesome() { return 'Awesome'; }, }) .toCompileTo('Awesome'); expectTemplate('{{awesome}}') .withInput({ awesome() { return this.more; }, more: 'More awesome', }) .toCompileTo('More awesome'); }); it('functions with context argument', () => { expectTemplate('{{awesome frank}}') .withInput({ awesome(context: any) { return context; }, frank: 'Frank', }) .toCompileTo('Frank'); }); it('pathed functions with context argument', () => { expectTemplate('{{bar.awesome frank}}') .withInput({ bar: { awesome(context: any) { return context; }, }, frank: 'Frank', }) .toCompileTo('Frank'); }); it('depthed functions with context argument', () => { expectTemplate('{{#with frank}}{{../awesome .}}{{/with}}') .withInput({ awesome(context: any) { return context; }, frank: 'Frank', }) .toCompileTo('Frank'); }); it('block functions with context argument', () => { expectTemplate('{{#awesome 1}}inner {{.}}{{/awesome}}') .withInput({ awesome(context: any, options: any) { return options.fn(context); }, }) .toCompileTo('inner 1'); }); it('depthed block functions with context argument', () => { expectTemplate('{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}') .withInput({ value: true, awesome(context: any, options: any) { return options.fn(context); }, }) .toCompileTo('inner 1'); }); it('block functions without context argument', () => { expectTemplate('{{#awesome}}inner{{/awesome}}') .withInput({ awesome(options: any) { return options.fn(this); }, }) .toCompileTo('inner'); }); it('pathed block functions without context argument', () => { expectTemplate('{{#foo.awesome}}inner{{/foo.awesome}}') .withInput({ foo: { awesome() { return this; }, }, }) .toCompileTo('inner'); }); it('depthed block functions without context argument', () => { expectTemplate('{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}') .withInput({ value: true, awesome() { return this; }, }) .toCompileTo('inner'); }); it('paths with hyphens', () => { expectTemplate('{{foo-bar}}').withInput({ 'foo-bar': 'baz' }).toCompileTo('baz'); expectTemplate('{{foo.foo-bar}}') .withInput({ foo: { 'foo-bar': 'baz' } }) .toCompileTo('baz'); expectTemplate('{{foo/foo-bar}}') .withInput({ foo: { 'foo-bar': 'baz' } }) .toCompileTo('baz'); }); it('nested paths', () => { expectTemplate('Goodbye {{alan/expression}} world!') .withInput({ alan: { expression: 'beautiful' } }) .toCompileTo('Goodbye beautiful world!'); }); it('nested paths with empty string value', () => { expectTemplate('Goodbye {{alan/expression}} world!') .withInput({ alan: { expression: '' } }) .toCompileTo('Goodbye world!'); }); it('literal paths', () => { expectTemplate('Goodbye {{[@alan]/expression}} world!') .withInput({ '@alan': { expression: 'beautiful' } }) .toCompileTo('Goodbye beautiful world!'); expectTemplate('Goodbye {{[foo bar]/expression}} world!') .withInput({ 'foo bar': { expression: 'beautiful' } }) .toCompileTo('Goodbye beautiful world!'); }); it('literal references', () => { expectTemplate('Goodbye {{[foo bar]}} world!') .withInput({ 'foo bar': 'beautiful' }) .toCompileTo('Goodbye beautiful world!'); expectTemplate('Goodbye {{"foo bar"}} world!') .withInput({ 'foo bar': 'beautiful' }) .toCompileTo('Goodbye beautiful world!'); expectTemplate("Goodbye {{'foo bar'}} world!") .withInput({ 'foo bar': 'beautiful' }) .toCompileTo('Goodbye beautiful world!'); expectTemplate('Goodbye {{"foo[bar"}} world!') .withInput({ 'foo[bar': 'beautiful' }) .toCompileTo('Goodbye beautiful world!'); expectTemplate('Goodbye {{"foo\'bar"}} world!') .withInput({ "foo'bar": 'beautiful' }) .toCompileTo('Goodbye beautiful world!'); expectTemplate("Goodbye {{'foo\"bar'}} world!") .withInput({ 'foo"bar': 'beautiful' }) .toCompileTo('Goodbye beautiful world!'); }); it("that current context path ({{.}}) doesn't hit helpers", () => { expectTemplate('test: {{.}}') .withInput(null) // @ts-expect-error Setting the helper to a string instead of a function doesn't make sense normally, but here it doesn't matter .withHelpers({ helper: 'awesome' }) .toCompileTo('test: '); }); it('complex but empty paths', () => { expectTemplate('{{person/name}}') .withInput({ person: { name: null } }) .toCompileTo(''); expectTemplate('{{person/name}}').withInput({ person: {} }).toCompileTo(''); }); it('this keyword in paths', () => { expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}') .withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] }) .toCompileTo('goodbyeGoodbyeGOODBYE'); expectTemplate('{{#hellos}}{{this/text}}{{/hellos}}') .withInput({ hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }], }) .toCompileTo('helloHelloHELLO'); }); it('this keyword nested inside path', () => { expectTemplate('{{#hellos}}{{text/this/foo}}{{/hellos}}').toThrow( 'Invalid path: text/this - 1:13' ); expectTemplate('{{[this]}}').withInput({ this: 'bar' }).toCompileTo('bar'); expectTemplate('{{text/[this]}}') .withInput({ text: { this: 'bar' } }) .toCompileTo('bar'); }); it('this keyword in helpers', () => { const helpers = { foo(value: any) { return 'bar ' + value; }, }; expectTemplate('{{#goodbyes}}{{foo this}}{{/goodbyes}}') .withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] }) .withHelpers(helpers) .toCompileTo('bar goodbyebar Goodbyebar GOODBYE'); expectTemplate('{{#hellos}}{{foo this/text}}{{/hellos}}') .withInput({ hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }], }) .withHelpers(helpers) .toCompileTo('bar hellobar Hellobar HELLO'); }); it('this keyword nested inside helpers param', () => { expectTemplate('{{#hellos}}{{foo text/this/foo}}{{/hellos}}').toThrow( 'Invalid path: text/this - 1:17' ); expectTemplate('{{foo [this]}}') .withInput({ foo(value: any) { return value; }, this: 'bar', }) .toCompileTo('bar'); expectTemplate('{{foo text/[this]}}') .withInput({ foo(value: any) { return value; }, text: { this: 'bar' }, }) .toCompileTo('bar'); }); it('pass string literals', () => { expectTemplate('{{"foo"}}').toCompileTo(''); expectTemplate('{{"foo"}}').withInput({ foo: 'bar' }).toCompileTo('bar'); expectTemplate('{{#"foo"}}{{.}}{{/"foo"}}') .withInput({ foo: ['bar', 'baz'], }) .toCompileTo('barbaz'); }); it('pass number literals', () => { expectTemplate('{{12}}').toCompileTo(''); expectTemplate('{{12}}').withInput({ '12': 'bar' }).toCompileTo('bar'); expectTemplate('{{12.34}}').toCompileTo(''); expectTemplate('{{12.34}}').withInput({ '12.34': 'bar' }).toCompileTo('bar'); expectTemplate('{{12.34 1}}') .withInput({ '12.34'(arg: any) { return 'bar' + arg; }, }) .toCompileTo('bar1'); }); it('pass boolean literals', () => { expectTemplate('{{true}}').toCompileTo(''); expectTemplate('{{true}}').withInput({ '': 'foo' }).toCompileTo(''); expectTemplate('{{false}}').withInput({ false: 'foo' }).toCompileTo('foo'); }); it('should handle literals in subexpression', () => { expectTemplate('{{foo (false)}}') .withInput({ false() { return 'bar'; }, }) .withHelper('foo', function (arg) { return arg; }) .toCompileTo('bar'); }); });