From 9ccfd92d3a2f776236ce2979f3fccd28698b5594 Mon Sep 17 00:00:00 2001 From: Curran Kelleher Date: Tue, 4 Jul 2023 09:45:10 -0400 Subject: [PATCH] Adopt Prettier --- .prettierignore | 1 + .prettierrc | 3 + README.md | 47 ++-- index.js | 32 ++- package.json | 2 + rollup.config.js => rollup.config.mjs | 8 +- test.js | 307 +++++++++++++++----------- 7 files changed, 232 insertions(+), 168 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc rename rollup.config.js => rollup.config.mjs (71%) diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..1521c8b --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +dist diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..544138b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/README.md b/README.md index bb31b89..33da47e 100644 --- a/README.md +++ b/README.md @@ -11,31 +11,31 @@ Here's how you can use this library. Begin by installing via NPM: Here's a small example of usage showing the simplest case, a single string. ```js -const parse = require("json-templates"); +const parse = require('json-templates'); -const template = parse("{{foo}}"); +const template = parse('{{foo}}'); console.log(template.parameters); // Prints [{ key: "foo" }] -console.log(template({ foo: "bar" })); // Prints "bar" +console.log(template({ foo: 'bar' })); // Prints "bar" ``` Parameters can have default values, specified using a colon. These come into play only when the parameter is `undefined`. ```js -const template = parse("{{foo:bar}}"); +const template = parse('{{foo:bar}}'); console.log(template.parameters); // Prints [{ key: "foo", defaultValue: "bar" }] console.log(template()); // Prints "bar", using the default value. -console.log(template({ foo: "baz" })); // Prints "baz", using the given value. +console.log(template({ foo: 'baz' })); // Prints "baz", using the given value. ``` Parameters can come from a nested object. ```js -const template = parse("{{foo.value:baz}}"); +const template = parse('{{foo.value:baz}}'); console.log(template.parameters); // Prints [{ key: "foo.value", defaultValue: "baz" }] @@ -44,51 +44,51 @@ console.log(template()); // Prints "baz", using the default value. console.log(template({ foo: { value: 'bar' } })); // Prints "bar", using the given value. // Example with parameter coming from array -const template = parse({ a: "{{foo.1:baz}}" }); +const template = parse({ a: '{{foo.1:baz}}' }); console.log(template.parameters); // Prints [{ key: "foo.1", defaultValue: "baz" }] console.log(template()); // Prints { a: "baz" }, using the default value. -console.log(template({ foo: ["baq", "bar"] })); // Prints { a: "bar" }, using the given value of array. +console.log(template({ foo: ['baq', 'bar'] })); // Prints { a: "bar" }, using the given value of array. ``` Context values could be objects and arrays. ```js -const template = parse("{{foo:baz}}"); +const template = parse('{{foo:baz}}'); console.log(template.parameters); // Prints [{ key: "foo", defaultValue: "baz" }] console.log(template()); // Prints "baz", using the default value. console.log(template({ foo: { value: 'bar' } })); // Prints { value: 'bar' } , using the given value. - ``` + The kind of templating you can see in the above examples gets applied to any string values in complex object structures such as ElasticSearch queries. Here's an example of an ElasticSearch query. ```js const template = parse({ - index: "myindex", + index: 'myindex', body: { query: { match: { - title: "{{myTitle}}" - } + title: '{{myTitle}}', + }, }, facets: { tags: { terms: { - field: "tags" - } - } - } - } + field: 'tags', + }, + }, + }, + }, }); console.log(template.parameters); // Prints [{ key: "myTitle" }] -console.log(template({ title: "test" })); +console.log(template({ title: 'test' })); ``` The last line prints the following structure: @@ -115,10 +115,9 @@ The last line prints the following structure: The parse function also handles nested arrays and arbitrary leaf values. For more detailed examples, check out the [tests](https://github.com/curran/json-templates/blob/master/test.js). - ## Why? -The use case for this came about while working with ElasticSearch queries that need to be parameterized. We wanted the ability to *specify query templates within JSON*, and also make any of the string values parameterizable. The ideas was to make something kind of like [Handlebars](http://handlebarsjs.com/), but just for the values within the query. +The use case for this came about while working with ElasticSearch queries that need to be parameterized. We wanted the ability to _specify query templates within JSON_, and also make any of the string values parameterizable. The ideas was to make something kind of like [Handlebars](http://handlebarsjs.com/), but just for the values within the query. We also needed to know which parameters are required to "fill in" a given query template (in order to check if we have the right context parameters to actually execute the query). Related to this requirement, sometimes certain parameters should have default values. These parameters are not strictly required from the context. If not specified, the default value from the template will be used, otherwise the value from the context will be used. @@ -148,6 +147,6 @@ Also it was a fun challenge and a great opportunity to write some heady recursiv ## Related Work - * [json-templater](https://www.npmjs.com/package/json-templater) - * [bodybuilder](https://github.com/danpaz/bodybuilder) - * [elasticsearch-query-builder](https://github.com/leonardw/elasticsearch-query-builder) +- [json-templater](https://www.npmjs.com/package/json-templater) +- [bodybuilder](https://github.com/danpaz/bodybuilder) +- [elasticsearch-query-builder](https://github.com/leonardw/elasticsearch-query-builder) diff --git a/index.js b/index.js index 3f94a7b..394c25c 100644 --- a/index.js +++ b/index.js @@ -30,7 +30,7 @@ function Parameter(match) { if (i !== -1) { param = { key: matchValue.substr(0, i), - defaultValue: matchValue.substr(i + 1) + defaultValue: matchValue.substr(i + 1), }; } else { param = { key: matchValue }; @@ -41,7 +41,9 @@ function Parameter(match) { // Constructs a template function with deduped `parameters` property. function Template(fn, parameters) { - fn.parameters = Array.from(new Map(parameters.map(parameter => [parameter.key, parameter])).values()) + fn.parameters = Array.from( + new Map(parameters.map((parameter) => [parameter.key, parameter])).values() + ); return fn; } @@ -62,7 +64,7 @@ function parse(value) { case 'array': return parseArray(value); default: - return Template(function() { + return Template(function () { return value; }, []); } @@ -75,14 +77,14 @@ const parseString = (() => { // template parameter syntax such as {{foo}} or {{foo:someDefault}}. const regex = /{{(\w|:|[\s-+.,@/\//()?=*_$])+}}/g; - return str => { + return (str) => { let parameters = []; let templateFn = () => str; const matches = str.match(regex); if (matches) { parameters = matches.map(Parameter); - templateFn = context => { + templateFn = (context) => { context = context || {}; return matches.reduce((result, match, i) => { const parameter = parameters[i]; @@ -101,7 +103,11 @@ const parseString = (() => { } // Accommodate numbers as values. - if (matches.length === 1 && str.startsWith('{{') && str.endsWith('}}')) { + if ( + matches.length === 1 && + str.startsWith('{{') && + str.endsWith('}}') + ) { return value; } @@ -116,16 +122,19 @@ const parseString = (() => { // Parses non-leaf-nodes in the template object that are objects. function parseObject(object) { - const children = Object.keys(object).map(key => ({ + const children = Object.keys(object).map((key) => ({ keyTemplate: parseString(key), - valueTemplate: parse(object[key]) + valueTemplate: parse(object[key]), })); const templateParameters = children.reduce( (parameters, child) => - parameters.concat(child.valueTemplate.parameters, child.keyTemplate.parameters), + parameters.concat( + child.valueTemplate.parameters, + child.keyTemplate.parameters + ), [] ); - const templateFn = context => { + const templateFn = (context) => { return children.reduce((newObject, child) => { newObject[child.keyTemplate(context)] = child.valueTemplate(context); return newObject; @@ -142,7 +151,8 @@ function parseArray(array) { (parameters, template) => parameters.concat(template.parameters), [] ); - const templateFn = context => templates.map(template => template(context)); + const templateFn = (context) => + templates.map((template) => template(context)); return Template(templateFn, templateParameters); } diff --git a/package.json b/package.json index dcd8dcd..2f2c383 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "build": "rollup -c", "pretest": "npm run build", "test": "mocha", + "prettier": "prettier --write .", "prepublishOnly": "npm test", "postpublish": "git push && git push --tags" }, @@ -26,6 +27,7 @@ "homepage": "https://github.com/datavis-tech/json-templates#readme", "devDependencies": { "mocha": "^10.2.0", + "prettier": "^2.8.8", "rollup": "^3.26.0", "rollup-plugin-buble": "^0.19.8" }, diff --git a/rollup.config.js b/rollup.config.mjs similarity index 71% rename from rollup.config.js rename to rollup.config.mjs index e530e08..d07e74c 100644 --- a/rollup.config.js +++ b/rollup.config.mjs @@ -5,9 +5,7 @@ export default { output: { format: 'umd', name: 'jsonTemplates', - file: 'dist/index.js' + file: 'dist/index.js', }, - plugins: [ - buble() - ] -} + plugins: [buble()], +}; diff --git a/test.js b/test.js index 4f4c2c6..c8a5a39 100644 --- a/test.js +++ b/test.js @@ -21,13 +21,15 @@ describe('json-template', () => { it('should compute template for a string with a nested object parameter', () => { const template = parse('{{foo.value:baz}}'); - assert.deepEqual(template.parameters, [{ key: 'foo.value', defaultValue: 'baz' }]); + assert.deepEqual(template.parameters, [ + { key: 'foo.value', defaultValue: 'baz' }, + ]); assert.equal(template({ foo: { value: 'bar' } }), 'bar'); assert.equal(template(), 'baz'); }); it('should compute template for strings with no parameters', () => { - ['foo', '{{}}', '}}{{', '}}foo{{'].forEach(function(value) { + ['foo', '{{}}', '}}{{', '}}foo{{'].forEach(function (value) { const template = parse(value); assert.deepEqual(template.parameters, []); assert.equal(template(), value); @@ -39,8 +41,8 @@ describe('json-template', () => { assert.deepEqual(template.parameters, [ { key: 'foo', - defaultValue: 'bar' - } + defaultValue: 'bar', + }, ]); assert.equal(template(), 'bar'); assert.equal(template({ foo: 'baz' }), 'baz'); @@ -52,8 +54,8 @@ describe('json-template', () => { assert.deepEqual(template.parameters, [ { key: 'foo', - defaultValue: 'bar:baz' - } + defaultValue: 'bar:baz', + }, ]); assert.equal(template(), 'bar:baz'); assert.equal(template({ foo: 'baz' }), 'baz'); @@ -68,7 +70,10 @@ describe('json-template', () => { it('should compute template for a string with multiple inner parameters', () => { const template = parse('Hello {{firstName}} {{lastName}}, how are you ?'); - assert.deepEqual(template.parameters, [{ key: 'firstName' }, { key: 'lastName' }]); + assert.deepEqual(template.parameters, [ + { key: 'firstName' }, + { key: 'lastName' }, + ]); assert.equal( template({ firstName: 'Jane', lastName: 'Doe' }), 'Hello Jane Doe, how are you ?' @@ -76,8 +81,13 @@ describe('json-template', () => { }); it('should handle extra whitespace', () => { - const template = parse('Hello {{firstName }} {{ lastName}}, how are you ?'); - assert.deepEqual(template.parameters, [{ key: 'firstName' }, { key: 'lastName' }]); + const template = parse( + 'Hello {{firstName }} {{ lastName}}, how are you ?' + ); + assert.deepEqual(template.parameters, [ + { key: 'firstName' }, + { key: 'lastName' }, + ]); assert.equal( template({ firstName: 'Jane', lastName: 'Doe' }), 'Hello Jane Doe, how are you ?' @@ -86,21 +96,27 @@ describe('json-template', () => { it('should handle dashes in defaults', () => { const template = parse('{{startTime:now-24h}}'); - assert.deepEqual(template.parameters, [{ key: 'startTime', defaultValue: 'now-24h' }]); + assert.deepEqual(template.parameters, [ + { key: 'startTime', defaultValue: 'now-24h' }, + ]); assert.equal(template({ startTime: 'now-48h' }), 'now-48h'); assert.equal(template(), 'now-24h'); }); it('should handle special characters in defaults', () => { const template = parse('{{foo:-+., @/()?=*_}}'); - assert.deepEqual(template.parameters, [{ key: 'foo', defaultValue: '-+., @/()?=*_' }]); + assert.deepEqual(template.parameters, [ + { key: 'foo', defaultValue: '-+., @/()?=*_' }, + ]); assert.equal(template({ foo: '-+., @/()?=*_' }), '-+., @/()?=*_'); assert.equal(template(), '-+., @/()?=*_'); }); it('should handle email address in defaults', () => { const template = parse('{{email:jdoe@mail.com}}'); - assert.deepEqual(template.parameters, [{ key: 'email', defaultValue: 'jdoe@mail.com' }]); + assert.deepEqual(template.parameters, [ + { key: 'email', defaultValue: 'jdoe@mail.com' }, + ]); assert.equal(template({ email: 'jdoe@mail.com' }), 'jdoe@mail.com'); assert.equal(template(), 'jdoe@mail.com'); }); @@ -108,16 +124,19 @@ describe('json-template', () => { it('should handle phone number in defaults', () => { const template = parse('{{phone:+1 (256) 34-34-4556}}'); assert.deepEqual(template.parameters, [ - { key: 'phone', defaultValue: '+1 (256) 34-34-4556' } + { key: 'phone', defaultValue: '+1 (256) 34-34-4556' }, ]); - assert.equal(template({ phone: '+1 (256) 34-34-4556' }), '+1 (256) 34-34-4556'); + assert.equal( + template({ phone: '+1 (256) 34-34-4556' }), + '+1 (256) 34-34-4556' + ); assert.equal(template(), '+1 (256) 34-34-4556'); }); it('should handle url in defaults', () => { const template = parse('{{url:http://www.host.com/path?key_1=value}}'); assert.deepEqual(template.parameters, [ - { key: 'url', defaultValue: 'http://www.host.com/path?key_1=value' } + { key: 'url', defaultValue: 'http://www.host.com/path?key_1=value' }, ]); assert.equal( template({ url: 'http://www.host.com/path?key_1=value' }), @@ -143,7 +162,9 @@ describe('json-template', () => { it('should compute template with an object that has inner parameter', () => { const template = parse({ title: 'Hello {{foo}}, how are you ?' }); assert.deepEqual(template.parameters, [{ key: 'foo' }]); - assert.deepEqual(template({ foo: 'john' }), { title: 'Hello john, how are you ?' }); + assert.deepEqual(template({ foo: 'john' }), { + title: 'Hello john, how are you ?', + }); }); it('should compute template with an object', () => { @@ -167,26 +188,31 @@ describe('json-template', () => { it('should compute template with an object with multiple parameters', () => { const template = parse({ title: '{{myTitle}}', - description: '{{myDescription}}' + description: '{{myDescription}}', }); - assert.deepEqual(template.parameters, [{ key: 'myTitle' }, { key: 'myDescription' }]); + assert.deepEqual(template.parameters, [ + { key: 'myTitle' }, + { key: 'myDescription' }, + ]); assert.deepEqual( template({ myTitle: 'foo', - myDescription: 'bar' + myDescription: 'bar', }), { title: 'foo', - description: 'bar' + description: 'bar', } ); }); it('should compute template for an object with a nested object parameter', () => { const template = parse({ a: '{{foo.1:baz}}' }); - assert.deepEqual(template.parameters, [{ key: 'foo.1', defaultValue: 'baz' }]); + assert.deepEqual(template.parameters, [ + { key: 'foo.1', defaultValue: 'baz' }, + ]); assert.deepEqual(template({ foo: ['baq', 'bar'] }), { a: 'bar' }); assert.deepEqual(template(), { a: 'baz' }); }); @@ -194,32 +220,35 @@ describe('json-template', () => { it('should compute template with nested objects', () => { const template = parse({ body: { - title: '{{foo}}' - } + title: '{{foo}}', + }, }); assert.deepEqual(template.parameters, [{ key: 'foo' }]); assert.deepEqual(template({ foo: 'bar' }), { body: { - title: 'bar' - } + title: 'bar', + }, }); }); it('should compute template keys', () => { const template = parse({ body: { - 'A simple {{message}} to': '{{foo}}' - } + 'A simple {{message}} to': '{{foo}}', + }, }); - assert.deepEqual(template.parameters, [{ key: 'foo' }, { key: 'message' }]); + assert.deepEqual(template.parameters, [ + { key: 'foo' }, + { key: 'message' }, + ]); assert.deepEqual(template({ foo: 'bar', message: 'hello' }), { body: { - 'A simple hello to': 'bar' - } + 'A simple hello to': 'bar', + }, }); }); @@ -230,63 +259,66 @@ describe('json-template', () => { const template = parse({ disk: '/project/{{project}}/region/{{region}}/ssd', - vm: '/project/{{project}}/region/{{region}}/cpu' + vm: '/project/{{project}}/region/{{region}}/cpu', }); it('should correctly fill duplicate references in a template', () => { assert.deepEqual(template({ project: 'alpha', region: 'us-central' }), { disk: '/project/alpha/region/us-central/ssd', - vm: '/project/alpha/region/us-central/cpu' + vm: '/project/alpha/region/us-central/cpu', }); }); it('should deduplicate template parameters', () => { - assert.deepEqual(template.parameters, [{ key: 'project' }, { key: 'region' }]); + assert.deepEqual(template.parameters, [ + { key: 'project' }, + { key: 'region' }, + ]); }); }); it('should compute template keys with default value', () => { const template = parse({ body: { - 'A simple {{message:hello}} to': '{{foo}}' - } + 'A simple {{message:hello}} to': '{{foo}}', + }, }); assert.deepEqual(template.parameters, [ { key: 'foo' }, - { key: 'message', defaultValue: 'hello' } + { key: 'message', defaultValue: 'hello' }, ]); assert.deepEqual(template({ foo: 'bar' }), { body: { - 'A simple hello to': 'bar' - } + 'A simple hello to': 'bar', + }, }); }); it('should compute template keys with default value and period in the string', () => { const template = parse({ body: { - 'A simple {{message:hello.foo}} to': '{{foo}}' - } + 'A simple {{message:hello.foo}} to': '{{foo}}', + }, }); assert.deepEqual(template.parameters, [ { key: 'foo' }, - { key: 'message', defaultValue: 'hello.foo' } + { key: 'message', defaultValue: 'hello.foo' }, ]); assert.deepEqual(template({ foo: 'bar' }), { body: { - 'A simple hello.foo to': 'bar' - } + 'A simple hello.foo to': 'bar', + }, }); }); it('should allow template with null leaf values', () => { const spec = { x: '{{foo}}', - y: null + y: null, }; const template = parse(spec); assert.deepEqual(template.parameters, [{ key: 'foo' }]); @@ -316,13 +348,24 @@ describe('json-template', () => { it('should compute template with function', () => { const template = parse(['{{userCard}}']); assert.deepEqual(template.parameters, [{ key: 'userCard' }]); - assert.deepEqual(template({ userCard: () => ({ id: 1, user: "John" }) }), [{ id: 1, user: "John" }]); + assert.deepEqual( + template({ userCard: () => ({ id: 1, user: 'John' }) }), + [{ id: 1, user: 'John' }] + ); }); it('should compute template with function with multiple inner parameters', () => { - const template = parse(JSON.stringify({ username: "{{username}}", password: "{{password}}" })); - assert.deepEqual(template.parameters, [{ key: 'username' }, { key: 'password' }]); - assert.equal(template({ username: () => ("John"), password: () => ("John") }), '{"username":"John","password":"John"}'); + const template = parse( + JSON.stringify({ username: '{{username}}', password: '{{password}}' }) + ); + assert.deepEqual(template.parameters, [ + { key: 'username' }, + { key: 'password' }, + ]); + assert.equal( + template({ username: () => 'John', password: () => 'John' }), + '{"username":"John","password":"John"}' + ); }); }); @@ -377,17 +420,17 @@ describe('json-template', () => { body: { query: { match: { - title: '{{title}}' - } + title: '{{title}}', + }, }, facets: { tags: { terms: { - field: 'tags' - } - } - } - } + field: 'tags', + }, + }, + }, + }, }); assert.deepEqual(template.parameters, [{ key: 'title' }]); @@ -397,17 +440,17 @@ describe('json-template', () => { body: { query: { match: { - title: 'test' - } + title: 'test', + }, }, facets: { tags: { terms: { - field: 'tags' - } - } - } - } + field: 'tags', + }, + }, + }, + }, }); }); @@ -417,24 +460,24 @@ describe('json-template', () => { body: { query: { match: { - title: '{{title:test}}' - } + title: '{{title:test}}', + }, }, facets: { tags: { terms: { - field: 'tags' - } - } - } - } + field: 'tags', + }, + }, + }, + }, }); assert.deepEqual(template.parameters, [ { key: 'title', - defaultValue: 'test' - } + defaultValue: 'test', + }, ]); assert.deepEqual(template(), { @@ -442,17 +485,17 @@ describe('json-template', () => { body: { query: { match: { - title: 'test' - } + title: 'test', + }, }, facets: { tags: { terms: { - field: 'tags' - } - } - } - } + field: 'tags', + }, + }, + }, + }, }); assert.deepEqual(template({ title: 'foo' }), { @@ -460,17 +503,17 @@ describe('json-template', () => { body: { query: { match: { - title: 'foo' - } + title: 'foo', + }, }, facets: { tags: { terms: { - field: 'tags' - } - } - } - } + field: 'tags', + }, + }, + }, + }, }); }); @@ -480,81 +523,84 @@ describe('json-template', () => { bool: { must: { term: { - user: 'kimchy' - } + user: 'kimchy', + }, }, filter: { term: { - tag: 'tech' - } + tag: 'tech', + }, }, must_not: { range: { age: { from: 10, - to: 20 - } - } + to: 20, + }, + }, }, should: [ { term: { - tag: '{{myTag1}}' - } + tag: '{{myTag1}}', + }, }, { term: { - tag: '{{myTag2}}' - } - } + tag: '{{myTag2}}', + }, + }, ], minimum_should_match: 1, - boost: 1 - } + boost: 1, + }, }); - assert.deepEqual(template.parameters, [{ key: 'myTag1' }, { key: 'myTag2' }]); + assert.deepEqual(template.parameters, [ + { key: 'myTag1' }, + { key: 'myTag2' }, + ]); assert.deepEqual( template({ myTag1: 'wow', - myTag2: 'cats' + myTag2: 'cats', }), { bool: { must: { term: { - user: 'kimchy' - } + user: 'kimchy', + }, }, filter: { term: { - tag: 'tech' - } + tag: 'tech', + }, }, must_not: { range: { age: { from: 10, - to: 20 - } - } + to: 20, + }, + }, }, should: [ { term: { - tag: 'wow' - } + tag: 'wow', + }, }, { term: { - tag: 'cats' - } - } + tag: 'cats', + }, + }, ], minimum_should_match: 1, - boost: 1 - } + boost: 1, + }, } ); }); @@ -565,20 +611,20 @@ describe('json-template', () => { it('should replace object without stringify', () => { const template = parse({ s: '1', - b: '{{c.d}}' + b: '{{c.d}}', }); const context = { c: { d: { - j: 'a' - } - } + j: 'a', + }, + }, }; const expected = { s: '1', b: { - j: 'a' - } + j: 'a', + }, }; assert.deepEqual(template.parameters, [{ key: 'c.d' }]); assert.equal(JSON.stringify(template(context)), JSON.stringify(expected)); @@ -587,16 +633,16 @@ describe('json-template', () => { it('should replace array without stringify', () => { const template = parse({ s: '1', - b: '{{c.d}}' + b: '{{c.d}}', }); const context = { c: { - d: ['a', 'b', 'c'] - } + d: ['a', 'b', 'c'], + }, }; const expected = { s: '1', - b: ['a', 'b', 'c'] + b: ['a', 'b', 'c'], }; assert.deepEqual(template.parameters, [{ key: 'c.d' }]); assert.equal(JSON.stringify(template(context)), JSON.stringify(expected)); @@ -636,8 +682,13 @@ describe('json-template', () => { }); it('default value', () => { - const template = parse({ boo: '{{foo.isNull:null}} {{foo.isUndefined:undefined}} {{foo.isNonNull}}' }); - assert.deepStrictEqual(template({ foo: { isNull: null, isNonNull: 'value' } }), { boo: ' undefined value' }); + const template = parse({ + boo: '{{foo.isNull:null}} {{foo.isUndefined:undefined}} {{foo.isNonNull}}', + }); + assert.deepStrictEqual( + template({ foo: { isNull: null, isNonNull: 'value' } }), + { boo: ' undefined value' } + ); }); - }) + }); });