Skip to content

Commit

Permalink
feat(tag): add unless tag
Browse files Browse the repository at this point in the history
unless tag is opposite of if tag
  • Loading branch information
thetutlage committed Mar 28, 2017
1 parent 3385d5b commit 5c86bb0
Show file tree
Hide file tree
Showing 4 changed files with 336 additions and 3 deletions.
104 changes: 104 additions & 0 deletions src/Tags/UnlessTag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
'use strict'

/*
* edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* 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
3 changes: 2 additions & 1 deletion src/Tags/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'))()
}
4 changes: 2 additions & 2 deletions test/unit/tags/if.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
<p> It is {{ 2 + 2 }} </p>
Expand All @@ -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)
<p> Hello {{ cartTotal }} </p>
Expand Down
228 changes: 228 additions & 0 deletions test/unit/tags/unless.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
'use strict'

/*
* adonis-edge
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* 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')
<p> Hello virk </p>
@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 += \` <p> Hello virk </p>\\n\`
}
return out
}).bind(this)()`)
})

test('parse block with else', (assert) => {
const statement = dedent`
@unless(username === 'virk')
<p> Hello virk </p>
@else
<p> Hello anonymous </p>
@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 += \` <p> Hello virk </p>\\n\`
} else {
out += \` <p> Hello anonymous </p>\\n\`
}
return out
}).bind(this)()
`)
})

test('parse block with literal inside unless', (assert) => {
const statement = dedent`
@unless('virk')
<p> Hello virk </p>
@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 += \` <p> Hello virk </p>\\n\`
}
return out
}).bind(this)()`)
})

test('parse block with identifier inside unless', (assert) => {
const statement = dedent`
@unless(username)
<p> Hello {{ username }} </p>
@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 += \` <p> Hello \${this.context.escape(this.context.resolve('username'))} </p>\\n\`
}
return out
}).bind(this)()`)
})

test('parse block with arithmetic expression', (assert) => {
const statement = dedent`
@unless(2 + 2)
<p> It is {{ 2 + 2 }} </p>
@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 += \` <p> It is \${this.context.escape(2 + 2)} </p>\\n\`
}
return out
}).bind(this)()`)
})

test('parse block with arithmatic and binary expression expression', (assert) => {
const statement = dedent`
@unless(2 + 2 === cartTotal)
<p> Hello {{ cartTotal }} </p>
@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 += \` <p> Hello \${this.context.escape(this.context.resolve('cartTotal'))} </p>\\n\`
}
return out
}).bind(this)()`)
})

test('parse when a function has been passed', (assert) => {
const statement = dedent`
@unless(count(users))
<p> There are {{ count(users) }} </p>
@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 += \` <p> There are \${this.context.escape(this.context.callFn('count', [this.context.resolve('users')]))} </p>\\n\`
}
return out
}).bind(this)()`)
})

test('parse when a native function is called', (assert) => {
const statement = dedent`
@unless(users.indexOf('virk') > -1)
<p> Hello {{ users['virk'] }} </p>
@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 += \` <p> Hello \${this.context.escape(this.context.accessChild(this.context.resolve('users'), ['virk']))} </p>\\n\`
}
return out
}).bind(this)()`)
})

test('parse when a property accessor is passed', (assert) => {
const statement = dedent`
@unless(user.isLoggedIn)
<p> Hello {{ user.username }} </p>
@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 += \` <p> Hello \${this.context.escape(this.context.accessChild(this.context.resolve('user'), ['username']))} </p>\\n\`
}
return out
}).bind(this)()`)
})

test('throw exception when assignment expression is passed', (assert) => {
const statement = dedent`
@unless(age = 22)
<p> You are 22 years old </p>
@endunless`

const template = new Template(this.tags)
const output = () => template.compileString(statement)
assert.throw(output, 'lineno:1 charno:0 E_INVALID_EXPRESSION: Invalid expression <age = 22> passed to (unless) block')
})

test('throw exception when sequence expression is passed', (assert) => {
const statement = dedent`
@unless(age, username)
<p> You are 22 years old </p>
@endunless`

const template = new Template(this.tags)
const output = () => template.compileString(statement)
assert.throw(output, 'lineno:1 charno:0 E_INVALID_EXPRESSION: Invalid expression <age, username> passed to (unless) block')
})

test('should work with unary expression', (assert) => {
const statement = dedent`
@unless(!age)
<h2> Please type your age </h2>
@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 += \` <h2> Please type your age </h2>\\n\`
}
return out
}).bind(this)()
`)
})
})

0 comments on commit 5c86bb0

Please sign in to comment.