@danielkalen/simplybind
Version:
Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.
458 lines (328 loc) • 15.6 kB
text/coffeescript
suite "Data Binding", ()->
suiteSetup(restartSandbox)
import ./placeholders
import ./throttling
import ./delaying
suite "Binding-Type Specific", ()->
import ./type-DOMValue
import ./type-DOMCheckbox
import ./type-DOMRadio
import ./type-DOMAttr
import ./type-Function
import ./type-Array
import ./type-Event
import ./type-Proxy
import ./type-ObjectProp
suite "Group Bindings", ()->
import ./group-basic
import ./group-transforms
import ./group-conditions
import ./group-misc
import ./group-unbinding
test "Infinite loops should not occur in looping update chains", ()->
SimplyBind.defaultOptions.updateEvenIfSame = true
SimplyBind('prop').of(objectA)
.to('prop').of(objectB).bothWays()
.chainTo('prop').of(objectC).bothWays()
.chainTo('prop').of(objectA).bothWays()
objectA.prop = 'from objectA'
expect(objectA.prop).to.equal 'from objectA'
expect(objectB.prop).to.equal 'from objectA'
expect(objectC.prop).to.equal 'from objectA'
objectB.prop = 'from objectB'
expect(objectA.prop).to.equal 'from objectB'
expect(objectB.prop).to.equal 'from objectB'
expect(objectC.prop).to.equal 'from objectB'
SimplyBind('array:list').of(objectA)
.to('array:list').of(objectB).bothWays()
.chainTo('array:list').of(objectC).bothWays()
.chainTo('array:list').of(objectA).bothWays()
expect(objectA.list.length).to.equal 10
expect(objectB.list.length).to.equal 10
expect(objectC.list.length).to.equal 10
expect(objectA.list).not.to.equal(objectB.list)
expect(objectA.list).to.eql(objectB.list)
expect(objectB.list).to.eql(objectC.list)
objectA.list.pop()
expect(objectA.list.length).to.equal 9
expect(objectB.list.length).to.equal 9
expect(objectC.list.length).to.equal 9
expect(objectA.list).not.to.equal(objectB.list)
expect(objectA.list).to.eql(objectB.list)
expect(objectB.list).to.eql(objectC.list)
objectB.list.pop()
expect(objectA.list.length).to.equal 8
expect(objectB.list.length).to.equal 8
expect(objectC.list.length).to.equal 8
expect(objectA.list).not.to.equal(objectB.list)
expect(objectA.list).to.eql(objectB.list)
expect(objectB.list).to.eql(objectC.list)
objectC.list.push(1)
expect(objectA.list.length).to.equal 9
expect(objectB.list.length).to.equal 9
expect(objectC.list.length).to.equal 9
expect(objectA.list).not.to.equal(objectB.list)
expect(objectA.list).to.eql(objectB.list)
expect(objectB.list).to.eql(objectC.list)
if isBrowser
SimplyBind('attr:someattr').of(regA)
.to('attr:someattr').of(regB).bothWays()
.chainTo('attr:someattr').of(regC).bothWays()
.chainTo('attr:someattr').of(regA).bothWays()
SimplyBind('attr:someattr').of(regA).set('from regElementA')
expect(regA.getAttribute 'someattr').to.equal 'from regElementA'
expect(regB.getAttribute 'someattr').to.equal 'from regElementA'
expect(regC.getAttribute 'someattr').to.equal 'from regElementA'
SimplyBind('attr:someattr').of(regB).set('from regElementB')
expect(regA.getAttribute 'someattr').to.equal 'from regElementB'
expect(regB.getAttribute 'someattr').to.equal 'from regElementB'
expect(regC.getAttribute 'someattr').to.equal 'from regElementB'
SimplyBind('textContent').of(regA)
.to('textContent').of(regB).bothWays()
.chainTo('textContent').of(regC).bothWays()
.chainTo('textContent').of(regA).bothWays()
SimplyBind('textContent').of(regA).set 'from regElementA'
expect(regA.textContent).to.equal 'from regElementA'
expect(regB.textContent).to.equal 'from regElementA'
expect(regC.textContent).to.equal 'from regElementA'
SimplyBind('textContent').of(regB).set 'from regElementB'
expect(regA.textContent).to.equal 'from regElementB'
expect(regB.textContent).to.equal 'from regElementB'
expect(regC.textContent).to.equal 'from regElementB'
SimplyBind('value').of(inputA)
.to('value').of(inputB).bothWays()
.chainTo('value').of(inputC).bothWays()
.chainTo('value').of(inputA).bothWays()
inputA.value = 'from inputElementA'
inputA.emit 'change'
expect(inputA.value).to.equal 'from inputElementA'
expect(inputB.value).to.equal 'from inputElementA'
expect(inputC.value).to.equal 'from inputElementA'
inputB.value = 'from inputElementB'
inputB.emit 'change'
expect(inputA.value).to.equal 'from inputElementB'
expect(inputB.value).to.equal 'from inputElementB'
expect(inputC.value).to.equal 'from inputElementB'
SimplyBind('event:eventA').of(eventEmitterA)
.to('event:eventB').of(eventEmitterA).bothWays()
SimplyBind('event:eventB').of(eventEmitterA)
.to('event:eventC').of(eventEmitterA).bothWays()
SimplyBind('event:eventC').of(eventEmitterA)
.to('event:eventA').of(eventEmitterA).bothWays()
invokeCountA = invokeCountB = invokeCountC = 0
eventEmitterA.on 'eventA', ()-> invokeCountA++
eventEmitterA.on 'eventB', ()-> invokeCountB++
eventEmitterA.on 'eventC', ()-> invokeCountC++
eventEmitterA.emit 'eventA'
eventEmitterA.emit 'eventB'
eventEmitterA.emit 'eventC'
expect(invokeCountA).to.equal 3
expect(invokeCountB).to.equal 3
expect(invokeCountC).to.equal 3
SimplyBind.defaultOptions.updateEvenIfSame = false
restartSandbox()
test "Infinite loops should not occur for colliding two-way bindings", ()->
SimplyBind.defaultOptions.updateEvenIfSame = true
dispatcher = 'prop': 0
objectA.prop1 = 0
objectB.prop1 = 0
objectC.prop1 = 0
SimplyBind('prop').of(dispatcher)
.to('prop1').of(objectA).bothWays().transform (v, ov)-> v+ov
.and.to('prop1').of(objectB).bothWays().transform (v, ov)-> v+ov
.and.to('prop1').of(objectC).bothWays().transform (v, ov)-> v+ov
expect(dispatcher.prop).to.equal 0
expect(objectA.prop1).to.equal 0
expect(objectB.prop1).to.equal 0
expect(objectC.prop1).to.equal 0
dispatcher.prop = 1
expect(dispatcher.prop).to.equal 1
expect(objectA.prop1).to.equal 1
expect(objectB.prop1).to.equal 1
expect(objectC.prop1).to.equal 1
objectA.prop1 = 2
expect(dispatcher.prop).to.equal 3
expect(objectA.prop1).to.equal 2
expect(objectB.prop1).to.equal 4
expect(objectC.prop1).to.equal 4
objectC.prop1 = 3
expect(dispatcher.prop).to.equal 6
expect(objectA.prop1).to.equal 8
expect(objectB.prop1).to.equal 10
expect(objectC.prop1).to.equal 3
SimplyBind.defaultOptions.updateEvenIfSame = false
restartSandbox()
test "Infinite loops should occur only when a value is bound to a function and that function updates the value", ()->
invokeCount = 0
SimplyBind('prop1').of(objectA)
.to ()-> unless invokeCount is 15
objectA.prop1 = ++invokeCount
expect(invokeCount).to.equal(objectA.prop1)
expect(invokeCount).to.equal(15)
restartSandbox()
test "Update subscribers", ()->
SimplyBind.defaultOptions.updateOnBind = false
invokeCount =
'object': 0
'array': 0
'function': 0
'proxy': 0
'domAttr': 0
'domText': 0
'domValue': 0
'domCheckboxSingle': 0
'domCheckbox': 0
'domRadioSingle': 0
'domRadio': 0
'event': 0
SimplyBind('prop').of(objectA).to ()-> invokeCount.object++
objectA.prop = true
expect(invokeCount.object).to.equal(1)
# ==== Arrays =================================================================================
SimplyBind('array:list').of(objectA).to ()-> invokeCount.array++
objectA.list.push(1)
objectA.list.unshift(1)
objectA.list.pop()
objectA.list.shift()
objectA.list.splice(0,1)
expect(invokeCount.array).to.equal(5)
SimplyBind(()->true).to ()-> invokeCount.function++
expect(invokeCount.function).to.equal(1)
obj = base:10, test:(a,b)-> (a+b)*@base
SimplyBind('func:test').of(obj).to (value)->
expect(value.result).to.equal 50
expect(value.args).to.eql [2,3]
invokeCount.proxy++
obj.test(2,3)
expect(invokeCount.proxy).to.equal(1)
if isBrowser
SimplyBind('attr:someattr').of(regA).to ()-> invokeCount.domAttr++
regA.setAttribute('someattr', 10)
SimplyBind('attr:someattr').of(regA).set(true)
expect(invokeCount.domAttr).to.equal(1)
SimplyBind('textContent').of(regA).to ()-> invokeCount.domText++
SimplyBind('textContent').of(regA).set 'true'
expect(invokeCount.domText).to.equal(1)
SimplyBind('value').of(inputA).to ()-> invokeCount.domValue++
inputA.value = 'true'
inputA.emit 'change'
expect(invokeCount.domValue).to.equal(1)
SimplyBind('checked').of(checkboxA).to ()-> invokeCount.domCheckboxSingle++
checkboxA.checked = true
checkboxA.emit 'change'
expect(invokeCount.domCheckboxSingle).to.equal(1)
SimplyBind('checked').of(checkboxFields).to ()-> invokeCount.domCheckbox++
checkboxB.checked = true
checkboxB.emit 'change'
expect(invokeCount.domCheckbox).to.equal(1)
SimplyBind('checked').of(radioA).to ()-> invokeCount.domRadioSingle++
radioA.checked = true
radioA.emit 'change'
expect(invokeCount.domRadioSingle).to.equal(1)
SimplyBind('checked').of(radioFields).to ()-> invokeCount.domRadio++
radioB.checked = true
radioB.emit 'change'
expect(invokeCount.domRadio).to.equal(1)
SimplyBind.defaultOptions.updateEvenIfSame = true
SimplyBind('event:someEvent').of(eventEmitterA).to ()-> invokeCount.event++
eventEmitterA.emit 'someEvent'
expect(invokeCount.event).to.equal(1)
SimplyBind.defaultOptions.updateEvenIfSame = false
SimplyBind.defaultOptions.updateOnBind = true
restartSandbox()
test "Receive a value as a dependent", ()->
SimplyBind.defaultOptions.updateOnBind = false
invokeCount =
'object': 0
'array': 0
'proxy': 0
'function': 0
'domAttr': 0
'domText': 0
'domValue': 0
'domCheckboxSingle': 0
'domCheckbox': 0
'domRadio': 0
'event': 0
SimplyBind('prop').of(objectA).to ()-> invokeCount.object++
SimplyBind(()-> true).to('prop').of(objectA)
expect(objectA.prop).to.equal(true)
expect(invokeCount.object).to.equal(1)
expect(objectA.list.length).to.equal(10)
SimplyBind('array:list').of(objectA).to ()-> invokeCount.array++
SimplyBind(()-> [1,2]).to('array:list').of(objectA)
expect(objectA.list.length).to.equal(2)
expect(invokeCount.array).to.equal(1)
obj = base:10, test:(a,b)-> (a+b)*@base
proxyInterface = SimplyBind('func:test').of(obj).to ()-> invokeCount.proxy++
SimplyBind(()-> true).to(proxyInterface)
expect(invokeCount.proxy).to.equal(1)
if isBrowser
SimplyBind('attr:someattr').of(regA).to ()-> invokeCount.domAttr++
SimplyBind(()-> 'true').to('attr:someattr').of(regA)
expect(regA.getAttribute('someattr')).to.equal('true')
expect(invokeCount.domAttr).to.equal(1)
SimplyBind('textContent').of(regA).to ()-> invokeCount.domText++
SimplyBind(()-> 'true').to('textContent').of(regA)
expect(regA.textContent).to.equal('true')
expect(invokeCount.domText).to.equal(1)
SimplyBind('value').of(inputA).to ()-> invokeCount.domValue++
SimplyBind(()-> 'true').to('value').of(inputA)
expect(inputA.value).to.equal('true')
expect(invokeCount.domValue).to.equal(1)
expect(checkboxA.checked).to.be.false
SimplyBind('checked').of(checkboxA).to ()-> invokeCount.domCheckboxSingle++
SimplyBind(()-> 'truthy').to('checked').of(checkboxA)
expect(checkboxA.checked).to.be.true
expect(invokeCount.domCheckboxSingle).to.equal(1)
# ==== DOMCheckbox =================================================================================
SimplyBind('checked').of($checkboxFields).to ()-> invokeCount.domCheckbox++
SimplyBind(()-> ['checkboxB', 'checkboxC']).to('checked').of($checkboxFields)
expect(checkboxA.checked).to.be.false
expect(checkboxB.checked).to.be.true
expect(checkboxC.checked).to.be.true
expect(invokeCount.domCheckbox).to.equal(1)
SimplyBind(()-> 'checkboxA').to('checked').of($checkboxFields)
expect(checkboxA.checked).to.be.true
expect(checkboxB.checked).to.be.false
expect(checkboxC.checked).to.be.false
expect(invokeCount.domCheckbox).to.equal(2)
# ==== DOMRadio =================================================================================
SimplyBind('checked').of($radioFields).to ()-> invokeCount.domRadio++
SimplyBind(()-> 'radioC').to('checked').of($radioFields)
expect(radioA.checked).to.be.false
expect(radioB.checked).to.be.false
expect(radioC.checked).to.be.true
expect(invokeCount.domRadio).to.equal(1)
# ==== Event =================================================================================
SimplyBind('event:someEvent').of(eventEmitterA).to ()-> invokeCount.event++
SimplyBind(()-> true).to('event:someEvent').of(eventEmitterA)
expect(invokeCount.event).to.equal(1)
SimplyBind.defaultOptions.updateOnBind = true
restartSandbox()