UNPKG

@danielkalen/simplybind

Version:

Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.

1,448 lines (975 loc) 173 kB
if mocha? and window? isBrowser = true mocha.setup('tdd') mocha.bail() unless @isSauceLabsTest or location.hostname else isBrowser = false global.window = global chai = require 'chai' chaiSpies = require 'chai-spies' SimplyBind = require '../dist/simplybind.node.js' startSandbox = require('./spec-helper.js').startSandbox restartSandbox = require('./spec-helper.js').restartSandbox chai.use chaiSpies expect = chai.expect should = chai.should() suite "SimplyBind", ()-> suiteSetup(startSandbox) suite "Options", ()-> test "SimplyBind.options should contain the global options list", ()-> options = SimplyBind.options expect(options.silent).not.to.be.undefined expect(options.liveProps).not.to.be.undefined expect(options.dispatchEvents).not.to.be.undefined expect(options.updateEvenIfSame).not.to.be.undefined expect(options.updateOnBind).not.to.be.undefined expect(options.mutateInherited).not.to.be.undefined expect(options.trackArrayChildren).not.to.be.undefined expect(options.simpleSelector).not.to.be.undefined expect(options.promiseTransforms).not.to.be.undefined expect(options.placeholder).not.to.be.undefined test "SimplyBind.options should return a non-editable copy of the global options list", ()-> expect(SimplyBind.options.dispatchEvents).to.be.false SimplyBind.options.dispatchEvents = true expect(SimplyBind.options.dispatchEvents).to.be.false test "SimplyBind.setOption() should set a new value for a single option", ()-> expect(SimplyBind.options.silent).to.equal false SimplyBind.setOption 'silent', true expect(SimplyBind.options.silent).to.equal true SimplyBind.setOption 'silent', false test "SimplyBind.setOptions() should set new values for a all the options passed", ()-> expect(SimplyBind.options.silent).to.equal false expect(SimplyBind.options.liveProps).to.equal true SimplyBind.setOptions 'silent': true 'liveProps': false expect(SimplyBind.options.silent).to.equal true expect(SimplyBind.options.liveProps).to.equal false SimplyBind.setOptions 'silent': false 'liveProps': true SimplyBind.setOptions {} expect(SimplyBind.options.silent).to.equal false expect(SimplyBind.options.liveProps).to.equal true test "SimplyBind.setOptions() should not add non-registered options", ()-> SimplyBind.setOptions placeholder: ['{{', '}}'] youtube: 'yea!' expect(SimplyBind.options.placeholder[0]).to.equal '{{' expect(SimplyBind.options.placeholder[1]).to.equal '}}' expect(SimplyBind.options.youtube).to.be.undefined test "<bound instance>.setOption should update a registered option only for the instance", ()-> SimplyBind.setOption 'updateOnBind', false expect(SimplyBind.options.updateOnBind).to.be.false binding = SimplyBind('prop1').of(objectA) expect(binding._.options.updateOnBind).to.be.false binding.setOption 'updateOnBind', true expect(binding._.options.updateOnBind).to.be.true expect(SimplyBind.options.updateOnBind).to.be.false SimplyBind.setOption 'updateOnBind', true restartSandbox() test "Creating a new binding interface for a cached binding without passing an options object shouldn't overwrite existing options", ()-> SimplyBind.setOption 'updateOnBind', false expect(SimplyBind.options.updateOnBind).to.be.false binding = SimplyBind('prop1').of(objectA) expect(binding._.options.updateOnBind).to.be.false binding.setOption 'updateOnBind', true expect(SimplyBind('prop1').of(objectA)._.options.updateOnBind).to.be.true expect(SimplyBind.options.updateOnBind).to.be.false SimplyBind.setOption 'updateOnBind', true restartSandbox() test "<bound instance>.setOption should update a registered option only for the instance", ()-> binding = SimplyBind('prop1').of(objectA) binding.setOption 'placeholder', ['{{', '}}'] binding.setOption 'youtube', 'yea' expect(binding._.options.placeholder[0]).to.equal '{{' expect(binding._.options.placeholder[1]).to.equal '}}' expect(binding._.options.youtube).to.be.undefined restartSandbox() test "Passing an options object as the 2nd param of .of() should update registered options only for the instance", ()-> SimplyBind.setOption 'updateOnBind', false expect(SimplyBind.options.updateOnBind).to.be.false binding = SimplyBind('prop1', {'updateOnBind':true}).of(objectA) expect(binding._.options.updateOnBind).to.be.true expect(SimplyBind.options.updateOnBind).to.be.false SimplyBind.setOption 'updateOnBind', true restartSandbox() test "Passing an options object as the 2nd param of .of() should update the options even for an existing binding instance", ()-> SimplyBind.setOption 'updateOnBind', false expect(SimplyBind.options.updateOnBind).to.be.false binding = SimplyBind('prop1').of(objectA) expect(binding._.options.updateOnBind).to.be.false binding = SimplyBind('prop1', {'updateOnBind':true, 'nonexistent':true}).of(objectA) expect(binding._.options.updateOnBind).to.be.true expect(binding._.options.nonexistent).to.be.undefined expect(SimplyBind.options.updateOnBind).to.be.false SimplyBind.setOption 'updateOnBind', true restartSandbox() test "Passing an options object as the 2nd param of .of() for an existing binding should invoke the required methods for these options", ()-> SimplyBind.setOption 'liveProps', false SimplyBind.setOption 'updateOnBind', false SimplyBind.setOption 'mutateInherited', false SimplyBind.setOption 'trackArrayChildren', false Object::id = '' arrayInvokeCount = 0 newOptions = {'liveProps':true, 'mutateInherited':true, 'trackArrayChildren':true} bindings = 'liveProps': SimplyBind('prop1').of(objectA) 'mutateInherited': SimplyBind('id').of(objectB) 'trackArrayChildren': SimplyBind(arrayA).to ()-> arrayInvokeCount++ objectA.prop1 = '1' expect(bindings.liveProps.value).not.to.equal '1' objectB.id = '1' expect(bindings.mutateInherited.value).not.to.equal '1' arrayA[3] = '1' expect(arrayInvokeCount).to.equal 0 SimplyBind.setOption 'liveProps', true SimplyBind.setOption 'updateOnBind', true SimplyBind('prop1', newOptions).of(objectA) SimplyBind('id', newOptions).of(objectB) SimplyBind(arrayA, newOptions) objectA.prop1 = '2' expect(bindings.liveProps.value).to.equal '2' objectB.id = '2' expect(bindings.mutateInherited.value).to.equal '2' arrayA[5] = '2' expect(arrayInvokeCount).to.equal 1 delete Object::id restartSandbox() suite "Argument Values", ()-> suiteSetup(restartSandbox) suite "SimplyBind() function", ()-> test "Can only accept an object type of String, Number, Array, Function, or an existing binding instance", ()-> expect ()-> SimplyBind('s') .not.to.throw() expect ()-> SimplyBind(0) .not.to.throw() expect ()-> SimplyBind([]) .not.to.throw() expect ()-> SimplyBind(()->) .not.to.throw() expect ()-> SimplyBind(SimplyBind('prop1').of(objectA)) .not.to.throw() expect ()-> SimplyBind(true) .to.throw() expect ()-> SimplyBind(null) .to.throw() expect ()-> SimplyBind(undefined) .to.throw() expect ()-> SimplyBind({}) .to.throw() expect ()-> SimplyBind(new Date()) .to.throw() if isBrowser expect ()-> SimplyBind($('<div />')[0]) .to.throw() if window.Map and window.Set and window.Symbol expect ()-> SimplyBind(new Map()) .to.throw() expect ()-> SimplyBind(new WeakMap()) .to.throw() expect ()-> SimplyBind(new Set()) .to.throw() expect ()-> SimplyBind(new WeakSet()) .to.throw() expect ()-> SimplyBind(Symbol('a')) .to.throw() test "Should throw when passing an empty string", ()-> expect ()-> SimplyBind('') .to.throw() suite ".of() method", ()-> test "Can only accept non-primitive objects", ()-> expect ()-> SimplyBind(0).of([]) .not.to.throw() expect ()-> SimplyBind(0).of(()->) .not.to.throw() expect ()-> SimplyBind(0).of({}) .not.to.throw() expect ()-> SimplyBind(0).of(new Date()) .not.to.throw() if isBrowser expect ()-> SimplyBind(0).of($('<div />')[0]) .not.to.throw() if window.Map and window.Set and window.Symbol expect ()-> SimplyBind(0).of(new Map()) .not.to.throw() expect ()-> SimplyBind(0).of(new WeakMap()) .not.to.throw() expect ()-> SimplyBind(0).of(new Set()) .not.to.throw() expect ()-> SimplyBind(0).of(new WeakSet()) .not.to.throw() expect ()-> SimplyBind(0).of(Symbol(0)) .to.throw() expect ()-> SimplyBind(0).of(SimplyBind('prop1').of(objectA)) .not.to.throw() expect ()-> SimplyBind(0).of('s') .to.throw() expect ()-> SimplyBind(0).of(0) .to.throw() expect ()-> SimplyBind(0).of(true) .to.throw() expect ()-> SimplyBind(0).of(null) .to.throw() expect ()-> SimplyBind(0).of(undefined) .to.throw() test "Will extract and use the subject bound object from the instance if passed a binding instance", ()-> bound = SimplyBind('prop3').of(objectA).to('prop3').of(objectB) bound.set 'standard' expect(objectA.prop3).to.equal 'standard' expect(objectB.prop3).to.equal 'standard' bound2 = SimplyBind('prop4').of(bound).to('prop4').of(objectB) bound2.set 'ofBounded' expect(objectA.prop4).to.equal 'ofBounded' expect(objectB.prop4).to.equal 'ofBounded' suite ".ofEvent() method", ()-> test "Can only accept string values", ()-> expect ()-> SimplyBind(0).ofEvent('s') .not.to.throw() expect ()-> SimplyBind(0).of(objectA).toEvent('s') .not.to.throw() expect ()-> SimplyBind(0).ofEvent([]) .to.throw() expect ()-> SimplyBind(0).ofEvent(()->) .to.throw() expect ()-> SimplyBind(0).ofEvent({}) .to.throw() expect ()-> SimplyBind(0).ofEvent(new Date()) .to.throw() if isBrowser expect ()-> SimplyBind(0).ofEvent($('<div />')[0]) .to.throw() if window.Map and window.Set and window.Symbol expect ()-> SimplyBind(0).ofEvent(new Map()) .to.throw() expect ()-> SimplyBind(0).ofEvent(new WeakMap()) .to.throw() expect ()-> SimplyBind(0).ofEvent(new Set()) .to.throw() expect ()-> SimplyBind(0).ofEvent(new WeakSet()) .to.throw() expect ()-> SimplyBind(0).ofEvent(Symbol(0)) .to.throw() expect ()-> SimplyBind(0).ofEvent(SimplyBind(0).ofEvent('s')) .to.throw() expect ()-> SimplyBind(0).ofEvent(0) .to.throw() expect ()-> SimplyBind(0).ofEvent(true) .to.throw() expect ()-> SimplyBind(0).ofEvent(null) .to.throw() expect ()-> SimplyBind(0).ofEvent(undefined) .to.throw() suite ".transform/condition/All/Self() methods", ()-> test "Can only accept function values", ()-> window.origLog = console.warn console.warn = chai.spy() timesCalled = 0 SimplyBind(0).of({}).to(()->).transform(()->) expect(console.warn).to.have.been.called.exactly(timesCalled += 0) SimplyBind(0).of({}).to(()->).transformAll(()->) expect(console.warn).to.have.been.called.exactly(timesCalled += 0) SimplyBind(0).of({}).to(()->).condition(()->) expect(console.warn).to.have.been.called.exactly(timesCalled += 0) SimplyBind(0).of({}).to(()->).conditionAll(()->) expect(console.warn).to.have.been.called.exactly(timesCalled += 0) SimplyBind(0).of({}).transformSelf(()->) expect(console.warn).to.have.been.called.exactly(timesCalled += 0) SimplyBind(0).of({}).to(()->).transform([]) expect(console.warn).to.have.been.called.exactly(timesCalled += 1) SimplyBind(0).of({}).to(()->).transformAll({}) expect(console.warn).to.have.been.called.exactly(timesCalled += 1) SimplyBind(0).of({}).to(()->).conditionAll(new Date()) expect(console.warn).to.have.been.called.exactly(timesCalled += 1) if isBrowser SimplyBind(0).of({}).to(()->).condition($('<div />')[0]) expect(console.warn).to.have.been.called.exactly(timesCalled += 1) if window.Map and window.Set and window.Symbol SimplyBind(0).of({}).to(()->).transform(new Map()) expect(console.warn).to.have.been.called.exactly(timesCalled += 1) SimplyBind(0).of({}).to(()->).transformAll(new WeakMap()) expect(console.warn).to.have.been.called.exactly(timesCalled += 1) SimplyBind(0).of({}).to(()->).condition(new Set()) expect(console.warn).to.have.been.called.exactly(timesCalled += 1) SimplyBind(0).of({}).to(()->).conditionAll(new WeakSet()) expect(console.warn).to.have.been.called.exactly(timesCalled += 1) SimplyBind(0).of({}).transformSelf(Symbol(0)) expect(console.warn).to.have.been.called.exactly(timesCalled += 1) SimplyBind(0).of({}).to(()->).transform(SimplyBind('prop1').of(objectA)) expect(console.warn).to.have.been.called.exactly(timesCalled += 1) SimplyBind(0).of({}).to(()->).transformAll('s') expect(console.warn).to.have.been.called.exactly(timesCalled += 1) SimplyBind(0).of({}).to(()->).condition(0) expect(console.warn).to.have.been.called.exactly(timesCalled += 1) SimplyBind(0).of({}).to(()->).conditionAll(true) expect(console.warn).to.have.been.called.exactly(timesCalled += 1) SimplyBind(0).of({}).transformSelf(null) expect(console.warn).to.have.been.called.exactly(timesCalled += 1) SimplyBind(0).of({}).transformSelf(undefined) expect(console.warn).to.have.been.called.exactly(timesCalled += 1) console.warn = origLog suite ".throttle()", ()-> test "Will only accept a truthy number-type argument or a boolean false", ()-> binding = SimplyBind('prop').of(objectA)._ expect(binding.throttleRate or binding.thR).to.be.undefined SimplyBind('prop').of(objectA).throttle(200) expect(binding.throttleRate or binding.thR).to.equal 200 SimplyBind('prop').of(objectA).throttle(false) expect(binding.throttleRate or binding.thR).to.equal.undefined SimplyBind('prop').of(objectA).throttle(0) expect(binding.throttleRate or binding.thR).to.equal.undefined SimplyBind('prop').of(objectA).throttle('0') expect(binding.throttleRate or binding.thR).to.equal.undefined SimplyBind('prop').of(objectA).throttle(NaN) expect(binding.throttleRate or binding.thR).to.equal.undefined SimplyBind('prop').of(objectA).throttle({}) expect(binding.throttleRate or binding.thR).to.equal.undefined SimplyBind('prop').of(objectA).throttle([]) expect(binding.throttleRate or binding.thR).to.equal.undefined SimplyBind('prop').of(objectA).throttle(()->) expect(binding.throttleRate or binding.thR).to.equal.undefined if window.Map and window.Set and window.Symbol SimplyBind('prop').of(objectA).throttle(new Map()) expect(binding.throttleRate or binding.thR).to.equal.undefined SimplyBind('prop').of(objectA).throttle(new WeakMap()) expect(binding.throttleRate or binding.thR).to.equal.undefined SimplyBind('prop').of(objectA).throttle(new Set()) expect(binding.throttleRate or binding.thR).to.equal.undefined SimplyBind('prop').of(objectA).throttle(new WeakSet()) expect(binding.throttleRate or binding.thR).to.equal.undefined SimplyBind('prop').of(objectA).throttle(Symbol(9)) expect(binding.throttleRate or binding.thR).to.equal.undefined restartSandbox() suite "Errors & Warnings", ()-> suiteSetup(restartSandbox) test "Warning when binding a property of a jQuery element containing multiple els", ()-> if not isBrowser then @skip() else window.origLog = console.warn console.warn = chai.spy() SimplyBind('value').of $('input') expect(console.warn).to.have.been.called() console.warn = origLog test "Warning when binding a property of a jQuery element containing zero els", ()-> if not isBrowser then @skip() else origLog = console.warn console.warn = chai.spy() SimplyBind('value').of $('input[type="nonexistent"]') expect(console.warn).to.have.been.called() console.warn = origLog test "Warning when binding a property of a NodeList/HTMLCollection containing multiple els", ()-> if not isBrowser then @skip() else origLog = console.warn console.warn = chai.spy() SimplyBind('value').of document.querySelectorAll('input') expect(console.warn).to.have.been.called() console.warn = origLog test "No Warning when binding 'checked' of a jQuery element containing multiple radio/checkbox inputs", ()-> if not isBrowser then @skip() else origLog = console.warn console.warn = chai.spy() SimplyBind('checked').of $radioFields expect(console.warn).not.to.have.been.called() SimplyBind('value').of $radioFields expect(console.warn).to.have.been.called.exactly(1) console.warn = origLog test "No Warning when binding 'checked' of a NodeList/HTMLCollection/Array containing multiple radio/checkbox inputs", ()-> if not isBrowser then @skip() else origLog = console.warn console.warn = chai.spy() SimplyBind('checked').of $radioFields.toArray() expect(console.warn).not.to.have.been.called() SimplyBind('checked').of radioFields expect(console.warn).not.to.have.been.called() SimplyBind('value').of $radioFields.toArray() SimplyBind('value').of radioFields expect(console.warn).to.have.been.called.exactly(2) console.warn = origLog test "Warning when binding a property of a NodeList/HTMLCollection/Array/jQuery containing elements with mixed types", ()-> if not isBrowser then @skip() else origLog = console.warn console.warn = chai.spy() SimplyBind('checked').of $radioFields.add($checkboxFields).toArray() expect(console.warn).to.have.been.called.exactly(1) SimplyBind('checked').of $radioFields.add($checkboxFields) expect(console.warn).to.have.been.called.exactly(2) SimplyBind('checked').of document.querySelectorAll 'input' expect(console.warn).to.have.been.called.exactly(3) console.warn = origLog test "Warning when creating a binding and passing a non-function object to .transform/.condition/All", ()-> binding = SimplyBind('prop1').of(objectA).to('prop1').of(objectB) origLog = console.warn console.warn = chai.spy() timesCalled = 0 binding.transform [] expect(console.warn).to.have.been.called.exactly(timesCalled += 1) binding.transformAll [] expect(console.warn).to.have.been.called.exactly(timesCalled += 1) binding.transform {} expect(console.warn).to.have.been.called.exactly(timesCalled += 1) binding.transform new Date() expect(console.warn).to.have.been.called.exactly(timesCalled += 1) if isBrowser binding.transform $('<div />')[0] expect(console.warn).to.have.been.called.exactly(timesCalled += 1) if window.Map and window.Set and window.Symbol binding.transform new Map() expect(console.warn).to.have.been.called.exactly(timesCalled += 1) binding.transform new WeakMap() expect(console.warn).to.have.been.called.exactly(timesCalled += 1) binding.transform new Set() expect(console.warn).to.have.been.called.exactly(timesCalled += 1) binding.transform new WeakSet() expect(console.warn).to.have.been.called.exactly(timesCalled += 1) binding.transform Symbol(0) expect(console.warn).to.have.been.called.exactly(timesCalled += 1) binding.transform 's' expect(console.warn).to.have.been.called.exactly(timesCalled += 1) binding.transform 0 expect(console.warn).to.have.been.called.exactly(timesCalled += 1) binding.transform true expect(console.warn).to.have.been.called.exactly(timesCalled += 1) binding.transform null expect(console.warn).to.have.been.called.exactly(timesCalled += 1) binding.transform undefined expect(console.warn).to.have.been.called.exactly(timesCalled += 1) binding.condition [] expect(console.warn).to.have.been.called.exactly(timesCalled += 1) binding.conditionAll [] expect(console.warn).to.have.been.called.exactly(timesCalled += 1) console.warn = origLog restartSandbox() test "Warning when passed a non-number argument selector to an event binding", ()-> origLog = console.warn console.warn = chai.spy() SimplyBind('value').ofEvent 'someEvent' SimplyBind('15').ofEvent 'someEvent' SimplyBind(' 1aos95').ofEvent 'someEvent' expect(console.warn).to.have.been.called.exactly(1) console.warn = origLog test "No warnings should be thrown when SimplyBind.options.silent is on", ()-> SimplyBind.setOption 'silent', true origLog = console.warn console.warn = chai.spy() SimplyBind('value').ofEvent('someEvent') SimplyBind('prop3').of(objectA).to('prop3').of(objectB).transform [] expect(console.warn).not.to.have.been.called() console.warn = origLog SimplyBind.setOption 'silent', false test "Errors should still be thrown when SimplyBind.options.silent is on", ()-> SimplyBind.setOption 'silent', true expect ()-> SimplyBind '' .to.throw() SimplyBind.setOption 'silent', false test "No errors should be thrown when scanning for placeholders on a non-string object", ()-> dispatcher = 'prop':'value' receiver = 'prop':null expect(()-> SimplyBind('prop').of(dispatcher) .to('prop.placeholder').of(receiver) ).not.to.throw() expect(receiver.prop).not.to.equal('value') suite "Internal Behavior", ()-> suiteSetup(restartSandbox) test "A binding should have the correct public properies available", ()-> binding = SimplyBind('prop1').of(objectA).to('prop2').of(objectB) expect(binding.ID).not.to.be.undefined expect(binding.value).not.to.be.undefined expect(binding.original).not.to.be.undefined expect(binding.dependents).not.to.be.undefined expect(binding._).not.to.be.undefined restartSandbox() test "Bindings to a nonexistent object prop should not create instantiate the object prop at first", ()-> expect(objectA.prop20).to.be.undefined expect(objectB.prop20).to.be.undefined binding = SimplyBind('prop20').of(objectA).to('prop20').of(objectB) expect(objectA.prop20).to.be.undefined expect(objectB.prop20).to.be.undefined binding.set 'new value' expect(objectA.prop20).to.equal 'new value' expect(objectB.prop20).to.equal 'new value' restartSandbox() test "The binding.value property and binding.get() method should return the same value", ()-> tempObject = 'a': 'with' 'b': 'text {{verb}} a placeholder' SimplyBind('a').of(tempObject).to('b.verb').of tempObject bound = SimplyBind('b.verb').of(tempObject) expect(tempObject.b).to.equal 'text with a placeholder' expect(bound.value).to.equal 'text with a placeholder' expect(bound.get()).to.equal 'with' tempObject.a = 'without' expect(tempObject.b).to.equal 'text without a placeholder' expect(bound.value).to.equal 'text without a placeholder' expect(bound.get()).to.equal 'without' suite "Method availablity in different stages", ()-> test "Stage 0 (Selection Stage)", ()-> binding = ()-> SimplyBind('1') expect(()-> binding().of objectA).not.to.throw() expect(()-> binding().ofEvent objectA).to.throw() expect(()-> binding().to 'prop2').to.throw() expect(()-> binding().toEvent '/local').to.throw() expect(()-> binding().and 'prop2').to.throw() expect(()-> binding().set 'value').to.throw() expect(()-> binding().get()).to.throw() expect(()-> binding().transform ()->).to.throw() expect(()-> binding().transformAll ()->).to.throw() expect(()-> binding().transformSelf ()->).to.throw() expect(()-> binding().condition ()->).to.throw() expect(()-> binding().conditionAll ()->).to.throw() expect(()-> binding().bothWays()).to.throw() expect(()-> binding().stopPolling()).to.throw() expect(()-> binding().pollEvery(1000).stopPolling()).to.throw() expect(()-> binding().unBind()).to.throw() expect(()-> binding().chainTo ()->).to.throw() expect(()-> binding().updateDepsOnEvent 'a').to.throw() expect(()-> binding().removeEvent 'a').to.throw() expect(()-> binding().throttle 5).to.throw() binding = ()-> SimplyBind('1').ofEvent 'click' expect(()-> binding().of objectA).not.to.throw() expect(()-> binding().ofEvent objectA).to.throw() expect(()-> binding().to 'prop2').to.throw() expect(()-> binding().toEvent '/local').to.throw() expect(()-> binding().and 'prop2').to.throw() expect(()-> binding().set 'value').to.throw() expect(()-> binding().get()).to.throw() expect(()-> binding().transform ()->).to.throw() expect(()-> binding().transformAll ()->).to.throw() expect(()-> binding().transformSelf ()->).to.throw() expect(()-> binding().condition ()->).to.throw() expect(()-> binding().conditionAll ()->).to.throw() expect(()-> binding().bothWays()).to.throw() expect(()-> binding().stopPolling()).to.throw() expect(()-> binding().pollEvery(1000).stopPolling()).to.throw() expect(()-> binding().unBind()).to.throw() expect(()-> binding().chainTo ()->).to.throw() expect(()-> binding().updateDepsOnEvent 'a').to.throw() expect(()-> binding().removeEvent 'a').to.throw() expect(()-> binding().throttle 5).to.throw() test "Stage 1 (Indication Stage)", ()-> binding = ()-> SimplyBind('1').of objectA expect(()-> binding().of objectA).to.throw() expect(()-> binding().ofEvent objectA).to.throw() expect(()-> binding().to 'prop2').not.to.throw() expect(()-> binding().toEvent '/local').not.to.throw() expect(()-> binding().and 'prop2').to.throw() expect(()-> binding().set 'value').not.to.throw() expect(()-> binding().get()).not.to.throw() expect(()-> binding().transform ()->).to.throw() expect(()-> binding().transformAll ()->).to.throw() expect(()-> binding().transformSelf ()->).not.to.throw() expect(()-> binding().condition ()->).to.throw() expect(()-> binding().conditionAll ()->).to.throw() expect(()-> binding().bothWays()).to.throw() expect(()-> binding().stopPolling()).to.throw() expect(()-> binding().pollEvery(1000).stopPolling()).to.throw() expect(()-> binding().unBind()).to.throw() expect(()-> binding().chainTo ()->).to.throw() expect(()-> binding().updateDepsOnEvent 'a').to.throw() expect(()-> binding().removeEvent 'a').to.throw() expect(()-> binding().throttle 5).not.to.throw() binding = ()-> SimplyBind('1').ofEvent('click').of eventEmitterA expect(()-> binding().of objectA).to.throw() expect(()-> binding().ofEvent objectA).to.throw() expect(()-> binding().to 'prop2').not.to.throw() expect(()-> binding().toEvent '/local').not.to.throw() expect(()-> binding().and 'prop2').to.throw() expect(()-> binding().set 'value').not.to.throw() expect(()-> binding().get()).not.to.throw() expect(()-> binding().transform ()->).to.throw() expect(()-> binding().transformAll ()->).to.throw() expect(()-> binding().transformSelf ()->).not.to.throw() expect(()-> binding().condition ()->).to.throw() expect(()-> binding().conditionAll ()->).to.throw() expect(()-> binding().bothWays()).to.throw() expect(()-> binding().stopPolling()).to.throw() expect(()-> binding().pollEvery(1000).stopPolling()).to.throw() expect(()-> binding().unBind()).to.throw() expect(()-> binding().chainTo ()->).to.throw() expect(()-> binding().updateDepsOnEvent 'a').to.throw() expect(()-> binding().removeEvent 'a').to.throw() expect(()-> binding().throttle 5).not.to.throw() test "Stage 2 (Binding Selection Stage)", ()-> binding = ()-> SimplyBind('1').of(objectA).to 'prop1' expect(()-> binding().of objectA).not.to.throw() expect(()-> binding().ofEvent objectA).to.throw() expect(()-> binding().to 'prop2').to.throw() expect(()-> binding().toEvent '/local').to.throw() expect(()-> binding().and 'prop2').to.throw() expect(()-> binding().set 'value').to.throw() expect(()-> binding().get()).to.throw() expect(()-> binding().transform ()->).to.throw() expect(()-> binding().transformAll ()->).to.throw() expect(()-> binding().transformSelf ()->).to.throw() expect(()-> binding().condition ()->).to.throw() expect(()-> binding().conditionAll ()->).to.throw() expect(()-> binding().bothWays()).to.throw() expect(()-> binding().stopPolling()).to.throw() expect(()-> binding().pollEvery(1000).stopPolling()).to.throw() expect(()-> binding().unBind()).to.throw() expect(()-> binding().chainTo ()->).to.throw() expect(()-> binding().updateDepsOnEvent 'a').to.throw() expect(()-> binding().removeEvent 'a').to.throw() expect(()-> binding().throttle 5).to.throw() binding = ()-> SimplyBind('1').of(objectA).to('prop:prop1').of(objectB).and 'prop2' expect(()-> binding().of objectA).not.to.throw() expect(()-> binding().ofEvent objectA).to.throw() expect(()-> binding().to 'prop2').to.throw() expect(()-> binding().toEvent '/local').to.throw() expect(()-> binding().and 'prop2').to.throw() expect(()-> binding().set 'value').to.throw() expect(()-> binding().get()).to.throw() expect(()-> binding().transform ()->).to.throw() expect(()-> binding().transformAll ()->).to.throw() expect(()-> binding().transformSelf ()->).to.throw() expect(()-> binding().condition ()->).to.throw() expect(()-> binding().conditionAll ()->).to.throw() expect(()-> binding().bothWays()).to.throw() expect(()-> binding().stopPolling()).to.throw() expect(()-> binding().pollEvery(1000).stopPolling()).to.throw() expect(()-> binding().unBind()).to.throw() expect(()-> binding().chainTo ()->).to.throw() expect(()-> binding().updateDepsOnEvent 'a').to.throw() expect(()-> binding().removeEvent 'a').to.throw() expect(()-> binding().throttle 5).to.throw() test "Stage 3 (Binding Complete Stage)", ()-> binding = ()-> SimplyBind('1').of(objectA).to('prop1').of objectB expect(()-> binding().of objectA).to.throw() expect(()-> binding().ofEvent objectA).to.throw() expect(()-> binding().to 'prop2').to.throw() expect(()-> binding().toEvent '/local').to.throw() expect(()-> binding().and 'prop2').not.to.throw() expect(()-> binding().set 'value').not.to.throw() expect(()-> binding().get()).not.to.throw() expect(()-> binding().transform ()->).not.to.throw() expect(()-> binding().transformAll ()->).not.to.throw() expect(()-> binding().transformSelf ()->).to.throw() expect(()-> binding().condition ()->).not.to.throw() expect(()-> binding().conditionAll ()->).not.to.throw() expect(()-> binding().bothWays()).not.to.throw() expect(()-> binding().stopPolling()).not.to.throw() expect(()-> binding().pollEvery(1000).stopPolling()).not.to.throw() expect(()-> binding().unBind()).not.to.throw() expect(()-> binding().chainTo ()->).not.to.throw() expect(()-> binding().updateDepsOnEvent 'a').not.to.throw() expect(()-> binding().removeEvent 'a').not.to.throw() expect(()-> binding().throttle 5).not.to.throw() binding = ()-> SimplyBind('1').of(objectA).to('prop1').of(objectB).and('prop2').of objectB expect(()-> binding().of objectA).to.throw() expect(()-> binding().ofEvent objectA).to.throw() expect(()-> binding().to 'prop2').to.throw() expect(()-> binding().toEvent '/local').to.throw() expect(()-> binding().and 'prop2').not.to.throw() expect(()-> binding().set 'value').not.to.throw() expect(()-> binding().get()).not.to.throw() expect(()-> binding().transform ()->).not.to.throw() expect(()-> binding().transformAll ()->).not.to.throw() expect(()-> binding().transformSelf ()->).to.throw() expect(()-> binding().condition ()->).not.to.throw() expect(()-> binding().conditionAll ()->).not.to.throw() expect(()-> binding().bothWays()).not.to.throw() expect(()-> binding().stopPolling()).not.to.throw() expect(()-> binding().pollEvery(1000).stopPolling()).not.to.throw() expect(()-> binding().unBind()).not.to.throw() expect(()-> binding().chainTo ()->).not.to.throw() expect(()-> binding().updateDepsOnEvent 'a').not.to.throw() expect(()-> binding().removeEvent 'a').not.to.throw() expect(()-> binding().throttle 5).not.to.throw() binding = ()-> SimplyBind('1').of(objectA).to('prop1').of(objectB).bothWays() expect(()-> binding().of objectA).to.throw() expect(()-> binding().ofEvent objectA).to.throw() expect(()-> binding().to 'prop2').to.throw() expect(()-> binding().toEvent '/local').to.throw() expect(()-> binding().and 'prop2').not.to.throw() expect(()-> binding().set 'value').not.to.throw() expect(()-> binding().get()).not.to.throw() expect(()-> binding().transform ()->).not.to.throw() expect(()-> binding().transformAll ()->).not.to.throw() expect(()-> binding().transformSelf ()->).to.throw() expect(()-> binding().condition ()->).not.to.throw() expect(()-> binding().conditionAll ()->).not.to.throw() expect(()-> binding().bothWays()).not.to.throw() expect(()-> binding().stopPolling()).not.to.throw() expect(()-> binding().pollEvery(1000).stopPolling()).not.to.throw() expect(()-> binding().unBind()).not.to.throw() expect(()-> binding().chainTo ()->).not.to.throw() expect(()-> binding().updateDepsOnEvent 'a').not.to.throw() expect(()-> binding().removeEvent 'a').not.to.throw() expect(()-> binding().throttle 5).not.to.throw() binding = ()-> SimplyBind('1').of(objectA).to('prop1').of(objectB).transform ()-> expect(()-> binding().of objectA).to.throw() expect(()-> binding().ofEvent objectA).to.throw() expect(()-> binding().to 'prop2').to.throw() expect(()-> binding().toEvent '/local').to.throw() expect(()-> binding().and 'prop2').not.to.throw() expect(()-> binding().set 'value').not.to.throw() expect(()-> binding().get()).not.to.throw() expect(()-> binding().transform ()->).to.throw() expect(()-> binding().transformAll ()->).to.throw() expect(()-> binding().transformSelf ()->).to.throw() expect(()-> binding().condition ()->).not.to.throw() expect(()-> binding().conditionAll ()->).not.to.throw() expect(()-> binding().bothWays()).not.to.throw() expect(()-> binding().stopPolling()).not.to.throw() expect(()-> binding().pollEvery(1000).stopPolling()).not.to.throw() expect(()-> binding().unBind()).not.to.throw() expect(()-> binding().chainTo ()->).not.to.throw() expect(()-> binding().updateDepsOnEvent 'a').not.to.throw() expect(()-> binding().removeEvent 'a').not.to.throw() expect(()-> binding().throttle 5).not.to.throw() binding = ()-> SimplyBind('1').of(objectA).to('prop1').of(objectB).transformAll ()-> expect(()-> binding().of objectA).to.throw() expect(()-> binding().ofEvent objectA).to.throw() expect(()-> binding().to 'prop2').to.throw() expect(()-> binding().toEvent '/local').to.throw() expect(()-> binding().and 'prop2').to.throw() expect(()-> binding().set 'value').not.to.throw() expect(()-> binding().get()).not.to.throw() expect(()-> binding().transform ()->).to.throw() expect(()-> binding().transformAll ()->).to.throw() expect(()-> binding().transformSelf ()->).to.throw() expect(()-> binding().condition ()->).not.to.throw() expect(()-> binding().conditionAll ()->).not.to.throw() expect(()-> binding().bothWays()).to.throw() expect(()-> binding().stopPolling()).not.to.throw() expect(()-> binding().pollEvery(1000).stopPolling()).not.to.throw() expect(()-> binding().unBind()).not.to.throw() expect(()-> binding().chainTo ()->).not.to.throw() expect(()-> binding().updateDepsOnEvent 'a').not.to.throw() expect(()-> binding().removeEvent 'a').not.to.throw() expect(()-> binding().throttle 5).not.to.throw() binding = ()-> SimplyBind('1').of(objectA).to('prop1').of(objectB).condition ()-> expect(()-> binding().of objectA).to.throw() expect(()-> binding().ofEvent objectA).to.throw() expect(()-> binding().to 'prop2').to.throw() expect(()-> binding().toEvent '/local').to.throw() expect(()-> binding().and 'prop2').not.to.throw() expect(()-> binding().set 'value').not.to.throw() expect(()-> binding().get()).not.to.throw() expect(()-> binding().transform ()->).not.to.throw() expect(()-> binding().transformAll ()->).not.to.throw() expect(()-> binding().transformSelf ()->).to.throw() expect(()-> binding().condition ()->).not.to.throw() expect(()-> binding().conditionAll ()->).not.to.throw() expect(()-> binding().bothWays()).not.to.throw() expect(()-> binding().stopPolling()).not.to.throw() expect(()-> binding().pollEvery(1000).stopPolling()).not.to.throw() expect(()-> binding().unBind()).not.to.throw() expect(()-> binding().chainTo ()->).not.to.throw() expect(()-> binding().updateDepsOnEvent 'a').not.to.throw() expect(()-> binding().removeEvent 'a').not.to.throw() expect(()-> binding().throttle 5).not.to.throw() binding = ()-> SimplyBind('1').of(objectA).to('prop1').of(objectB).conditionAll ()-> expect(()-> binding().of objectA).to.throw() expect(()-> binding().ofEvent objectA).to.throw() expect(()-> binding().to 'prop2').to.throw() expect(()-> binding().toEvent '/local').to.throw() expect(()-> binding().and 'prop2').not.to.throw() expect(()-> binding().set 'value').not.to.throw() expect(()-> binding().get()).not.to.throw() expect(()-> binding().transform ()->).not.to.throw() expect(()-> binding().transformAll ()->).not.to.throw() expect(()-> binding().transformSelf ()->).to.throw() expect(()-> binding().condition ()->).not.to.throw() expect(()-> binding().conditionAll ()->).not.to.throw() expect(()-> binding().bothWays()).not.to.throw() expect(()-> binding().stopPolling()).not.to.throw() expect(()-> binding().pollEvery(1000).stopPolling()).not.to.throw() expect(()-> binding().unBind()).not.to.throw() expect(()-> binding().chainTo ()->).not.to.throw() expect(()-> binding().updateDepsOnEvent 'a').not.to.throw() expect(()-> binding().removeEvent 'a').not.to.throw() expect(()-> binding().throttle 5).not.to.throw() binding = ()-> SimplyBind('1').of(objectA).to('prop1').of(objectB).transform(()->).and ()-> expect(()-> binding().of objectA).to.throw() expect(()-> binding().ofEvent objectA).to.throw() expect(()-> binding().to 'prop2').to.throw() expect(()-> binding().toEvent '/local').to.throw() expect(()-> binding().and 'prop2').not.to.throw() expect(()-> binding().set 'value').not.to.throw() expect(()-> binding().get()).not.to.throw() expect(()-> binding().transform ()->).not.to.throw() expect(()-> binding().transformAll ()->).not.to.throw() expect(()-> binding().transformSelf ()->).to.throw() expect(()-> binding().condition ()->).not.to.throw() expect(()-> binding().conditionAll ()->).not.to.throw() expect(()-> binding().bothWays()).not.to.throw() expect(()-> binding().stopPolling()).not.to.throw() expect(()-> binding().pollEvery(1000).stopPolling()).not.to.throw() expect(()-> binding().unBind()).not.to.throw() expect(()-> binding().chainTo ()->).not.to.throw() expect(()-> binding().updateDepsOnEvent 'a').not.to.throw() expect(()-> binding().removeEvent 'a').not.to.throw() expect(()-> binding().throttle 5).not.to.throw() binding = ()-> SimplyBind('1').of(objectA).to('prop1').of(objectB).transform(()->).and(()->).chainTo ()-> expect(()-> binding().of objectA).to.throw() expect(()-> binding().ofEvent objectA).to.throw() expect(()-> binding().to 'prop2').to.throw() expect(()-> binding().toEvent '/local').to.throw() expect(()-> binding().and 'prop2').not.to.throw() expect(()-> binding().set 'value').not.to.throw() expect(()-> binding().get()).not.to.throw() expect(()-> binding().transform ()->).not.to.throw() expect(()-> binding().transformAll ()->).not.to.throw() expect(()-> binding().transformSelf ()->).to.throw() expect(()-> binding().condition ()->).not.to.throw() expect(()-> binding().conditionAll ()->).not.to.throw() expect(()-> binding().bothWays()).not.to.throw() expect(()-> binding().stopPolling()).not.to.throw() expect(()-> binding().pollEvery(1000).stopPolling()).not.to.throw() expect(()-> binding().unBind()).not.to.throw() expect(()-> binding().chainTo ()->).not.to.throw() expect(()-> binding().updateDepsOnEvent 'a').not.to.throw() expect(()-> binding().removeEvent 'a').not.to.throw() expect(()-> binding().throttle 5).not.to.throw() suite "Cached Bindings", ()-> test "A cached version of the binding should be returned when re-creating an existing one", ()-> # ==== ObjectProp ================================================================================= expect(SimplyBind('prop1').of(objectA).ID) .to.equal(SimplyBind('prop1').of(objectA).ID) expect(SimplyBind('prop1').of(objectA).to('prop1').of(objectB)._.deps[0].ID) .to.equal(SimplyBind('prop1').of(objectB).ID) if isBrowser # ==== DOMAttr ================================================================================= expect(SimplyBind('attr:someattr').of(regA).ID) .to.equal(SimplyBind('attr:someattr').of(regA).ID) expect(SimplyBind('attr:someattr').of(regA).to('attr:someattr').of(regB)._.deps[0].ID) .to.equal(SimplyBind('attr:someattr').of(regB).ID) # ==== DOMValue ================================================================================= expect(SimplyBind('value').of(inputA).ID) .to.equal(SimplyBind('value').of(inputA).ID) expect(SimplyBind('value').of(inputA).to('value').of(inputB)._.deps[0].ID) .to.equal(SimplyBind('value').of(inputB).ID) # ==== DOMCheckbox Single ================================================================================= expect(SimplyBind('checked').of(checkboxA).ID) .to.equal(SimplyBind('checked').of(checkboxA).ID) expect(SimplyBind('checked').of(checkboxA).to('checked').of(checkboxB)._.deps[0].ID) .to.equal(SimplyBind('checked').of(checkboxB).ID) # ==== DOMCheckbox ================================================================================= expect(SimplyBind('checked').of($checkboxFields).ID) .to.equal(SimplyBind('checked').of($checkboxFields).ID) # ==== DOMRadio ================================================================================= expect(SimplyBind('checked').of($radioFields).ID) .to.equal(SimplyBind('checked').of($radioFields).ID) # ==== DOMText ================================================================================= expect(SimplyBind('textContent').of(regA).ID) .to.equal(SimplyBind('textContent').of(regA).ID) expect(SimplyBind('textContent').of(regA).to('textContent').of(regB)._.deps[0].ID) .to.equal(SimplyBind('textContent').of(regB).ID) # ==== Event ================================================================================= expect(SimplyBind(0).ofEvent('someEvent').of(eventEmitterA).ID) .to.equal(SimplyBind(0).ofEvent('someEvent').of(eventEmitterA).ID) expect(SimplyBind(0).ofEvent('someEvent').of(eventEmitterA).toEvent('someEvent').of(eventEmitterB)._.deps[0].ID) .to.equal(SimplyBind(0).ofEvent('someEvent').of(eventEmitterB).ID) # ==== Array ================================================================================= expect(SimplyBind(arrayA).ID) .to.equal(SimplyBind(arrayA).ID) expect(SimplyBind(arrayA).to(arrayB)._.deps[0].ID) .to.equal(SimplyBind(arrayB).ID) # ==== Function ================================================================================= expect(SimplyBind(fnA).ID) .to.equal(SimplyBind(fnA).ID) expect(SimplyBind(fnA).to(fnB)._.deps[0].ID) .to.equal(SimplyBind(fnB).ID) restartSandbox() test "When binding an object prop with a placeholder indicator in the selector for an existing binding that didn't have indicators, placeholders will be rescanned", ()-> dispatcher = 'prop':'This is an {{size}} size shirt' receiver = 'prop':'' SimplyBind('prop').of(dispatcher) .to('prop').of(receiver) expect(receiver.prop).to.equal(dispatcher.prop) expect(dispatcher.prop).to.equal 'This is an {{size}} size shirt' SimplyBind('prop').of(dispatcher) .to('prop.size').of(receiver) dispatcher.prop = 'XXL' expect(dispatcher.prop).to.equal 'XXL' expect(receiver.prop).to.equal 'This is an XXL size shirt' test "When re-binding a binding with placeholder where the global placeholders regEx has changed to a diff regEx from present in the original binding, the binding's regEx shouldn't change", ()-> dispatcher = 'noun':'', 'adjective':'' receiver = 'prop':'This {{noun}} is **adjective**' SimplyBind('noun').of(dispatcher) .to('prop.noun').of(receiver) dispatcher.noun = 'test' expect(receiver.prop).to.equal 'This test is **adjective**' SimplyBind.setOption 'placeholder', ['**', '**'] SimplyBind('adjective').of(dispatcher) .to('prop.adjective').of(receiver) dispatcher.adjective = 'good' expect(receiver.prop).to.equal 'This test is **adjective**' dispatcher.noun = 'suite' expect(receiver.prop).to.equal 'This suite is **adjective**' SimplyBind.setOption 'placeholder', ['{{', '}}'] test "When re-binding an object prop that was deleted manually make sure to re-make the property a live one", ()-> objectA.prop = 1 SimplyBind('prop').of(objectA) .to('prop').of(objectB) expect(objectB.prop).to.equal(objectA.prop) objectA.prop = 2 expect(objectB.prop).to.equal(objectA.prop) delete objectB.prop objectA.prop = 3 expect(objectB.prop).not.to.equal(objectA.prop) SimplyBind('prop').of(objectA) .to('prop').of(objectB) expect(objectB.prop).to.equal(objectA.prop) objectA.prop = 4 expect(objectB.prop).to.equal(objectA.prop) restartSandbox() test "When binding an index of an array that's brand new and the array is tracked and so are its children, the new index should be made so it also tracked", ()-> invokeCount = 0 SimplyBind(arrayA, 'trackArrayChildren':true).to ()-> invokeCount++ expect(invokeCount).to.equal 1 arrayA[3] = 44 expect(invokeCount).to.equal 2 arrayA[20] = 21 expect(invokeCount).to.equal 2 SimplyBind('18').of(arrayA) arrayA[18] = 188 expect(invokeCount).to.equal 4 arrayA[18] = 1888 expect(invokeCount).to.equal 5 suite "Data Binding Behavior", ()-> suiteSetup(restartSandbox) test "Object properties should behave the same way across all object types", ()-> dispatcher = 'standard': objectA 'array': arrayA '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': objectB 'array': arrayB '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() for name,object of dispatcher SimplyBind('prop').of(dispatcher[name]).to('prop').of(receiver[name]) expect(receiver[name].prop).to.be.undefined SimplyBind('prop').of(dispatcher[name]).set(100) expect(receiver[name].prop).to.equal(100) restartSandbox() test "Object properties should be made into live properties when SimplyBind.options.liveProps is on", ()-> expect(SimplyBind.options.liveProps).to.be.true invokeCount = 0 SimplyBind('prop1').of(objectA).to ()-> invokeCount++ expect(invokeCount).to.equal(1) objectA.prop1 = !objectA.prop1 expect(invokeCount).to.equal(2) SimplyBind('prop1').of(objectA).set !objectA.prop1 expect(invokeCount).to.equal(3) SimplyBind.setOption 'liveProps', false invokeCount = 0 SimplyBind('prop1').of(objectB).to ()-> invokeCount++ expect(invokeCount).to.equal(1) objectB.prop1 = !objectB.prop1 expect(invokeCount).to.equal(1) SimplyBind('prop1').of(objectB).set !objectB.prop1 expect(invokeCount).to.equal(2) SimplyBind.setOption 'liveProps', true restartSandbox() test "Object properties inherited from the prototype should be made into live properties when SimplyBind.options.mutateInherited is on", ()-> testArray = [] SimplyBind.setOption 'mutateInherited', true Array::inherited = 'I was inherited' SimplyBind('inherited').of(testArray).to('prop1').of objectB testArray.inherited = 'inherited live prop change :)' expect(objectB.prop1).to.equal 'inherited live prop change :)' delete Array::inherited