rule
Version:
Coffeescript friendly css selector based templating
595 lines (514 loc) • 24.4 kB
text/coffeescript
Rule = @Rule
expect = @expect
window = @window
document = @document
describe 'Rule', ->
makeNode = (string) ->
container = document.createElement 'div'
container.innerHTML = string
container.childNodes
asString = (object) ->
container = document.createElement 'div'
if object instanceof Array
container.appendChild(node.cloneNode(true)) for node in object
container.innerHTML
else
object.outerHTML
before (done) ->
if module? and @module isnt module
Rule = require('../rule')
expect = require('expect.js')
window = require('sub').window
document = window.document
Rule.env = window
do done
describe '::split', ->
selectors = [undefined, 'div', '.a', '.a-b', '["a=b"]', 'div:nth-child(n)', 'div > span', 'div + span']
attributes = [undefined, 'a', 'a-b']
positions = [undefined, '-', '+', '=', '<', '>']
it "should return [selector, attribute, position]", ->
for selector in selectors
for attribute in attributes
for position in positions
expect(Rule.split (selector ? '')+(if attribute then '@'+attribute else '')+(position ? '')).to.be.eql [selector, attribute, position]
describe '::parse', ->
it "should return the parsed result of the function bound to data", ->
expect(Rule.parse (->@), 'a').to.be.eql 'a'
expect(Rule.parse (->@a), {a:'b'}).to.be.eql 'b'
expect(Rule.parse (->->@a), {a:'b'}).to.be.eql 'b'
a = -> @a
expect(Rule.parse (->a), {a:'b'}).to.be.eql 'b'
it "should return the array with each array item parsed", ->
a = document.createTextNode('a')
b = document.createTextNode('b')
c = document.createTextNode('c')
expect(Rule.parse [a,b,c]).to.be.eql [a,b,c]
expect(Rule.parse [(->@a),(->@b),(->@c)], {a: a, b: b, c: c}).to.be.eql [a,b,c]
it "should return the result of the rule's render function", ->
rule = new Rule
'.a': ->@
selection = makeNode '<div><span class="a"></div>'
Rule.parse rule, 'b', selection: selection
expect(asString selection).to.be.equal asString makeNode '<div><span class="a">b</div>'
selection = makeNode '<div><span class="a"></div>'
rule.template = selection
expect(asString Rule.parse rule, 'b').to.be.eql asString makeNode '<div><span class="a">b</span></div>'
it "should return the passed in HTMLElement", ->
el = makeNode('<div>')[0]
expect(Rule.parse el).to.be el
it "should return the passed in node array as an array", ->
el = makeNode('<div>')
expect((Rule.parse el)).to.eql el
el = makeNode('<div></div><span></span>')
expect((Rule.parse el)).to.eql el
it "should return undefined", ->
expect(Rule.parse undefined).to.be undefined
it "should return null", ->
expect(Rule.parse null).to.be null
it "should return the object's toString results", ->
expect(Rule.parse true).to.be 'true'
expect(Rule.parse false).to.be 'false'
expect(Rule.parse 'abc').to.be 'abc'
expect(Rule.parse 123).to.be '123'
O = ->
O.prototype.toString = -> 'test'
o = new O
expect(Rule.parse o).to.be 'test'
o = {toString: -> 'test'}
expect(Rule.parse o).to.be 'test'
it "should return the results of the object compiled as a new rule with selection as the template", ->
rule =
'.a': ->@
selection = makeNode '<div><span class="a"></div>'
Rule.parse rule, 'b', selection: selection
expect(asString selection).to.be.eql asString makeNode '<div><span class="a">b</div>'
describe '::add', ->
it "should prepend the attribute with content", ->
e = makeNode('<div class="b">')
expect(asString Rule.add 'a', e, 'class', '-').to.be.eql asString makeNode('<div class="a b">')
it "should append the attribute with content", ->
e = makeNode('<div class="a">')
expect(asString Rule.add 'b', e, 'class', '+').to.be.eql asString makeNode('<div class="a b">')
it "should add before the attribute with content", ->
e = makeNode('<div class="b">')
expect(asString Rule.add 'a', e, 'class', '<').to.be.eql asString makeNode('<div class="ab">')
it "should add after the attribute with content", ->
e = makeNode('<div class="a">')
expect(asString Rule.add 'b', e, 'class', '>').to.be.eql asString makeNode('<div class="ab">')
it "should set the attribute to content", ->
e = makeNode('<div class="b">')
expect(asString Rule.add 'a', e, 'class').to.be.eql asString makeNode('<div class="a">')
it "should add content before selection", ->
c = makeNode('<div><span></span></div>')
r = Rule.add 'a', c, null, '<'
expect(asString c).to.be.eql asString makeNode('<div>a<span></span></div>')
expect(asString r).to.be.eql asString makeNode('<div>a<span></span></div>')
it "should add content after selection", ->
c = makeNode('<div><span></span></div>')
r = Rule.add 'a', c, null, '>'
expect(asString c).to.be.eql asString makeNode('<div><span></span>a</div>')
expect(asString r).to.be.eql asString makeNode('<div><span></span>a</div>')
it "should add content as the first child of selection", ->
c = makeNode('<div>')[0]
e = c.appendChild makeNode('<span>')[0]
f = e.appendChild makeNode('<span>')[0]
r = Rule.add 'a', [f], null, '-'
expect(asString r).to.be.eql asString makeNode('a<span></span>')
expect(asString c).to.be.eql asString makeNode('<div><span>a<span></span></span></div>')
it "should add content as the last child of selection", ->
c = makeNode('<div>')[0]
e = c.appendChild makeNode('<span>')[0]
f = e.appendChild makeNode('<span>')[0]
r = Rule.add 'a', [f], null, '+'
expect(asString c).to.be.eql asString makeNode('<div><span><span></span>a</span></div>')
expect(asString r).to.be.eql asString makeNode('<span></span>a')
it "should set content to replace selection", ->
c = makeNode('<div>')[0]
e = c.appendChild makeNode('<span>')[0]
r = Rule.add 'a', [e], null, '='
expect(asString c).to.be.eql asString makeNode('<div>a</div>')
expect(asString r).to.be.eql 'a'
it "should set content as the only child of selection", ->
c = makeNode('<div>')[0]
e = c.appendChild makeNode('<span>')[0]
f = e.appendChild makeNode('<span>')[0]
r = Rule.add 'a', [e]
expect(asString c).to.be.eql asString makeNode('<div><span>a</span></div>')
expect(asString r).to.be.eql asString makeNode('<span>a</span>')
it "should set array of content as children of selection", ->
c = makeNode('<div>')
r = Rule.add ['a','b','c','d'], c
expect(asString c).to.be.eql asString makeNode('<div>abcd</div>')
expect(asString r).to.be.eql asString makeNode('<div>abcd</div>')
it "should set joined array of content as attribute", ->
c = makeNode('<div>')
r = Rule.add ['a','b','c','d'], c, 'class'
expect(asString r).to.be.eql asString makeNode('<div class="abcd"></div>')
describe '.render', ->
# From template and application
it "should clone a template and return that object", ->
template = makeNode('<div>')
rule = new Rule
'': 'test',
template
expect(asString rule.render()).to.be.eql asString makeNode('<div>test</div>')
expect(asString template).to.be.eql asString makeNode('<div>')
it "should alter a template and return that object", ->
template = makeNode('<div>')
rule = new Rule
'': 'test'
expect(asString rule.render {}, template).to.be.eql asString makeNode('<div>test</div>')
expect(template).to.be.equal template
it "should allow text nodes in the template", ->
template = makeNode('<div>test<span class="a"></span>test</div>')
rule = new Rule
'.a': 'test'
expect(asString rule.render {}, template).to.be.eql asString makeNode('<div>test<span class="a">test</span>test</div>')
expect(template).to.be.equal template
it "should execute a template function and render on that generated template", ->
template = -> makeNode('<div>')
rule = new Rule
'': 'test'
expect(asString rule.render {}, template).to.be.eql asString makeNode('<div>test</div>')
expect(template).to.be.equal template
# Attributes
it "should set the attributes of the parent", ->
rule = new Rule
'@class': 'test',
makeNode('<div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div class="test"></div>')
it "should set the attributes of a selection", ->
rule = new Rule
'span@class': 'test',
makeNode('<div><span></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><span class="test"></span></div>')
it "should set the attributes of a subparent", ->
rule = new Rule
'span':
'@class': 'test',
makeNode('<div><span></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><span class="test"></span></div>')
it "should not set the attribute if the content is undefined", ->
rule = new Rule
'@class': 'test',
makeNode('<div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div class="test"></div>')
# Data insertion
it "should set the contents based on a data object", ->
rule = new Rule
'': ->@a,
makeNode('<div>')
expect(asString rule.render {a: 'test'}).to.be.eql asString makeNode('<div>test</div>')
# Selections
it "should set the contents of a simple tag selection", ->
rule = new Rule
'span': 'test',
makeNode('<div><span></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><span>test</span></div>')
it "should replace the contents of a simple tag selection", ->
rule = new Rule
'span': 'test',
makeNode('<div><span><a>a</a><a>b</a><a>c</a></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><span>test</span></div>')
it "should not find the selection and do nothing", ->
rule = new Rule
'span': 'x',
makeNode('<div><a></a></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><a></a></div>')
it "should set the contents of a simple class selection", ->
rule = new Rule
'.simple': 'test',
makeNode('<div><a><span>a</span><h1>x</h1><span class="simple">b</span><span>c</span></a></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><a><span>a</span><h1>x</h1><span class="simple">test</span><span>c</span></a></div>')
# Multiple Selections
it "should set the contents of multiple selections", ->
rule = new Rule
'span': 'test',
makeNode('<div><span></span><span></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><span>test</span><span>test</span></div>')
it "should set the contents of multiple selections on different levels", ->
rule = new Rule
'span': 'test',
makeNode('<div><span></span><a><span></span></a></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><span>test</span><a><span>test</span></a></div>')
# Parent Positioning
it "should add content in the right order and return the added siblings", ->
rule = new Rule
'': 'c'
'>': 'd'
'<': 'b'
makeNode('<span>x</span>')
expect(asString rule.render()).to.be.eql asString makeNode('<span>bcd</span>')
# Selection Positioning
it "should add content before and after a selection", ->
rule = new Rule
'span-': ->makeNode('<a>')[0]
makeNode('<div><span></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><a></a><span></span></div>')
rule = new Rule
'span+': ->makeNode('<a>')[0]
makeNode('<div><span></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><span></span><a></a></div>')
it "should add content to the start and end of a selection", ->
rule = new Rule
'span<': ->makeNode('<a>')[0]
makeNode('<div><span><p></p></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><span><a></a><p></p></span></div>')
rule = new Rule
'span>': ->makeNode('<a>')[0]
makeNode('<div><span><p></p></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><span><p></p><a></a></span></div>')
it "should replace a selection", ->
rule = new Rule
'span=': ->makeNode('<a>')[0]
makeNode('<div><span><p></p></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><a></a></div>')
it "should remove multiple selections", ->
rule = new Rule
'span=': ->''
'span': ->'hi'
makeNode('<div><span><p>test</p></span><span><h1>title</h1></span><span><a>link</a></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div></div>')
it "should replace a selection and not match again", ->
pRan = false
rule = new Rule
'span=': ->''
'span': -> pRan = true
makeNode('<div><span><p></p></span></div>')
rule.render()
expect(pRan).to.be.eql(false)
it "should replace a selection and not execute replaced match", ->
pRan = false
rule = new Rule
'span=': ->makeNode('<a>')[0]
'p': -> pRan = true
makeNode('<div><span><p></p></span></div>')
rule.render()
expect(pRan).to.be.eql(false)
# Multiple Selection Positioning
it "should add content before and after multiple selections", ->
rule = new Rule
'span-': ->makeNode('<a>')[0]
makeNode('<div><span></span><span></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><a></a><span></span><a></a><span></span></div>')
rule = new Rule
'span+': ->makeNode('<a>')[0]
makeNode('<div><span></span><span></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><span></span><a></a><span></span><a></a></div>')
it "should add content to the start and end of multiple selections", ->
rule = new Rule
'span<': ->makeNode('<a>')[0]
makeNode('<div><span><p></p></span><span><h1></h1></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><span><a></a><p></p></span><span><a></a><h1></h1></span></div>')
rule = new Rule
'span>': ->makeNode('<a>')[0]
makeNode('<div><span><p></p></span><span><h1></h1></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><span><p></p><a></a></span><span><h1></h1><a></a></span></div>')
it "should replace multiple selections", ->
rule = new Rule
'span=': ->makeNode('<a>')[0]
makeNode('<div><span><p></p></span><span><h1></h1></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><a></a><a></a></div>')
# Selection Attributes
it "should alter attributes of a selection", ->
rule = new Rule
'span@class': 'test',
makeNode('<div><span></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><span class="test"></span></div>')
it "should alter attributes of multiple selections", ->
rule = new Rule
'span@class': 'test'
makeNode('<div><span></span><span></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><span class="test"></span><span class="test"></span></div>')
# Sub Rules
it "should select into a new scope and apply a new rule object to it", ->
rule = new Rule
'a':
'span': 'c',
makeNode('<div><a><span>b</span></a></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><a><span>c</span></a></div>')
it "should select into a new scope and not find the selection in the new context", ->
rule = new Rule
'a':
'div': 'c',
makeNode('<div><a><span>b</span></a><div></div></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><a><span>b</span></a><div></div></div>')
it "should select into a new scope and do nothing", ->
rule = new Rule
'': {},
makeNode('<div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div></div>')
it "should remove multiple selections", ->
rule = new Rule
'.a=': ''
makeNode('<div><span class="a">b</span><span class="a">c</span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div></div>')
it "should remove a selection then attempt to add to it", ->
rule = new Rule
'a':
'=': ''
'': 'c',
makeNode('<div><a>b</a></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div></div>')
it "should not remove a selection then add to it", ->
rule = new Rule
'a':
'=': null
'': 'c',
makeNode('<div><a>b</a></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><a>c</a></div>')
it "should remove a selection then attempt to add an attribute to it", ->
rule = new Rule
'a':
'=': ''
'@href': 'c',
makeNode('<div><a>b</a></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div></div>')
it "should remove a selection then attempt to add to a child of it", ->
rule = new Rule
'a':
'=': ''
'span': 'c',
makeNode('<div><a><span></span></a></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div></div>')
it "should add a sibling to a selection then add to the root", ->
rule = new Rule
'a':
'+': ->makeNode('<a>')[0]
'': 'c'
makeNode('<div><a>b</a></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><a>c</a><a></a></div>')
# Arrays
it "should set the contents to the result of an array of functions", ->
rule = new Rule
'span': [(->@a), (->@b), (->@c)],
makeNode('<div><span></span></div>')
expect(asString rule.render {a:'x',b:'y',c:'z'}).to.be.eql asString makeNode('<div><span>xyz</span></div>')
it "should set the contents to the result of a function that returns an array of functions", ->
rule = new Rule
'span': -> ((i)->i*@x).bind(@, i) for i in [1...5],
makeNode('<div><span></span></div>')
expect(asString rule.render {x: 2}).to.be.eql asString makeNode('<div><span>2468</span></div>')
# Class extension
it "should extend the Rule class then render off it", ->
class Test extends Rule
rule:
'': 'test'
template:
makeNode('<div></div>')
expect(asString (new Test).render()).to.be.eql asString makeNode('<div>test</div>')
it "should extend the Rule class while providing a rule property", ->
class Test extends Rule
rule:
'': 'test'
template:
makeNode('<div></div>')
test = new Test
'>': 'test2'
expect(asString test.render()).to.be.eql asString makeNode('<div>test2</div>')
it "should extend the Rule class then set the contents of a nested element", ->
class Test extends Rule
rule:
'.test':
'': 'test'
template:
makeNode('<div><p><span class="test"></span></p></div>')
expect(asString (new Test).render()).to.be.eql asString makeNode('<div><p><span class="test">test</span></p></div>')
it "should extend the Rule class then replace the contents of a nested element", ->
class Test extends Rule
rule:
'.test':
'=': 'test'
template:
makeNode('<div><p><span class="test"></span></p></div>')
expect(asString (new Test).render()).to.be.eql asString makeNode('<div><p>test</p></div>')
it "should extend an extended Rule, and then apply both rules in order of oldest first", ->
class Test extends Rule
rule:
'.test': 'X'
'.test2': 'X'
template:
makeNode('<div><p><span class="test"></span><span class="test2"></span></p></div>')
class Test2 extends Test
rule:
'.test2': 'Y'
expect(asString (new Test2).render()).to.be.eql asString makeNode('<div><p><span class="test">X</span><span class="test2">Y</span></p></div>')
it "should extend an extended Rule, and and overwrite the order of the overwritten rule", ->
class Test extends Rule
rule:
'.test': 'X'
'.test2': 'Y'
template:
makeNode('<div><p><span class="test test2"></span></p></div>')
class Test2 extends Test
rule:
'.test': 'X'
expect(asString (new Test2).render()).to.be.eql asString makeNode('<div><p><span class="test test2">X</span></p></div>')
it "should extend an extended Rule twice, and then apply both rules in order of oldest first", ->
class Test extends Rule
rule:
'.test': 'X'
'.test2': 'X'
'.test3': 'X'
template:
makeNode('<div><p><span class="test"></span><span class="test2"></span><span class="test3"></span></p></div>')
class Test2 extends Test
rule:
'.test2': 'Y'
'.test3': 'Y'
class Test3 extends Test2
rule:
'.test3': 'Z'
expect(asString (new Test3).render()).to.be.eql asString makeNode('<div><p><span class="test">X</span><span class="test2">Y</span><span class="test3">Z</span></p></div>')
it "should extend an extended Rule, and then apply just the childmost overwritten rule", ->
class Test extends Rule
rule:
'.test<': 'X'
template:
makeNode('<div><p><span class="test"></span></p></div>')
class Test2 extends Test
rule:
'.test<': 'Y'
expect(asString (new Test2).render()).to.be.eql asString makeNode('<div><p><span class="test">Y</span></p></div>')
it "should extend the Rule class and overwrite the parse function", ->
class Test extends Rule
@parse: (rule, data, selection, context) ->
if typeof rule is 'string' or rule instanceof String
return rule+'suffix'
super
rule = new Test
'.test': 'X'
makeNode('<div><span class="test"></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><span class="test">Xsuffix</span></div>')
it "should extend the Rule class and overwrite the parse function and parse other rules that haven't been blocked", ->
class Class
class Test extends Rule
@parse: (rule, data, selection, context) ->
if rule instanceof Class
return 'class'
super
rule = new Test
'.test': new Class
'.test2': 'test'
makeNode('<div><span class="test"></span><span class="test2"></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><span class="test">class</span><span class="test2">test</span></div>')
it "should extend the Rule class with a custom parser and use that parser when a new rule is created from a rule object", ->
class Test extends Rule
@parse: (rule, data, selection, context) ->
if typeof rule is 'string' or rule instanceof String
return rule+'suffix'
super
rule = new Test
'.test':
'': 'X'
makeNode('<div><span class="test"></span></div>')
expect(asString rule.render()).to.be.eql asString makeNode('<div><span class="test">Xsuffix</span></div>')
it "should fail a rule without crashing", ->
rule = new Rule
'@class+': -> a.b
makeNode('<div>')
# Temporarily silence console.error
temp = console.error
console.error = ->
expect(asString rule.render()).to.not.throwError
console.error = temp