From 5c86bb000d47fd0070ad1bc8115146d1ad1283f6 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 28 Mar 2017 21:01:21 +0530 Subject: [PATCH] feat(tag): add unless tag unless tag is opposite of if tag --- src/Tags/UnlessTag.js | 104 ++++++++++++++++ src/Tags/index.js | 3 +- test/unit/tags/if.spec.js | 4 +- test/unit/tags/unless.spec.js | 228 ++++++++++++++++++++++++++++++++++ 4 files changed, 336 insertions(+), 3 deletions(-) create mode 100644 src/Tags/UnlessTag.js create mode 100644 test/unit/tags/unless.spec.js diff --git a/src/Tags/UnlessTag.js b/src/Tags/UnlessTag.js new file mode 100644 index 0000000..e761096 --- /dev/null +++ b/src/Tags/UnlessTag.js @@ -0,0 +1,104 @@ +'use strict' + +/* + * edge + * + * (c) Harminder Virk + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. +*/ + +const BaseTag = require('./BaseTag') + +/** + * The official unless tag. It is used + * as `@unless` inside templates. + * + * @class UnlessTag + * @extends {BaseTag} + * @static + */ +class UnlessTag extends BaseTag { + /** + * The tag name to used for registering the tag + * + * @attribute tagName + * + * @return {String} + */ + get tagName () { + return 'unless' + } + + /** + * Whether tag is a block level tag or + * not. + * + * @attribute isBlock + * + * @return {Boolean} + */ + get isBlock () { + return true + } + + /** + * The expressions allowed to be passed to the + * tag. Any other expressions will cause an + * error. + * + * @attribute allowedExpressions + * + * @return {Array} + */ + get allowedExpressions () { + return ['BinaryExpression', 'Literal', 'Identifier', 'CallExpression', 'MemberExpression', 'UnaryExpression'] + } + + /** + * Compile the template and write to the buffer. + * + * @method compile + * + * @param {Object} compiler + * @param {Object} lexer + * @param {Object} buffer + * @param {String} options.body + * @param {Array} options.childs + * @param {Number} options.lineno + * + * @return {void} + */ + compile (compiler, lexer, buffer, { body, childs, lineno }) { + const compiledStatement = this._compileStatement(lexer, body, lineno).toStatement() + + /** + * Open if opposite of tag + */ + buffer.writeLine(`if (!(${compiledStatement})) {`) + buffer.indent() + + /** + * Re-parse all childs via compiler. + */ + childs.forEach((child) => compiler.parseLine(child)) + + /** + * Close the opposite if tag + */ + buffer.dedent() + buffer.writeLine('}') + } + + /** + * Nothing needs to be in done in runtime for + * an if tag. + * + * @method run + */ + run () { + } +} + +module.exports = UnlessTag diff --git a/src/Tags/index.js b/src/Tags/index.js index 4a028f4..963a840 100644 --- a/src/Tags/index.js +++ b/src/Tags/index.js @@ -20,5 +20,6 @@ module.exports = { sectionTag: new (require('./SectionTag'))(), yieldTag: new (require('./YieldTag'))(), debugger: new (require('./DebuggerTag'))(), - raw: new (require('./RawTag'))() + raw: new (require('./RawTag'))(), + unless: new (require('./UnlessTag'))() } diff --git a/test/unit/tags/if.spec.js b/test/unit/tags/if.spec.js index 591c4ae..0066b55 100644 --- a/test/unit/tags/if.spec.js +++ b/test/unit/tags/if.spec.js @@ -121,7 +121,7 @@ test.group('Tags | If ', (group) => { }).bind(this)()`) }) - test('parse block with arithmatic expression', (assert) => { + test('parse block with arithmetic expression', (assert) => { const statement = dedent` @if(2 + 2)

It is {{ 2 + 2 }}

@@ -139,7 +139,7 @@ test.group('Tags | If ', (group) => { }).bind(this)()`) }) - test('parse block with arithmatic and binary expression expression', (assert) => { + test('parse block with arithmetic and binary expression expression', (assert) => { const statement = dedent` @if(2 + 2 === cartTotal)

Hello {{ cartTotal }}

