UNPKG

@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
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 # proto = {A:10}; Object.defineProperty(proto, 'B', value:10) 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 # undefined value expect(objectB.prop1).to.be.undefined objectA.prop1 = 20 expect(objectB.prop1).to.equal 20 objectA.prop1 = window.nonexistent # undefined value 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()