@danielkalen/simplybind
Version:
Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.
461 lines (328 loc) • 14.4 kB
text/coffeescript
suite "Data Binding Behavior", ()->
suiteSetup(restartSandbox)
test "Live properties should behave the same way across all object types", ()->
dispatcher =
'standard': objectA
'array': objectA.list
'function': ()->
'domDiv': if isBrowser then $('<div />')[0] else {}
'domInput': if isBrowser then $('<input />')[0] else {}
'domNode': if isBrowser then $('<div>text</div>')[0].childNodes[0] else {}
'date': new Date()
receiver =
'standard': null
'array': null
'function': null
'domDiv': null
'domInput': null
'domNode': null
'date': null
for name,object of dispatcher
SimplyBind('prop').of(dispatcher[name]).to(name).of(receiver)
expect(receiver[name]).to.equal(undefined)
object.prop = 100
expect(receiver[name]).to.equal(100)
restartSandbox()
test "Object properties will be converted to live properties based off prototype values if existent, even if they are unconfigurable", ()->
SimplyBind.defaultOptions.updateOnBind = false
invokeCount = A:0,B:0,C:0,D:0
dispatcher = value:1
valueC = 1
valueD = 1
obj = Object.create {A:1}
Object.defineProperties Object.getPrototypeOf(obj),
B: value: 1
C:
configurable: true
get: ()-> valueC
set: ()-> valueC = arguments[0]
D:
configurable: false
get: ()-> valueD
set: ()-> valueD = arguments[0]
SimplyBind('A').of(obj).to ()-> invokeCount.A++
SimplyBind('B').of(obj).to ()-> invokeCount.B++
SimplyBind('C').of(obj).to ()-> invokeCount.C++
SimplyBind('D').of(obj).to ()-> invokeCount.D++
SimplyBind('value').of(dispatcher).to('A').of(obj).and.to('B').of(obj).and.to('C').of(obj).and.to('D').of(obj)
expect(invokeCount.A).to.equal 0
expect(invokeCount.B).to.equal 0
expect(invokeCount.C).to.equal 0
expect(invokeCount.D).to.equal 0
dispatcher.value++
expect(invokeCount.A).to.equal 1
expect(invokeCount.B).to.equal 1
expect(invokeCount.C).to.equal 1
expect(invokeCount.D).to.equal 1
expect(obj.A).to.equal 2
expect(obj.B).to.equal 2
expect(obj.C).to.equal 2
expect(obj.D).to.equal 2
obj.A++
expect(invokeCount.A).to.equal 2
obj.B++
expect(invokeCount.B).to.equal 2
obj.C++
expect(invokeCount.C).to.equal 2
obj.D++
expect(invokeCount.D).to.equal 2
SimplyBind.unBindAll(obj, true)
expect(obj.A).to.equal 3
expect(obj.B).to.equal 3
expect(obj.C).to.equal 3
expect(obj.D).to.equal 3
SimplyBind.defaultOptions.updateOnBind = true
test "Object properties should be made into live properties unless they are unconfigurable and not prototype-inherited", ()->
SimplyBind.defaultOptions.updateOnBind = false
invokeCount = 0
SimplyBind('prop1').of(objectA).to ()-> invokeCount++
expect(invokeCount).to.equal(0)
objectA.prop1 = !objectA.prop1
expect(invokeCount).to.equal(1)
### ==========================================================================
SimplyBind('nonexistent').of(objectA).to ()-> invokeCount++
expect(invokeCount).to.equal(1)
objectA.nonexistent = !objectA.nonexistent
expect(invokeCount).to.equal(2)
### ==========================================================================
Object.defineProperty objectA, 'protected', value:'cant touch this'
SimplyBind('protected').of(objectA).to ()-> invokeCount++
expect(invokeCount).to.equal(2)
objectA.protected = !objectA.protected
expect(invokeCount).to.equal(2)
### ==========================================================================
obj = Object.create(protected:'CAN touch this, even though its a prototype-inherited value')
SimplyBind('protected').of(obj).to ()-> invokeCount++
expect(invokeCount).to.equal(2)
obj.protected = !obj.protected
expect(invokeCount).to.equal(3)
SimplyBind.defaultOptions.updateOnBind = true
restartSandbox()
test "The property's original setter (if exists) should be invoked during a live property update", ()->
prop7 = 'hey!'
invokeCountGet = 0
invokeCountSet = 0
context = null
Object.defineProperty objectA, 'prop7',
enumerable: true
configurable: true
get: ()->
invokeCountGet++
prop7
set: (val)->
invokeCountSet++
context = @
prop7 = val
expect(objectA.prop7).to.equal 'hey!'
expect(invokeCountGet).to.equal 1
expect(invokeCountSet).to.equal 0
expect(context).to.equal null
SimplyBind('prop7').of(objectA).to('prop3').of objectB
objectA.prop7 = 'hello!'
expect(invokeCountGet).to.equal 2
expect(invokeCountSet).to.equal 1
expect(objectA.prop7).to.equal 'hello!'
expect(context).to.equal objectA
objectA.prop7 = 'hi!'
expect(invokeCountGet).to.equal 3
expect(invokeCountSet).to.equal 2
expect(context).to.equal objectA
SimplyBind.unBindAll(objectA, true)
expect(objectA.prop7).to.equal 'hi!'
expect(invokeCountGet).to.equal 4
expect(invokeCountSet).to.equal 2
objectA.prop7 = 'HI!'
expect(objectA.prop7).to.equal 'HI!'
expect(invokeCountGet).to.equal 5
expect(invokeCountSet).to.equal 3
expect(objectB.prop3).not.to.equal 'HI!'
restartSandbox()
test "Destruction of non-liveprops bindings should not redefine property descriptors", ()->
invokeCountA = invokeCountB = 0
obj = Object.create({A:10}, B:{value:10, writable:true, configurable:false})
descA = Object.getOwnPropertyDescriptor(obj, 'A')
descB = Object.getOwnPropertyDescriptor(obj, 'B')
expect(obj.A).to.equal 10
expect(obj.B).to.equal 10
expect(descA).to.be.undefined
expect(descB).not.to.be.undefined
binding = SimplyBind('A').of(obj).to ()-> invokeCountA++
binding = SimplyBind('B').of(obj).to ()-> invokeCountB++
expect(invokeCountA).to.equal 1
expect(invokeCountB).to.equal 1
expect(Object.getOwnPropertyDescriptor(obj, 'A')).not.to.be.undefined
expect(Object.getOwnPropertyDescriptor(obj, 'B')).not.to.equal(descB)
obj.A = 'anotherValue'
obj.B = 'anotherValue'
expect(invokeCountA).to.equal 2
expect(invokeCountB).to.equal 1
expect(obj.A).to.equal 'anotherValue'
expect(obj.A).to.equal 'anotherValue'
delete obj.A
delete obj.B
expect(invokeCountA).to.equal 2
expect(invokeCountB).to.equal 1
expect(obj.A).to.equal 10
expect(obj.B).to.equal 'anotherValue'
expect(Object.getOwnPropertyDescriptor(obj, 'A')).to.be.undefined
expect(Object.getOwnPropertyDescriptor(obj, 'B')).not.to.be.undefined
binding.unBind()
expect(invokeCountA).to.equal 2
expect(invokeCountB).to.equal 1
expect(obj.A).to.equal 10
expect(obj.B).to.equal 'anotherValue'
expect(Object.getOwnPropertyDescriptor(obj, 'A')).to.be.undefined
expect(Object.getOwnPropertyDescriptor(obj, 'B')).not.to.be.undefined
restartSandbox()
test "Publishers will update subscribers even if their value is falsy", ()->
binding = SimplyBind('prop1').of(objectA).to('prop1').of(objectB)
binding.set ''
expect(objectB.prop1).to.equal ''
binding.set 0
expect(objectB.prop1).to.equal 0
binding.set false
expect(objectB.prop1).to.equal false
binding.set undefined
expect(objectB.prop1).to.equal undefined
binding.set null
expect(objectB.prop1).to.equal null
restartSandbox()
test "Update subscribers of a binding even if it's unchanged when SimplyBind.options.updateEvenIfSame is on", ()->
expect(SimplyBind.defaultOptions.updateEvenIfSame).to.be.false
invokeCount = object:0, func:0, event:0
dispatcher = object:'start', func:'start', event:'start'
eventEmitterA.on 'alwaysEmit', ()-> invokeCount.event++
bindings =
'object': SimplyBind('object').of(dispatcher).to('prop').of(objectB).chainTo (value)-> invokeCount.object++
'func': SimplyBind('func').of(dispatcher).to(()-> invokeCount.func++)
'event': SimplyBind('event').of(dispatcher).to('event:alwaysEmit').of(eventEmitterA)
expect(objectB.prop).to.equal 'start'
expect(invokeCount.object).to.equal 1
dispatcher.object = "shouldn't update"
expect(objectB.prop).to.equal "shouldn't update"
expect(invokeCount.object).to.equal 2
dispatcher.object = "shouldn't update"
expect(objectB.prop).to.equal "shouldn't update"
expect(invokeCount.object).to.equal 2
expect(invokeCount.func).to.equal 1
dispatcher.func = "shouldn't update"
expect(invokeCount.func).to.equal 2
dispatcher.func = "shouldn't update"
expect(invokeCount.func).to.equal 2
expect(invokeCount.event).to.equal 1
dispatcher.event = "shouldn't update"
expect(invokeCount.event).to.equal 2
dispatcher.event = "shouldn't update"
expect(invokeCount.event).to.equal 2
bindings.object.setOption 'updateEvenIfSame', true
SimplyBind('object').of(dispatcher).to('prop').of(objectB).setOption 'updateEvenIfSame', true
dispatcher.object = 'should update'
expect(objectB.prop).to.equal 'should update'
expect(invokeCount.object).to.equal 3
dispatcher.object = 'should update'
dispatcher.object = 'should update'
dispatcher.object = 'should update'
dispatcher.object = 'should update'
expect(invokeCount.object).to.equal 7
bindings.object.setOption 'updateEvenIfSame', false
SimplyBind('object').of(dispatcher).to('prop').of(objectB).setOption 'updateEvenIfSame', false
dispatcher.object = "shouldn't update"
expect(objectB.prop).to.equal "shouldn't update"
expect(invokeCount.object).to.equal 8
dispatcher.object = "shouldn't update"
dispatcher.object = "shouldn't update"
dispatcher.object = "shouldn't update"
dispatcher.object = "shouldn't update"
expect(invokeCount.object).to.equal 8
restartSandbox()
test "Should update subscribers even when its new value is undefined", ()->
binding = SimplyBind('prop1').of(objectA).to('prop1').of(objectB)
objectA.prop1 = 10
expect(objectB.prop1).to.equal 10
objectA.prop1 = window.nonexistent
expect(objectB.prop1).to.be.undefined
objectA.prop1 = 20
expect(objectB.prop1).to.equal 20
objectA.prop1 = window.nonexistent
expect(objectB.prop1).to.be.undefined
restartSandbox()
test "Subscribers should update their own subscribers when being updated by a publisher, even if these subscribers are not liveProps", ()-> if not isBrowser then @skip() else
objectA.prop2 = 0.8
SimplyBind('prop2').of(objectA)
.to('opacity').of regA.style
SimplyBind('opacity').of(regA.style)
.to('font-size').of(regA.style)
.transform (opacity)-> opacity * 20 + 'px'
.and.to('font-size').of(regB.style)
.transform (opacity)-> opacity * 40 + 'px'
expect(regA.style.opacity.toString()).to.equal '0.8'
expect(regA.style['font-size']).to.equal '16px'
expect(regB.style['font-size']).to.equal '32px'
objectA.prop2 = 0.5
expect(regA.style.opacity.toString()).to.equal '0.5'
expect(regA.style['font-size']).to.equal '10px'
expect(regB.style['font-size']).to.equal '20px'
restartSandbox()
test "Placeholder indicators in a property name will be ignored if SimplyBind.options.simpleSelector is on", ()->
objA = 'first': 'This {{placeholder}} will change'
objB = 'second': 'This {{nonplaceholder}} will remain the same'
SimplyBind('prop1').of(objectA).to('first.placeholder').of(objA)
objectA.prop1 = 'placeholder'
SimplyBind('prop1').of(objectB).to('second.nonplaceholder', {'simpleSelector':true}).of(objB)
objectB.prop1 = 'placeholder'
expect(objA['first']).to.equal 'This placeholder will change'
expect(objB['second']).to.equal 'This {{nonplaceholder}} will remain the same'
test "Subscribers shouldn't be re-updated when a transform function is added if SimplyBind.options.updateOnBind is off", ()->
dispatcher = 'value': 123
invokeCount =
'object': 0
'array': 0
'function': 0
'domAttr': 0
'domText': 0
'domInput': 0
'jQueryProp': 0
'event': 0
fakeTransform = (value)-> value
SimplyBind.defaultOptions.updateOnBind = false
SimplyBind.defaultOptions.updateEvenIfSame = true
SimplyBind('prop').of(objectA).to(()-> invokeCount.object++).transform(fakeTransform)
SimplyBind('array:list').of(objectA).to(()-> invokeCount.array++).transform(fakeTransform)
SimplyBind(fn = ()-> true).to(()-> invokeCount.function++).transform(fakeTransform)
SimplyBind('attr:prop').of(regA).to(()-> invokeCount.domAttr++).transform(fakeTransform) if isBrowser
SimplyBind('textContent').of(regA).to(()-> invokeCount.domText++).transform(fakeTransform) if isBrowser
SimplyBind('value').of(inputA).to(()-> invokeCount.domInput++).transform(fakeTransform) if isBrowser
SimplyBind('prop').of($regB).to(()-> invokeCount.jQueryProp++).transform(fakeTransform) if isBrowser
SimplyBind('event:someEvent').of(eventEmitterA).to(()-> invokeCount.event++).transform(fakeTransform)
SimplyBind.defaultOptions.updateOnBind = true
SimplyBind('value').of(dispatcher).to('prop').of(objectA)
SimplyBind('value').of(dispatcher).to('array:list').of(objectA)
SimplyBind('value').of(dispatcher).to(fn)
SimplyBind('value').of(dispatcher).to('attr:prop').of(regA) if isBrowser
SimplyBind('value').of(dispatcher).to('textContent').of(regA) if isBrowser
SimplyBind('value').of(dispatcher).to('value').of(inputA) if isBrowser
SimplyBind('value').of(dispatcher).to('prop').of($regB) if isBrowser
SimplyBind('value').of(dispatcher).to('event:someEvent').of(eventEmitterA)
expect(invokeCount.object).to.equal 1
expect(invokeCount.array).to.equal 1
expect(invokeCount.function).to.equal 3 # updateOnBind always true
expect(invokeCount.event).to.equal 1
if isBrowser
expect(invokeCount.domAttr).to.equal 1
expect(invokeCount.domText).to.equal 1
expect(invokeCount.domInput).to.equal 1
expect(invokeCount.jQueryProp).to.equal 1
SimplyBind.defaultOptions.updateEvenIfSame = false
restartSandbox()
test "Publisher's shouldn't be able to add themselves as subscribers to themselves", ()->
binding = SimplyBind('prop1').of(objectA)._
expect(binding.subs.length).to.equal 0
bindingInterface = SimplyBind('prop1').of(objectA).to('prop1').of(objectA)
expect(binding.subs.length).to.equal 0
expect(bindingInterface.subs.length).to.equal 0
bindingInterface = SimplyBind('prop1').of(objectA).updateOn('prop1').of(objectA)
expect(binding.subs.length).to.equal 0
expect(bindingInterface.subs.length).to.equal 0
restartSandbox()