summaryrefslogtreecommitdiff
path: root/dev/lib/handlebars/src/spec/index.builtins.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dev/lib/handlebars/src/spec/index.builtins.test.ts')
-rw-r--r--dev/lib/handlebars/src/spec/index.builtins.test.ts676
1 files changed, 676 insertions, 0 deletions
diff --git a/dev/lib/handlebars/src/spec/index.builtins.test.ts b/dev/lib/handlebars/src/spec/index.builtins.test.ts
new file mode 100644
index 00000000..c47ec29f
--- /dev/null
+++ b/dev/lib/handlebars/src/spec/index.builtins.test.ts
@@ -0,0 +1,676 @@
+/*
+ * 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.
+ */
+
+/* eslint-disable max-classes-per-file */
+
+import Handlebars from '../..';
+import { expectTemplate } from '../__jest__/test_bench';
+
+describe('builtin helpers', () => {
+ describe('#if', () => {
+ it('if', () => {
+ const string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
+
+ expectTemplate(string)
+ .withInput({
+ goodbye: true,
+ world: 'world',
+ })
+ .toCompileTo('GOODBYE cruel world!');
+
+ expectTemplate(string)
+ .withInput({
+ goodbye: 'dummy',
+ world: 'world',
+ })
+ .toCompileTo('GOODBYE cruel world!');
+
+ expectTemplate(string)
+ .withInput({
+ goodbye: false,
+ world: 'world',
+ })
+ .toCompileTo('cruel world!');
+
+ expectTemplate(string).withInput({ world: 'world' }).toCompileTo('cruel world!');
+
+ expectTemplate(string)
+ .withInput({
+ goodbye: ['foo'],
+ world: 'world',
+ })
+ .toCompileTo('GOODBYE cruel world!');
+
+ expectTemplate(string)
+ .withInput({
+ goodbye: [],
+ world: 'world',
+ })
+ .toCompileTo('cruel world!');
+
+ expectTemplate(string)
+ .withInput({
+ goodbye: 0,
+ world: 'world',
+ })
+ .toCompileTo('cruel world!');
+
+ expectTemplate('{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!')
+ .withInput({
+ goodbye: 0,
+ world: 'world',
+ })
+ .toCompileTo('GOODBYE cruel world!');
+ });
+
+ it('if with function argument', () => {
+ const string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
+
+ expectTemplate(string)
+ .withInput({
+ goodbye() {
+ return true;
+ },
+ world: 'world',
+ })
+ .toCompileTo('GOODBYE cruel world!');
+
+ expectTemplate(string)
+ .withInput({
+ goodbye() {
+ return this.world;
+ },
+ world: 'world',
+ })
+ .toCompileTo('GOODBYE cruel world!');
+
+ expectTemplate(string)
+ .withInput({
+ goodbye() {
+ return false;
+ },
+ world: 'world',
+ })
+ .toCompileTo('cruel world!');
+
+ expectTemplate(string)
+ .withInput({
+ goodbye() {
+ return this.foo;
+ },
+ world: 'world',
+ })
+ .toCompileTo('cruel world!');
+ });
+
+ it('should not change the depth list', () => {
+ expectTemplate('{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}')
+ .withInput({
+ foo: { goodbye: true },
+ world: 'world',
+ })
+ .toCompileTo('GOODBYE cruel world!');
+ });
+ });
+
+ describe('#with', () => {
+ it('with', () => {
+ expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}')
+ .withInput({
+ person: {
+ first: 'Alan',
+ last: 'Johnson',
+ },
+ })
+ .toCompileTo('Alan Johnson');
+ });
+
+ it('with with function argument', () => {
+ expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}')
+ .withInput({
+ person() {
+ return {
+ first: 'Alan',
+ last: 'Johnson',
+ };
+ },
+ })
+ .toCompileTo('Alan Johnson');
+ });
+
+ it('with with else', () => {
+ expectTemplate(
+ '{{#with person}}Person is present{{else}}Person is not present{{/with}}'
+ ).toCompileTo('Person is not present');
+ });
+
+ it('with provides block parameter', () => {
+ expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}')
+ .withInput({
+ person: {
+ first: 'Alan',
+ last: 'Johnson',
+ },
+ })
+ .toCompileTo('Alan Johnson');
+ });
+
+ it('works when data is disabled', () => {
+ expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}')
+ .withInput({ person: { first: 'Alan', last: 'Johnson' } })
+ .withCompileOptions({ data: false })
+ .toCompileTo('Alan Johnson');
+ });
+ });
+
+ describe('#each', () => {
+ it('each', () => {
+ const string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
+
+ expectTemplate(string)
+ .withInput({
+ goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
+ world: 'world',
+ })
+ .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
+
+ expectTemplate(string)
+ .withInput({
+ goodbyes: [],
+ world: 'world',
+ })
+ .toCompileTo('cruel world!');
+ });
+
+ it('each without data', () => {
+ expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!')
+ .withInput({
+ goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
+ world: 'world',
+ })
+ .withRuntimeOptions({ data: false })
+ .withCompileOptions({ data: false })
+ .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
+
+ expectTemplate('{{#each .}}{{.}}{{/each}}')
+ .withInput({ goodbyes: 'cruel', world: 'world' })
+ .withRuntimeOptions({ data: false })
+ .withCompileOptions({ data: false })
+ .toCompileTo('cruelworld');
+ });
+
+ it('each without context', () => {
+ expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!')
+ .withInput(undefined)
+ .toCompileTo('cruel !');
+ });
+
+ it('each with an object and @key', () => {
+ const string = '{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!';
+
+ function Clazz(this: any) {
+ this['<b>#1</b>'] = { text: 'goodbye' };
+ this[2] = { text: 'GOODBYE' };
+ }
+ Clazz.prototype.foo = 'fail';
+ const hash = { goodbyes: new (Clazz as any)(), world: 'world' };
+
+ // Object property iteration order is undefined according to ECMA spec,
+ // so we need to check both possible orders
+ // @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
+ try {
+ expectTemplate(string)
+ .withInput(hash)
+ .toCompileTo('&lt;b&gt;#1&lt;/b&gt;. goodbye! 2. GOODBYE! cruel world!');
+ } catch (e) {
+ expectTemplate(string)
+ .withInput(hash)
+ .toCompileTo('2. GOODBYE! &lt;b&gt;#1&lt;/b&gt;. goodbye! cruel world!');
+ }
+
+ expectTemplate(string)
+ .withInput({
+ goodbyes: {},
+ world: 'world',
+ })
+ .toCompileTo('cruel world!');
+ });
+
+ it('each with @index', () => {
+ expectTemplate('{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!')
+ .withInput({
+ goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
+ world: 'world',
+ })
+ .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
+ });
+
+ it('each with nested @index', () => {
+ expectTemplate(
+ '{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!'
+ )
+ .withInput({
+ goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
+ world: 'world',
+ })
+ .toCompileTo(
+ '0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!'
+ );
+ });
+
+ it('each with block params', () => {
+ expectTemplate(
+ '{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!'
+ )
+ .withInput({
+ goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }],
+ world: 'world',
+ })
+ .toCompileTo('0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!');
+ });
+
+ // TODO: This test has been added to the `4.x` branch of the handlebars.js repo along with a code-fix,
+ // but a new version of the handlebars package containing this fix has not yet been published to npm.
+ //
+ // Before enabling this code, a new version of handlebars needs to be released and the corresponding
+ // updates needs to be applied to this implementation.
+ //
+ // See: https://github.com/handlebars-lang/handlebars.js/commit/30dbf0478109ded8f12bb29832135d480c17e367
+ it.skip('each with block params and strict compilation', () => {
+ expectTemplate('{{#each goodbyes as |value index|}}{{index}}. {{value.text}}!{{/each}}')
+ .withCompileOptions({ strict: true })
+ .withInput({ goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }] })
+ .toCompileTo('0. goodbye!1. Goodbye!');
+ });
+
+ it('each object with @index', () => {
+ expectTemplate('{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!')
+ .withInput({
+ goodbyes: {
+ a: { text: 'goodbye' },
+ b: { text: 'Goodbye' },
+ c: { text: 'GOODBYE' },
+ },
+ world: 'world',
+ })
+ .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
+ });
+
+ it('each with @first', () => {
+ expectTemplate('{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!')
+ .withInput({
+ goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
+ world: 'world',
+ })
+ .toCompileTo('goodbye! cruel world!');
+ });
+
+ it('each with nested @first', () => {
+ expectTemplate(
+ '{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!'
+ )
+ .withInput({
+ goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
+ world: 'world',
+ })
+ .toCompileTo('(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!');
+ });
+
+ it('each object with @first', () => {
+ expectTemplate('{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!')
+ .withInput({
+ goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } },
+ world: 'world',
+ })
+ .toCompileTo('goodbye! cruel world!');
+ });
+
+ it('each with @last', () => {
+ expectTemplate('{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!')
+ .withInput({
+ goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
+ world: 'world',
+ })
+ .toCompileTo('GOODBYE! cruel world!');
+ });
+
+ it('each object with @last', () => {
+ expectTemplate('{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!')
+ .withInput({
+ goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } },
+ world: 'world',
+ })
+ .toCompileTo('Goodbye! cruel world!');
+ });
+
+ it('each with nested @last', () => {
+ expectTemplate(
+ '{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!'
+ )
+ .withInput({
+ goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
+ world: 'world',
+ })
+ .toCompileTo('(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!');
+ });
+
+ it('each with function argument', () => {
+ const string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
+
+ expectTemplate(string)
+ .withInput({
+ goodbyes() {
+ return [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }];
+ },
+ world: 'world',
+ })
+ .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
+
+ expectTemplate(string)
+ .withInput({
+ goodbyes: [],
+ world: 'world',
+ })
+ .toCompileTo('cruel world!');
+ });
+
+ it('each object when last key is an empty string', () => {
+ expectTemplate('{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!')
+ .withInput({
+ goodbyes: {
+ a: { text: 'goodbye' },
+ b: { text: 'Goodbye' },
+ '': { text: 'GOODBYE' },
+ },
+ world: 'world',
+ })
+ .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
+ });
+
+ it('data passed to helpers', () => {
+ expectTemplate('{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}')
+ .withInput({ letters: ['a', 'b', 'c'] })
+ .withHelper('detectDataInsideEach', function (options) {
+ return options.data && options.data.exclaim;
+ })
+ .withRuntimeOptions({
+ data: {
+ exclaim: '!',
+ },
+ })
+ .toCompileTo('a!b!c!');
+ });
+
+ it('each on implicit context', () => {
+ expectTemplate('{{#each}}{{text}}! {{/each}}cruel world!').toThrow(Handlebars.Exception);
+ });
+
+ it('each on iterable', () => {
+ class Iterator {
+ private arr: any[];
+ private index: number = 0;
+
+ constructor(arr: any[]) {
+ this.arr = arr;
+ }
+
+ next() {
+ const value = this.arr[this.index];
+ const done = this.index === this.arr.length;
+ if (!done) {
+ this.index++;
+ }
+ return { value, done };
+ }
+ }
+
+ class Iterable {
+ private arr: any[];
+
+ constructor(arr: any[]) {
+ this.arr = arr;
+ }
+
+ [Symbol.iterator]() {
+ return new Iterator(this.arr);
+ }
+ }
+
+ const string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
+
+ expectTemplate(string)
+ .withInput({
+ goodbyes: new Iterable([{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]),
+ world: 'world',
+ })
+ .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
+
+ expectTemplate(string)
+ .withInput({
+ goodbyes: new Iterable([]),
+ world: 'world',
+ })
+ .toCompileTo('cruel world!');
+ });
+ });
+
+ describe('#log', function () {
+ /* eslint-disable no-console */
+ let $log: typeof console.log;
+ let $info: typeof console.info;
+ let $error: typeof console.error;
+
+ beforeEach(function () {
+ $log = console.log;
+ $info = console.info;
+ $error = console.error;
+
+ global.kbnHandlebarsEnv = Handlebars.create();
+ });
+
+ afterEach(function () {
+ console.log = $log;
+ console.info = $info;
+ console.error = $error;
+
+ global.kbnHandlebarsEnv = null;
+ });
+
+ it('should call logger at default level', function () {
+ let levelArg;
+ let logArg;
+ kbnHandlebarsEnv!.log = function (level, arg) {
+ levelArg = level;
+ logArg = arg;
+ };
+
+ expectTemplate('{{log blah}}').withInput({ blah: 'whee' }).toCompileTo('');
+ expect(1).toEqual(levelArg);
+ expect('whee').toEqual(logArg);
+ });
+
+ it('should call logger at data level', function () {
+ let levelArg;
+ let logArg;
+ kbnHandlebarsEnv!.log = function (level, arg) {
+ levelArg = level;
+ logArg = arg;
+ };
+
+ expectTemplate('{{log blah}}')
+ .withInput({ blah: 'whee' })
+ .withRuntimeOptions({ data: { level: '03' } })
+ .withCompileOptions({ data: true })
+ .toCompileTo('');
+ expect('03').toEqual(levelArg);
+ expect('whee').toEqual(logArg);
+ });
+
+ it('should output to info', function () {
+ let calls = 0;
+ const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2;
+
+ console.info = function (info) {
+ expect('whee').toEqual(info);
+ calls++;
+ if (calls === callsExpected) {
+ console.info = $info;
+ console.log = $log;
+ }
+ };
+ console.log = function (log) {
+ expect('whee').toEqual(log);
+ calls++;
+ if (calls === callsExpected) {
+ console.info = $info;
+ console.log = $log;
+ }
+ };
+
+ expectTemplate('{{log blah}}').withInput({ blah: 'whee' }).toCompileTo('');
+ expect(calls).toEqual(callsExpected);
+ });
+
+ it('should log at data level', function () {
+ let calls = 0;
+ const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2;
+
+ console.error = function (log) {
+ expect('whee').toEqual(log);
+ calls++;
+ if (calls === callsExpected) console.error = $error;
+ };
+
+ expectTemplate('{{log blah}}')
+ .withInput({ blah: 'whee' })
+ .withRuntimeOptions({ data: { level: '03' } })
+ .withCompileOptions({ data: true })
+ .toCompileTo('');
+ expect(calls).toEqual(callsExpected);
+ });
+
+ it('should handle missing logger', function () {
+ let calls = 0;
+ const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2;
+
+ // @ts-expect-error
+ console.error = undefined;
+ console.log = function (log) {
+ expect('whee').toEqual(log);
+ calls++;
+ if (calls === callsExpected) console.log = $log;
+ };
+
+ expectTemplate('{{log blah}}')
+ .withInput({ blah: 'whee' })
+ .withRuntimeOptions({ data: { level: '03' } })
+ .withCompileOptions({ data: true })
+ .toCompileTo('');
+ expect(calls).toEqual(callsExpected);
+ });
+
+ it('should handle string log levels', function () {
+ let calls = 0;
+ const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2;
+
+ console.error = function (log) {
+ expect('whee').toEqual(log);
+ calls++;
+ };
+
+ expectTemplate('{{log blah}}')
+ .withInput({ blah: 'whee' })
+ .withRuntimeOptions({ data: { level: 'error' } })
+ .withCompileOptions({ data: true })
+ .toCompileTo('');
+ expect(calls).toEqual(callsExpected);
+
+ calls = 0;
+
+ expectTemplate('{{log blah}}')
+ .withInput({ blah: 'whee' })
+ .withRuntimeOptions({ data: { level: 'ERROR' } })
+ .withCompileOptions({ data: true })
+ .toCompileTo('');
+ expect(calls).toEqual(callsExpected);
+ });
+
+ it('should handle hash log levels [1]', function () {
+ let calls = 0;
+ const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2;
+
+ console.error = function (log) {
+ expect('whee').toEqual(log);
+ calls++;
+ };
+
+ expectTemplate('{{log blah level="error"}}').withInput({ blah: 'whee' }).toCompileTo('');
+ expect(calls).toEqual(callsExpected);
+ });
+
+ it('should handle hash log levels [2]', function () {
+ let called = false;
+
+ console.info =
+ console.log =
+ console.error =
+ console.debug =
+ function () {
+ called = true;
+ console.info = console.log = console.error = console.debug = $log;
+ };
+
+ expectTemplate('{{log blah level="debug"}}').withInput({ blah: 'whee' }).toCompileTo('');
+ expect(false).toEqual(called);
+ });
+
+ it('should pass multiple log arguments', function () {
+ let calls = 0;
+ const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2;
+
+ console.info = console.log = function (log1, log2, log3) {
+ expect('whee').toEqual(log1);
+ expect('foo').toEqual(log2);
+ expect(1).toEqual(log3);
+ calls++;
+ if (calls === callsExpected) console.log = $log;
+ };
+
+ expectTemplate('{{log blah "foo" 1}}').withInput({ blah: 'whee' }).toCompileTo('');
+ expect(calls).toEqual(callsExpected);
+ });
+
+ it('should pass zero log arguments', function () {
+ let calls = 0;
+ const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2;
+
+ console.info = console.log = function () {
+ expect(arguments.length).toEqual(0);
+ calls++;
+ if (calls === callsExpected) console.log = $log;
+ };
+
+ expectTemplate('{{log}}').withInput({ blah: 'whee' }).toCompileTo('');
+ expect(calls).toEqual(callsExpected);
+ });
+ /* eslint-enable no-console */
+ });
+
+ describe('#lookup', () => {
+ it('should lookup arbitrary content', () => {
+ expectTemplate('{{#each goodbyes}}{{lookup ../data .}}{{/each}}')
+ .withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] })
+ .toCompileTo('foobar');
+ });
+
+ it('should not fail on undefined value', () => {
+ expectTemplate('{{#each goodbyes}}{{lookup ../bar .}}{{/each}}')
+ .withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] })
+ .toCompileTo('');
+ });
+ });
+});