diff --git a/test/unit/tags/unless.spec.js b/test/unit/tags/unless.spec.js new file mode 100644 index 0000000..d9e1c57 --- /dev/null +++ b/test/unit/tags/unless.spec.js @@ -0,0 +1,228 @@ +'use strict' + +/* + * adonis-edge + * + * (c) Harminder Virk + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. +*/ + +const test = require('japa') +const Template = require('../../../src/Template') +const dedent = require('dedent-js') + +test.group('Tags | Unless ', (group) => { + group.before(() => { + require('../../../test-helpers/transform-tags')(this, require('../../../src/Tags')) + }) + + test('parse simple unless block to compiled template', (assert) => { + const statement = dedent` + @unless(username === 'virk') +

Hello virk

+ @endunless + ` + const template = new Template(this.tags) + const output = template.compileString(statement) + assert.equal(output, dedent` + return (function templateFn () { + let out = new String() + if (!(this.context.resolve('username') === 'virk')) { + out += \`

Hello virk

\\n\` + } + return out + }).bind(this)()`) + }) + + test('parse block with else', (assert) => { + const statement = dedent` + @unless(username === 'virk') +

Hello virk

+ @else +

Hello anonymous

+ @endunless` + + const template = new Template(this.tags) + const output = template.compileString(statement) + assert.equal(output, dedent` + return (function templateFn () { + let out = new String() + if (!(this.context.resolve('username') === 'virk')) { + out += \`

Hello virk

\\n\` + } else { + out += \`

Hello anonymous

\\n\` + } + return out + }).bind(this)() + `) + }) + + test('parse block with literal inside unless', (assert) => { + const statement = dedent` + @unless('virk') +

Hello virk

+ @endunless` + + const template = new Template(this.tags) + const output = template.compileString(statement) + assert.equal(output, dedent` + return (function templateFn () { + let out = new String() + if (!('virk')) { + out += \`

Hello virk

\\n\` + } + return out + }).bind(this)()`) + }) + + test('parse block with identifier inside unless', (assert) => { + const statement = dedent` + @unless(username) +

Hello {{ username }}

+ @endunless` + + const template = new Template(this.tags) + const output = template.compileString(statement) + assert.equal(output, dedent` + return (function templateFn () { + let out = new String() + if (!(this.context.resolve('username'))) { + out += \`

Hello \${this.context.escape(this.context.resolve('username'))}

\\n\` + } + return out + }).bind(this)()`) + }) + + test('parse block with arithmetic expression', (assert) => { + const statement = dedent` + @unless(2 + 2) +

It is {{ 2 + 2 }}

+ @endunless` + + const template = new Template(this.tags) + const output = template.compileString(statement) + assert.equal(output, dedent` + return (function templateFn () { + let out = new String() + if (!(2 + 2)) { + out += \`

It is \${this.context.escape(2 + 2)}

\\n\` + } + return out + }).bind(this)()`) + }) + + test('parse block with arithmatic and binary expression expression', (assert) => { + const statement = dedent` + @unless(2 + 2 === cartTotal) +

Hello {{ cartTotal }}

+ @endunless` + + const template = new Template(this.tags) + const output = template.compileString(statement) + assert.equal(output, dedent` + return (function templateFn () { + let out = new String() + if (!(2 + 2 === this.context.resolve('cartTotal'))) { + out += \`

Hello \${this.context.escape(this.context.resolve('cartTotal'))}

\\n\` + } + return out + }).bind(this)()`) + }) + + test('parse when a function has been passed', (assert) => { + const statement = dedent` + @unless(count(users)) +

There are {{ count(users) }}

+ @endunless` + + const template = new Template(this.tags) + const output = template.compileString(statement) + assert.equal(output, dedent` + return (function templateFn () { + let out = new String() + if (!(this.context.callFn('count', [this.context.resolve('users')]))) { + out += \`

There are \${this.context.escape(this.context.callFn('count', [this.context.resolve('users')]))}

\\n\` + } + return out + }).bind(this)()`) + }) + + test('parse when a native function is called', (assert) => { + const statement = dedent` + @unless(users.indexOf('virk') > -1) +

Hello {{ users['virk'] }}

+ @endunless` + + const template = new Template(this.tags) + const output = template.compileString(statement) + assert.equal(output, dedent` + return (function templateFn () { + let out = new String() + if (!(this.context.resolve('users').indexOf('virk') > -1)) { + out += \`

Hello \${this.context.escape(this.context.accessChild(this.context.resolve('users'), ['virk']))}

\\n\` + } + return out + }).bind(this)()`) + }) + + test('parse when a property accessor is passed', (assert) => { + const statement = dedent` + @unless(user.isLoggedIn) +

Hello {{ user.username }}

+ @endunless` + + const template = new Template(this.tags) + const output = template.compileString(statement) + assert.equal(output, dedent` + return (function templateFn () { + let out = new String() + if (!(this.context.accessChild(this.context.resolve('user'), ['isLoggedIn']))) { + out += \`

Hello \${this.context.escape(this.context.accessChild(this.context.resolve('user'), ['username']))}

\\n\` + } + return out + }).bind(this)()`) + }) + + test('throw exception when assignment expression is passed', (assert) => { + const statement = dedent` + @unless(age = 22) +

You are 22 years old

+ @endunless` + + const template = new Template(this.tags) + const output = () => template.compileString(statement) + assert.throw(output, 'lineno:1 charno:0 E_INVALID_EXPRESSION: Invalid expression passed to (unless) block') + }) + + test('throw exception when sequence expression is passed', (assert) => { + const statement = dedent` + @unless(age, username) +

You are 22 years old

+ @endunless` + + const template = new Template(this.tags) + const output = () => template.compileString(statement) + assert.throw(output, 'lineno:1 charno:0 E_INVALID_EXPRESSION: Invalid expression passed to (unless) block') + }) + + test('should work with unary expression', (assert) => { + const statement = dedent` + @unless(!age) +

Please type your age

+ @endunless` + + const template = new Template(this.tags) + const output = template.compileString(statement) + assert.equal(output, dedent` + return (function templateFn () { + let out = new String() + if (!(!this.context.resolve('age'))) { + out += \`

Please type your age

\\n\` + } + return out + }).bind(this)() + `) + }) +})