caveman
Version:
A fast JS templating engine.
473 lines (451 loc) • 13.6 kB
text/coffeescript
Caveman = require('../caveman.js')
beforeEach ->
Caveman.options.openTag = '{{'
Caveman.options.closeTag = '}}'
Caveman.options.shrinkWrap = true
Caveman.options.escapeByDefault = false
describe 'Macros', ->
template = '<ul>{{- for d.uls as ul }}<li>{{ul.li}}</li>{{/}}</ul>'
Caveman.register('unorderedList', template)
template = '{{- for d.divs as div }}<div>{{- render unorderedList div }}</div>{{/}}'
Caveman.register('listOfDivs', template)
it 'if with truthy comparison', ->
data = {
tests: {
boolTrue: true
boolFalse: false
zero: 0
one: 1
emptyString: ''
emptyObject: {}
emptyArray: []
}
}
template = """
{{- each d.tests }}
{{- if d }}
<truthy>{{_key}}</truthy>
{{- end }}
{{- end }}
"""
expected = '<truthy>boolTrue</truthy>' +
'<truthy>one</truthy>' +
'<truthy>emptyObject</truthy>' +
'<truthy>emptyArray</truthy>'
expect(Caveman(template, data)).toEqual(expected)
it 'if with strict comparison', ->
data = {
emptyString: ''
emptyArray: []
arrayWithOne: [1]
}
template = """
{{- if d.emptyString === '' }}
<true>d.emptyString === ''</true>
{{- end }}
{{- if d.emptyArray.length === 0 }}
<true>d.emptyArray.length === 0</true>
{{- end }}
{{- if d.arrayWithOne.length > 0 }}
<true>d.arrayWithOne.length > 0</true>
{{- end }}
"""
expected = "<true>d.emptyString === ''</true>" +
'<true>d.emptyArray.length === 0</true>' +
'<true>d.arrayWithOne.length > 0</true>'
expect(Caveman(template, data)).toEqual(expected)
it 'unless with falsey comparison', ->
data = {
tests: {
boolTrue: true
boolFalse: false
zero: 0
one: 1
emptyString: ''
emptyObject: {}
emptyArray: []
}
}
template = """
{{- each d.tests }}
{{- unless d }}
<falsey>{{_key}}</falsey>
{{- end }}
{{- end }}
"""
expected = '<falsey>boolFalse</falsey>' +
'<falsey>zero</falsey>' +
'<falsey>emptyString</falsey>'
expect(Caveman(template, data)).toEqual(expected)
it 'shortcuts', ->
data = {
tests: {
boolTrue: true
boolFalse: false
zero: 0
one: 1
emptyString: ''
emptyObject: {}
emptyArray: []
}
}
template = """
{{? d.tests.boolTrue }}
<span>boolTrue</span>
{{/}}
{{?d.tests.one === 1}}
<span>boolTrue</span>
{{/}}
{{^ d.tests.emptyString }}
<span>emptyString</span>
{{/}}
{{^d.tests.zero > 0}}
<span>zero</span>
{{/}}
"""
expected = '<span>boolTrue</span>' +
'<span>boolTrue</span>' +
'<span>emptyString</span>' +
'<span>zero</span>'
expect(Caveman(template, data)).toEqual(expected)
it 'for', ->
data = {
nullValue: null
users: [
{ name: 'Jimmy' }
{ name: 'Ralph' }
{ name: 'Joe' }
]
}
template = """
<div class="users">
{{- for d.nullValue }}
<!-- Should handle non-array values -->
{{- end }}
{{- for d.undef }}
<!-- Should handle non-array values -->
{{- end }}
{{- for d.users }}
{{- for d }}
<!-- Won't work for objects either -->
{{- end }}
<div class="user">{{d.name}}</div>
{{- end }}
</div>
"""
expected = '<div class="users">' +
'<div class="user">Jimmy</div>' +
'<div class="user">Ralph</div>' +
'<div class="user">Joe</div>' +
'</div>'
expect(Caveman(template, data)).toEqual(expected)
it 'nested for', ->
data = {
posts: [
{
title: 'My Blog Post'
comments: ['First!', 'Great!', 'I hate this.']
images: [
{ src: '/image1.jpg' }
]
}
{
title: 'My Crappy Blog Post'
comments: ['Crickets.']
images: [
{ src: '/image1.jpg' },
{ src: '/image2.jpg', alt: 'My Alt Text' },
{ src: '/image3.jpg' }
]
}
]
}
template = """
<div class="posts">
{{- for d.posts as post }}
<div class="post">
<h1>{{post.title}}</h1>
<div class="images">
{{- for post.images as image}}
<img src="{{image.src}}" alt="{{image.alt}}" />
{{- end }}
</div>
<div class="comments">
{{- for post.comments as comment}}
<div class="comment">{{comment}}</div>
{{- end }}
</div>
</div>
{{- end }}
</div>
"""
expected = '' +
'<div class="posts">' +
'<div class="post">' +
'<h1>My Blog Post</h1>' +
'<div class="images">' +
'<img src="/image1.jpg" alt="" />' +
'</div>' +
'<div class="comments">' +
'<div class="comment">First!</div>' +
'<div class="comment">Great!</div>' +
'<div class="comment">I hate this.</div>' +
'</div>' +
'</div>' +
'<div class="post">' +
'<h1>My Crappy Blog Post</h1>' +
'<div class="images">' +
'<img src="/image1.jpg" alt="" />' +
'<img src="/image2.jpg" alt="My Alt Text" />' +
'<img src="/image3.jpg" alt="" />' +
'</div>' +
'<div class="comments">' +
'<div class="comment">Crickets.</div>' +
'</div>' +
'</div>' +
'</div>'
expect(Caveman(template, data)).toEqual(expected)
it 'for blocks inside partials inside for blocks', ->
data = {
divs: [
{ uls: [ { li: 'a1' }, { li: 'a2' } ] }
{ uls: [ { li: 'b1' }, { li: 'b2' } ] }
{ uls: [ { li: 'c1' }, { li: 'c2' } ] }
]
}
template = '<div>{{- for d.divs as div }}{{- for div.uls as ul }}{{ul.li}}{{/}}{{/}}</div><div>{{- render listOfDivs }}</div>'
expected = '<div>a1a2b1b2c1c2</div>' +
'<div>' +
'<div><ul><li>a1</li><li>a2</li></ul></div>' +
'<div><ul><li>b1</li><li>b2</li></ul></div>' +
'<div><ul><li>c1</li><li>c2</li></ul></div>' +
'</div>'
expect(Caveman(template, data)).toEqual(expected)
it 'deeply nested for blocks with if blocks', ->
data = {
arr: [ [ [ [ [ 'a1', 'a2', 'a3' ], [ 'b1', 'b2', 'b3' ] ] ] ] ]
}
template = """
{{- if d.arr }}
{{- for d.arr }}
{{_i}},
{{- for d }}
{{_i}},
{{- for d }}
{{_i}},
{{- if d }}
{{- for d }}
{{_i}},
{{- for d }}
{{d}},
{{/}}
{{/}}
{{/}}
{{/}}
{{/}}
{{/}}
{{- for d.arr as a }}
{{- for a as a }}
{{- for a as a }}
{{- for a as a }}
{{- if d }}
{{- for a as a }}
{{a}}
{{/}}
{{/}}
{{/}}
{{/}}
{{/}}
{{/}}
{{/}}
"""
expected = '0,0,0,0,a1,a2,a3,1,b1,b2,b3,a1a2a3b1b2b3'
expect(Caveman(template, data)).toEqual(expected)
it 'each', ->
data = {
car: {
make: 'Volvo'
model: '245s'
year: 1976
color: 'Orange'
}
}
template = """
<div class="attributes">
{{- each d.car }}
<div class="attribute">{{_key}}: {{d}}</div>
{{- end }}
</div>
"""
expected = '<div class="attributes">' +
'<div class="attribute">make: Volvo</div>' +
'<div class="attribute">model: 245s</div>' +
'<div class="attribute">year: 1976</div>' +
'<div class="attribute">color: Orange</div>' +
'</div>'
expect(Caveman(template, data)).toEqual(expected)
it 'nested each', ->
data = {
posts: [
{
title: 'My Blog Post'
comments: ['First!', 'Great!', 'I hate this.']
images: [
{ src: '/image1.jpg' }
]
}
{
title: 'My Crappy Blog Post'
comments: ['Crickets.']
images: [
{ src: '/image1.jpg' },
{ src: '/image2.jpg', alt: 'My Alt Text' },
{ src: '/image3.jpg' }
]
}
]
}
template = """
<div class="posts">
{{- each d.posts as post }}
<div class="post">
<h1>{{post.title}}</h1>
<div class="images">
{{- each post.images as image}}
<img src="{{image.src}}" alt="{{image.alt}}" />
{{- end }}
</div>
<div class="comments">
{{- each post.comments as comment}}
{{- if _key === 0 }}
<span>{{d.posts.length}} posts</span>
{{- end }}
<div class="comment">{{comment}}</div>
{{- end }}
</div>
</div>
{{- end }}
</div>
"""
expected = '' +
'<div class="posts">' +
'<div class="post">' +
'<h1>My Blog Post</h1>' +
'<div class="images">' +
'<img src="/image1.jpg" alt="" />' +
'</div>' +
'<div class="comments">' +
'<span>2 posts</span>' +
'<div class="comment">First!</div>' +
'<div class="comment">Great!</div>' +
'<div class="comment">I hate this.</div>' +
'</div>' +
'</div>' +
'<div class="post">' +
'<h1>My Crappy Blog Post</h1>' +
'<div class="images">' +
'<img src="/image1.jpg" alt="" />' +
'<img src="/image2.jpg" alt="My Alt Text" />' +
'<img src="/image3.jpg" alt="" />' +
'</div>' +
'<div class="comments">' +
'<span>2 posts</span>' +
'<div class="comment">Crickets.</div>' +
'</div>' +
'</div>' +
'</div>'
expect(Caveman(template, data)).toEqual(expected)
it 'with', ->
data = {
foo: {
a: 123
b: 234
}
}
template = """
{{- with d.foo }}
<span>a: {{d.a}}</span>
<span>b: {{d.b}}</span>
{{- end }}
{{- with d.foo as foo }}
<span>a: {{foo.a}}</span>
<span>b: {{foo.b}}</span>
{{- end }}
"""
expected = '<span>a: 123</span><span>b: 234</span><span>a: 123</span><span>b: 234</span>'
expect(Caveman(template, data)).toEqual(expected)
it 'script execution', ->
data = {
rows: [ 1, 2, 3, 4, 5 ]
}
template = """
<table>
{{- for d.rows }}
{{- everyThirdStripe = [_i % 3 ? '': 'zebra-stripe'] }}
<tr class="{{everyThirdStripe}}">
<td>{{d}}</td>
</tr>
{{- end }}
</table>
"""
expected = '<table>' +
'<tr class="zebra-stripe"><td>1</td></tr>' +
'<tr class=""><td>2</td></tr>' +
'<tr class=""><td>3</td></tr>' +
'<tr class="zebra-stripe"><td>4</td></tr>' +
'<tr class=""><td>5</td></tr>' +
'</table>'
expect(Caveman(template, data)).toEqual(expected)
it 'print', ->
data = {
rows: [ 1, 2, 3 ]
}
template = """
{{- for d.rows }}
<div>{{d}} x {{d}} = {{- print d * d }}</div>
{{- end }}
"""
expected = '<div>1 x 1 = 1</div>' +
'<div>2 x 2 = 4</div>' +
'<div>3 x 3 = 9</div>'
expect(Caveman(template, data)).toEqual(expected)
describe 'Escaping', ->
it 'does not escape by default', ->
data = {
html: '<script>alert("HELLO XSS!");</script> & \''
}
template = '<div>{{d.html}}</div>'
expected = '<div><script>alert("HELLO XSS!");</script> & \'</div>'
expect(Caveman(template, data)).toEqual(expected)
it 'escape macro', ->
data = {
html: '<script>alert("HELLO XSS!");</script> & \'',
zero: 0
}
template = '<div>{{- escape d.html }}{{- escape d.zero }}</div>'
expected = '<div><script>alert("HELLO XSS!");</script> & '0</div>'
expect(Caveman(template, data)).toEqual(expected)
it 'escape macro will not double-escape if escapeByDefault is true', ->
data = {
html: '<script>alert("HELLO XSS!");</script> & \''
}
template = '<div>{{- escape d.html }}</div>'
expected = '<div><script>alert("HELLO XSS!");</script> & '</div>'
Caveman.options.escapeByDefault = true
expect(Caveman(template, data)).toEqual(expected)
it 'escapes by default using the escapeByDefault option', ->
data = {
html: '<script>alert("HELLO XSS!");</script> & \''
}
template = '<div>{{d.html}}</div>'
expected = '<div><script>alert("HELLO XSS!");</script> & '</div>'
Caveman.options.escapeByDefault = true
expect(Caveman(template, data)).toEqual(expected)
it 'unescape overrides escapeByDefault', ->
data = {
html: '<script>alert("HELLO XSS!");</script> & \''
}
template = '<div>{{- unescape d.html }}</div>'
expected = '<div><script>alert("HELLO XSS!");</script> & \'</div>'
Caveman.options.escapeByDefault = true
expect(Caveman(template, data)).toEqual(expected)