/*______________ | ______ | U I Z E J A V A S C R I P T F R A M E W O R K | / / | --------------------------------------------------- | / O / | MODULE : Uize.Test.Uize.Util.PropertyAdapter Class | / / / | | / / / /| | ONLINE : http://uize.com | /____/ /__/_| | COPYRIGHT : (c)2011-2016 UIZE | /___ | LICENSE : Available under MIT License or GNU General Public License |_______________| http://uize.com/license.html */ /* Module Meta Data type: Test importance: 1 codeCompleteness: 100 docCompleteness: 100 */ /*? Introduction The =Uize.Test.Uize.Util.PropertyAdapter= module defines a suite of unit tests for the =Uize.Util.PropertyAdapter= module. *DEVELOPERS:* `Chris van Rensburg` */ Uize.module ({ name:'Uize.Test.Uize.Util.PropertyAdapter', required:'Uize.Class', builder:function () { 'use strict'; var _DummyClass = Uize.Class.subclass (); function _getRig (_extraAdapterProperties) { var _instanceA = new _DummyClass ({energy:'solar'}), _instanceB = new _DummyClass ({energy:'wind'}) ; return { _instanceA:_instanceA, _instanceB:_instanceB, _propertyAdapter:Uize.Util.PropertyAdapter ( Uize.copyInto ( { propertyA:{instance:_instanceA,property:'energy'}, propertyB:{instance:_instanceB,property:'energy'} }, _extraAdapterProperties ) ) }; } function _getRigWithValueAdapter () { var _instanceA = new _DummyClass ({normal:2}), _instanceB = new _DummyClass ({scaled:4}) ; return { _instanceA:_instanceA, _instanceB:_instanceB, _propertyAdapter:Uize.Util.PropertyAdapter ({ propertyA:{instance:_instanceA,property:'normal'}, propertyB:{instance:_instanceB,property:'scaled'}, valueAdapter:{ aToB:function (_value) {return _value * 2}, bToA:function (_value) {return _value / 2} } }) }; } function _propertyAorBConformerTest (_whichProperty) { var _propertyName = 'property' + _whichProperty; function _conformerTestCase (_title,_value,_expectedConformedValue) { return { title:_title, test:function () { var _propertyAdapter = Uize.Util.PropertyAdapter (); _propertyAdapter.set (_propertyName,_value); return this.expect (_expectedConformedValue,_propertyAdapter.get (_propertyName)); } }; } var _someDummyInstance = Uize.Class (); return { title:'Test that the conformer for ' + _propertyName + ' works correctly', test:[ _conformerTestCase ( 'Specifying simply an instance is resolved to an object where the instance property for the object is set to the specified instance, and the property property is set to "value"', _someDummyInstance, {instance:_someDummyInstance,property:'value'} ), { title:'An array value is conformed, such that the first element specifies the instance and the second element specifies the property', test:[ _conformerTestCase ( 'Specifying an array is resolved to an object where the instance is the first element from the array, and the property is the second element from the array', [_someDummyInstance,'foo'], {instance:_someDummyInstance,property:'foo'} ), _conformerTestCase ( 'Specifying the value null for the second element results in the property being defaulted to the "value" property', [_someDummyInstance,null], {instance:_someDummyInstance,property:'value'} ), _conformerTestCase ( 'Specifying the value undefined for the second element results in the property being defaulted to the "value" property', [_someDummyInstance,undefined], {instance:_someDummyInstance,property:'value'} ), _conformerTestCase ( 'Specifying a second element results in the property being defaulted to the "value" property', [_someDummyInstance], {instance:_someDummyInstance,property:'value'} ) ] }, { title:'An object value is conformed, such that the property property is defaulted to "value" if it is null, undefined, or omitted', test:[ _conformerTestCase ( 'Specifying the value null for the property results in the property being defaulted to "value"', {instance:_someDummyInstance,property:null}, {instance:_someDummyInstance,property:'value'} ), _conformerTestCase ( 'Specifying the value undefined for the property results in the property being defaulted to "value"', {instance:_someDummyInstance,property:undefined}, {instance:_someDummyInstance,property:'value'} ), _conformerTestCase ( 'Not specifying the property results in it being defaulted to "value"', {instance:_someDummyInstance}, {instance:_someDummyInstance,property:'value'} ) ] }, { title:'The values null or undefined for the value adapter are conformed to the value null', test:[ _conformerTestCase ( 'Specifying the value null for the value adapter results in it remaining null', null, null ), _conformerTestCase ( 'Specifying the value undefined for the value adapter results in it remaining null', undefined, undefined ) ] } ] }; } return Uize.Test.resolve ({ title:'Test for Uize.Util.PropertyAdapter Module', test:[ Uize.Test.requiredModulesTest ('Uize.Util.PropertyAdapter'), { title:'Test that an instance of the class can be successfully created', test:function () { return this.expectInstanceOf (Uize.Util.PropertyAdapter,Uize.Util.PropertyAdapter ()); } }, { title:'Connecting a property adapter between two properties of different objects immediately synchronizes property B to property A', test:function () { var _rig = _getRig (); return this.expect ('solar',_rig._instanceB.get ('energy')); } }, { title:'Test basic synchronization (without a value adapter) in both directions', test:[ { title:'Test that a property adapter correctly synchronizes from property A to property B', test:function () { var _rig = _getRig (); _rig._instanceA.set ({energy:'geothermal'}); return this.expect ('geothermal',_rig._instanceB.get ('energy')); } }, { title:'Test that a property adapter correctly synchronizes from property B to property A', test:function () { var _rig = _getRig (); _rig._instanceB.set ({energy:'geothermal'}); return this.expect ('geothermal',_rig._instanceA.get ('energy')); } } ] }, { title:'Test that synchronization with a value adapter works in both directions', test:[ { title:'The value adapter is applied when synchronizing from propertyA to propertyB', test:function () { var _rig = _getRigWithValueAdapter (); _rig._instanceA.set ({normal:2.5}); return this.expect (5,_rig._instanceB.get ('scaled')); } }, { title:'The value adapter is applied when synchronizing from propertyB to propertyA', test:function () { var _rig = _getRigWithValueAdapter (); _rig._instanceB.set ({scaled:5}); return this.expect (2.5,_rig._instanceA.get ('normal')); } } ] }, { title:'Test that changing the value adapter after properties have already been connected is handled correctly', test:[ { title:'Changing a value adapter mid-stream results in propertyB being immediately re-synchronized to propertyA using the new value adapter, with correct synchronization in both directions thereafter', test:function () { var _rig = _getRigWithValueAdapter (); _rig._propertyAdapter.set ({ valueAdapter:{ aToB:function (_value) {return _value * 3}, bToA:function (_value) {return _value / 3} } }); var _propertyBAfterChangingValueAdapter = _rig._instanceB.get ('scaled'); _rig._instanceA.set ({normal:5}); var _propertyBAfterSettingPropertyA = _rig._instanceB.get ('scaled'); _rig._instanceB.set ({scaled:30}); var _propertyAAfterSettingPropertyB = _rig._instanceA.get ('normal'); return ( this.expect (6,_propertyBAfterChangingValueAdapter) && this.expect (15,_propertyBAfterSettingPropertyA) && this.expect (10,_propertyAAfterSettingPropertyB) ); } }, { title:'Nulling out a value adapter mid-stream results in propertyB being immediately re-synchronized to propertyA without any value adapter translation, with correct synchronization in both directions thereafter', test:function () { var _rig = _getRigWithValueAdapter (); _rig._propertyAdapter.set ({valueAdapter:null}); var _propertyBAfterChangingValueAdapter = _rig._instanceB.get ('scaled'); _rig._instanceA.set ({normal:5}); var _propertyBAfterSettingPropertyA = _rig._instanceB.get ('scaled'); _rig._instanceB.set ({scaled:30}); var _propertyAAfterSettingPropertyB = _rig._instanceA.get ('normal'); return ( this.expect (2,_propertyBAfterChangingValueAdapter) && this.expect (5,_propertyBAfterSettingPropertyA) && this.expect (30,_propertyAAfterSettingPropertyB) ); } } ] }, { title:'Test that the connected state property is observed correctly', test:[ { title:'Connecting a property adapter between two properties of different objects with the adapter not initially connected results in property B *not* being immediately synchronized to property A', test:function () { var _rig = _getRig ({connected:false}); return this.expect ('wind',_rig._instanceB.get ('energy')); } }, { title:'Disconnecting a property adapter by setting its connected state property to false results in properties no longer being synchronized', test:function () { var _rig = _getRig (); _rig._propertyAdapter.set ({connected:false}); _rig._instanceA.set ({energy:'geothermal'}); var _propertyBAfterSettingPropertyA = _rig._instanceB.get ('energy'); _rig._instanceB.set ({energy:'tidal'}); var _propertyAAfterSettingPropertyB = _rig._instanceA.get ('energy'); return ( this.expect ('solar',_propertyBAfterSettingPropertyA) && this.expect ('geothermal',_propertyAAfterSettingPropertyB) ); } }, { title:'Disconnecting and then reconnecting a property adapter results in properties once again being synchronized correctly', test:function () { var _rig = _getRig (); _rig._propertyAdapter.set ({connected:false}); _rig._propertyAdapter.set ({connected:true}); _rig._instanceA.set ({energy:'geothermal'}); var _propertyBAfterSettingPropertyA = _rig._instanceB.get ('energy'); _rig._instanceB.set ({energy:'tidal'}); var _propertyAAfterSettingPropertyB = _rig._instanceA.get ('energy'); return ( this.expect ('geothermal',_propertyBAfterSettingPropertyA) && this.expect ('tidal',_propertyAAfterSettingPropertyB) ); } } ] }, { title:'Test that the conformer for the propertyA and propertyB state properties works correctly', test:[ _propertyAorBConformerTest ('A'), _propertyAorBConformerTest ('B') ] }, { title:'Test that changing either propertyA or propertyB mid-stream is handled correctly', test:[ { title:'When changing propertyA, propertyB is immediately re-synchronized to the new property for propertyA', test:function () { var _rig = _getRig (); _rig._propertyAdapter.set ({ propertyA:{instance:new _DummyClass ({energy:'biofuel'}),property:'energy'} }); return this.expect ('biofuel',_rig._instanceB.get ('energy')); } }, { title:'When changing propertyB, propertyB is immediately synchronized to the property for propertyA', test:function () { var _rig = _getRig (), _newInstanceB = new _DummyClass ({energy:'geothermal'}) ; _rig._propertyAdapter.set ({propertyB:{instance:_newInstanceB,property:'energy'}}); return this.expect ('solar',_newInstanceB.get ('energy')); } }, { title:'When changing propertyA, the old property for propertyA is no longer synchronized when the property for propertyB is modified', test:function () { var _rig = _getRig (); _rig._propertyAdapter.set ({ propertyA:{instance:new _DummyClass ({energy:'geothermal'}),property:'energy'} }); _rig._instanceB.set ({energy:'tidal'}); return this.expect ('solar',_rig._instanceA.get ('energy')); } }, { title:'After changing propertyA, modifying the value of the old property for propertyA no longer has an affect on the property for propertyB', test:function () { var _rig = _getRig (); _rig._propertyAdapter.set ({ propertyA:{instance:new _DummyClass ({energy:'geothermal'}),property:'energy'} }); _rig._instanceA.set ({energy:'tidal'}); return this.expect ('geothermal',_rig._instanceB.get ('energy')); } }, { title:'When changing propertyB, the old property for propertyB is no longer synchronized when the property for propertyA is modified', test:function () { var _rig = _getRig (), _newInstanceB = new _DummyClass ({energy:'geothermal'}) ; _rig._propertyAdapter.set ({propertyB:{instance:_newInstanceB,property:'energy'}}); _rig._instanceA.set ({energy:'tidal'}); return this.expect ('solar',_rig._instanceB.get ('energy')); } }, { title:'After changing propertyB, modifying the value of the old property for propertyB no longer has an affect on the property for propertyA', test:function () { var _rig = _getRig (); _rig._propertyAdapter.set ({ propertyB:{instance:new _DummyClass ({energy:'geothermal'}),property:'energy'} }); _rig._instanceB.set ({energy:'tidal'}); return this.expect ('solar',_rig._instanceA.get ('energy')); } } ] }, { title:'Test that nulling out either or both of propertyA and propertyB, after properties have already been connected, is handled correctly', test:[ { title:'Test that changing propertyA to null after a property adapter has already been connected is handled correctly', test:function () { var _rig = _getRig (); _rig._propertyAdapter.set ({propertyA:null}); var _propertyBAfterNullingPropertyA = _rig._instanceB.get ('energy'); _rig._instanceA.set ({energy:'geothermal'}); var _propertyBAfterSettingOldPropertyA = _rig._instanceB.get ('energy'); _rig._instanceB.set ({energy:'tidal'}); var _oldPropertyAAfterSettingPropertyB = _rig._instanceA.get ('energy'); return ( this.expect ('solar',_propertyBAfterNullingPropertyA) && this.expect ('solar',_propertyBAfterSettingOldPropertyA) && this.expect ('geothermal',_oldPropertyAAfterSettingPropertyB) ); } }, { title:'Test that changing propertyB to null after a property adapter has already been connected is handled correctly', test:function () { var _rig = _getRig (); _rig._propertyAdapter.set ({propertyB:null}); var _oldPropertyBAfterNullingPropertyB = _rig._instanceB.get ('energy'); _rig._instanceB.set ({energy:'geothermal'}); var _propertyAAfterSettingOldPropertyB = _rig._instanceA.get ('energy'); _rig._instanceA.set ({energy:'tidal'}); var _oldPropertyBAfterSettingPropertyA = _rig._instanceB.get ('energy'); return ( this.expect ('solar',_oldPropertyBAfterNullingPropertyB) && this.expect ('solar',_propertyAAfterSettingOldPropertyB) && this.expect ('geothermal',_oldPropertyBAfterSettingPropertyA) ); } }, { title:'Test that changing propertyA and propertyB to null after a property adapter has already been connected is handled correctly', test:function () { var _rig = _getRig (); _rig._propertyAdapter.set ({propertyA:null,propertyB:null}); var _oldPropertyAAfterNullingPropertyB = _rig._instanceA.get ('energy'), _oldPropertyBAfterNullingPropertyA = _rig._instanceA.get ('energy') ; _rig._instanceA.set ({energy:'geothermal'}); var _oldPropertyBAfterSettingOldPropertyA = _rig._instanceB.get ('energy'); _rig._instanceB.set ({energy:'tidal'}); var _oldPropertyAAfterSettingOldPropertyB = _rig._instanceA.get ('energy'); return ( this.expect ('solar',_oldPropertyAAfterNullingPropertyB) && this.expect ('solar',_oldPropertyBAfterNullingPropertyA) && this.expect ('solar',_oldPropertyBAfterSettingOldPropertyA) && this.expect ('geothermal',_oldPropertyAAfterSettingOldPropertyB) ); } } ] }, { title:'Test that the infinite loop prevention mechanism works correctly', test:[ { title:'The infinite loop prevention mechanism does not prevent two properties of the same instance from being connected successfully by an adapter', test:function () { var _instance = new _DummyClass ({prop1:'foo',prop2:'bar'}), _propertyAdapter = Uize.Util.PropertyAdapter ({ propertyA:{instance:_instance,property:'prop1'}, propertyB:{instance:_instance,property:'prop2'} }), _propertyBAfterAdapterConnected = _instance.get ('prop2') ; _instance.set ({prop1:'doo'}); var _propertyBAfterSettingPropertyA = _instance.get ('prop2'); _instance.set ({prop2:'goo'}); var _propertyAAfterSettingPropertyB = _instance.get ('prop1'); return ( this.expect ('foo',_propertyBAfterAdapterConnected) && this.expect ('doo',_propertyBAfterSettingPropertyA) && this.expect ('goo',_propertyAAfterSettingPropertyB) ); } }, { title:'The infinite loop prevention mechanism does not prevent three properties of the same instance from being connected successfully by two adapters', test:function () { var _instance = new _DummyClass ({prop1:'foo',prop2:'bar',prop3:'ha'}), _propertyAdapter1 = Uize.Util.PropertyAdapter ({ propertyA:{instance:_instance,property:'prop1'}, propertyB:{instance:_instance,property:'prop2'} }), _propertyAdapter2 = Uize.Util.PropertyAdapter ({ propertyA:{instance:_instance,property:'prop2'}, propertyB:{instance:_instance,property:'prop3'} }), _prop2AfterAdapterConnected = _instance.get ('prop2'), _prop3AfterAdapterConnected = _instance.get ('prop3') ; _instance.set ({prop1:'doo'}); var _prop2AfterSettingProp1 = _instance.get ('prop2'), _prop3AfterSettingProp1 = _instance.get ('prop3') ; _instance.set ({prop2:'hoo'}); var _prop1AfterSettingProp2 = _instance.get ('prop1'), _prop3AfterSettingProp2 = _instance.get ('prop3') ; _instance.set ({prop3:'hoo'}); var _prop1AfterSettingProp3 = _instance.get ('prop1'), _prop2AfterSettingProp3 = _instance.get ('prop2') ; return ( this.expect ('foo',_prop2AfterAdapterConnected) && this.expect ('foo',_prop3AfterAdapterConnected) && this.expect ('doo',_prop2AfterSettingProp1) && this.expect ('doo',_prop3AfterSettingProp1) && this.expect ('hoo',_prop1AfterSettingProp2) && this.expect ('hoo',_prop3AfterSettingProp2) && this.expect ('hoo',_prop1AfterSettingProp3) && this.expect ('hoo',_prop2AfterSettingProp3) ); } }, { title:'An infinite loop is prevented when two properties combined in a property adapter are guaranteed to never be able to ever settle their values, because of a divergent value adapter', test:function () { var _DummyClass = Uize.Class.subclass (); _DummyClass.stateProperties ({ _property1:'property1', _property2:'property2' }); var _instance = new _DummyClass, _propertyAdapter = Uize.Util.PropertyAdapter ({ propertyA:{instance:_instance,property:'property1'}, propertyB:{instance:_instance,property:'property2'}, valueAdapter:{ aToB:function (_value) {return _value * 2}, bToA:function (_value) {return _value * 2} } }) ; _instance.set ({property1:1}); return true; } }, { title:'An infinite loop is prevented when two properties combined in a property adapter are guaranteed to never be able to ever settle their values, based upon the definition of those properties', test:function () { var _CrazyClass = Uize.Class.subclass (); _CrazyClass.stateProperties ({ _property1:{ name:'property1', conformer:function (_value) {return _value + 1}, value:1 }, _property2:{ name:'property2', conformer:function (_value) {return _value + 1}, value:1 } }); var _instance = new _CrazyClass, _propertyAdapter = Uize.Util.PropertyAdapter ({ propertyA:{instance:_instance,property:'property1'}, propertyB:{instance:_instance,property:'property2'} }) ; _instance.set ({property1:10}); return true; } } ] } ] }); } });