// // # DependencyObservable // DependencyObservable is a fundamental building block in the NativeScript visual tree (DOM). // It provides enhanced property system allowing for various value modifiers on a per property basis. // This enables cascading values - e.g. default vs. inherited vs. local. // Load the module using the following code: // ``` JavaScript import dependencyObservableModule = require("ui/core/dependency-observable"); // ``` // import TKUnit = require("../TKUnit"); import types = require("utils/types"); import observableModule = require("data/observable"); var currentChangeData: dependencyObservableModule.PropertyChangeData; var propertyNameCount = 0; function generatePropertyName() { var name = "test" + propertyNameCount++; return name; } function onTestPropertyChanged(data: dependencyObservableModule.PropertyChangeData) { currentChangeData = data; } function validateDOChangeData(obj: TestDO, property: dependencyObservableModule.Property, oldValue: any, newValue: any) { TKUnit.assert(types.isDefined(currentChangeData), "metadata.onValueChanged function not called."); TKUnit.assert(currentChangeData.object === obj, "metadata.onValueChanged function called with wrong object member."); TKUnit.assert(currentChangeData.property === property, "metadata.onValueChanged method called with wrong property member"); TKUnit.assert(currentChangeData.newValue === newValue, "metadata.onValueChanged method called with wrong newValue member"); TKUnit.assert(currentChangeData.oldValue === oldValue, "metadata.onValueChanged method called with wrong oldValue member"); TKUnit.assert(currentChangeData.eventName === observableModule.Observable.propertyChangeEvent, "metadata.onValueChanged method called with wrong eventName member"); } function validateChangeData(data: observableModule.PropertyChangeData, obj: observableModule.Observable, propertyName: string, newValue: any) { TKUnit.assert(types.isDefined(data), "DependencyObservable.on for propertyChange not called."); TKUnit.assert(data.object === obj, "DependencyObservable.on called with wrong object member."); TKUnit.assert(data.propertyName === propertyName, "DependencyObservable.on called with wrong propertyName member"); TKUnit.assert(data.value === newValue, "DependencyObservable.on called with wrong value member"); TKUnit.assert(data.eventName === observableModule.Observable.propertyChangeEvent, "DependencyObservable.on method called with wrong eventName member"); } // // ### Creating a Property // The property backing mechanism in each DependencyObservable instance is implemented via the Property class. // Basically you create a new Property for a class (type): // ``` JavaScript var testProperty = new dependencyObservableModule.Property( "test", "TestDO", new dependencyObservableModule.PropertyMetadata( false, dependencyObservableModule.PropertyMetadataSettings.None /*0*/, onTestPropertyChanged ) ); // ``` // var testProperty1 = new dependencyObservableModule.Property( "test1", "TestDO", new dependencyObservableModule.PropertyMetadata( 0, dependencyObservableModule.PropertyMetadataSettings.None /*0*/, onTestPropertyChanged ) ); function validateProperty(value: any): boolean { return types.isNumber(value) && value >= 0; } var testPropertyWithValidation = new dependencyObservableModule.Property( "testWithValidation", "TestDO", new dependencyObservableModule.PropertyMetadata( 0, dependencyObservableModule.PropertyMetadataSettings.None /*0*/, onTestPropertyChanged, validateProperty ) ); class TestDO extends dependencyObservableModule.DependencyObservable { get test(): boolean { return this._getValue(testProperty); } set test(value: boolean) { this._setValue(testProperty, value); } get test1(): number { return this._getValue(testProperty1); } set test1(value: number) { this._setValue(testProperty1, value); } get testWithValidation(): number { return this._getValue(testPropertyWithValidation); } set testWithValidation(value: number) { this._setValue(testPropertyWithValidation, value); } public _onPropertyChanged(property: dependencyObservableModule.Property, oldValue: any, newValue: any) { super._onPropertyChanged(property, oldValue, newValue); } } export function test_DO_Members_AreDefined() { var obj = new dependencyObservableModule.DependencyObservable(); TKUnit.assert(types.isDefined(obj._setValue), "DependencyObservable._setValue is undefined."); TKUnit.assert(types.isDefined(obj._getValue), "DependencyObservable._getValue is undefined."); TKUnit.assert(types.isDefined(obj._getValueSource), "DependencyObservable._getValueSource is undefined."); TKUnit.assert(types.isDefined(obj._resetValue), "DependencyObservable._resetValue is undefined."); TKUnit.assert(types.isDefined(obj._onPropertyChanged), "DependencyObservable._resetValue is undefined."); TKUnit.assert(types.isDefined(obj._eachSetProperty), "DependencyObservable._resetValue is undefined."); } export function test_NewProperty_WillThrow_If_Name_IsInvalid() { var thrown = false; try { /* tslint:disable:no-unused-variable */ var p = new dependencyObservableModule.Property("", "", undefined); /* tslint:enable:no-unused-variable */ } catch (e) { thrown = true; TKUnit.assert(e.message === "Name should not be null or empty string.", "Invalid error message"); } TKUnit.assert(thrown, "Should not create a Property with invalid name."); } export function test_NewProperty_WillThrow_If_OwnerType_IsInvalid() { var thrown = false; try { /* tslint:disable:no-unused-variable */ var p = new dependencyObservableModule.Property(generatePropertyName(), "", undefined); /* tslint:enable:no-unused-variable */ } catch (e) { thrown = true; TKUnit.assert(e.message === "OwnerType should not be null or empty string.", "Invalid error message"); } TKUnit.assert(thrown, "Should not create a Property with invalid owner type."); } export function test_NewProperty_WillThrow_If_Metadata_IsInvalid() { var thrown = false; var p; try { // check for undefined p = new dependencyObservableModule.Property(generatePropertyName(), "testOwner", undefined); } catch (e) { thrown = true; TKUnit.assert(e.message === "Expected valid PropertyMetadata instance.", "Invalid error message"); } TKUnit.assert(thrown, "Should not create a Property with undefined metadata."); thrown = false; try { // check for invalid type var metadata: any = {}; p = new dependencyObservableModule.Property(generatePropertyName(), "testOwner", metadata); } catch (e) { thrown = true; TKUnit.assert(e.message === "Expected valid PropertyMetadata instance.", "Invalid error message"); } TKUnit.assert(thrown, "Should not create a Property with invalid metadata type."); } export function test_NewProperty_WillThrow_If_Key_AlreadyRegistered() { var thrown = false; try { /* tslint:disable:no-unused-variable */ var p = new dependencyObservableModule.Property("test", "testOwner", new dependencyObservableModule.PropertyMetadata(0)); var p1 = new dependencyObservableModule.Property("test", "testOwner", new dependencyObservableModule.PropertyMetadata(0)); /* tslint:enable:no-unused-variable */ } catch (e) { thrown = true; TKUnit.assert(e.message === "Property test already registered for type testOwner.", "Invalid error message"); } TKUnit.assert(thrown, "Should not create a Property with invalid name."); } export function test_Property_Id_IsUnique() { var p = new dependencyObservableModule.Property(generatePropertyName(), "testOwner", new dependencyObservableModule.PropertyMetadata(0)); var p1 = new dependencyObservableModule.Property(generatePropertyName(), "testOwner", new dependencyObservableModule.PropertyMetadata(0)); TKUnit.assert(p.id !== p1.id, "Property.id should be unique."); } export function test_PropertyMetadataSettings_HasValid_Memberes() { TKUnit.assert(dependencyObservableModule.PropertyMetadataSettings.None === 0, "PropertyMetadataSettings.None not defined correctly."); TKUnit.assert(dependencyObservableModule.PropertyMetadataSettings.AffectsLayout === 1, "PropertyMetadataSettings.AffectsLayout not defined correctly."); TKUnit.assert(dependencyObservableModule.PropertyMetadataSettings.AffectsStyle === 2, "PropertyMetadataSettings.AffectsStyle not defined correctly."); TKUnit.assert(dependencyObservableModule.PropertyMetadataSettings.Inheritable === 4, "PropertyMetadataSettings.Inheritable not defined correctly."); } export function test_ValueSource_HasValid_Memberes() { TKUnit.assert(dependencyObservableModule.ValueSource.Default === 0, "ValueSource.None not defined correctly."); TKUnit.assert(dependencyObservableModule.ValueSource.Inherited === 1, "ValueSource.Inherited not defined correctly."); TKUnit.assert(dependencyObservableModule.ValueSource.Css === 2, "ValueSource.Css not defined correctly."); TKUnit.assert(dependencyObservableModule.ValueSource.Local === 3, "ValueSource.Local not defined correctly."); TKUnit.assert(dependencyObservableModule.ValueSource.VisualState === 4, "ValueSource.VisualState not defined correctly."); } export function test_PropertyMetadata_Options() { var options = dependencyObservableModule.PropertyMetadataSettings.AffectsLayout | dependencyObservableModule.PropertyMetadataSettings.AffectsStyle | dependencyObservableModule.PropertyMetadataSettings.Inheritable; var p = new dependencyObservableModule.Property(generatePropertyName(), "testOwner", new dependencyObservableModule.PropertyMetadata(0, options)); TKUnit.assert(p.affectsLayout === true, "Property.affectsLayout not working as expected."); TKUnit.assert(p.affectsStyle === true, "Property.affectsStyle not working as expected."); TKUnit.assert(p.inheritable === true, "Property.inheritable not working as expected."); TKUnit.assert(p.metadata.options === options, "PropertyMetadata.options not properly set."); } export function test_PropertyEntry_effectiveValue() { var entry = new dependencyObservableModule.PropertyEntry(); // default value TKUnit.assertEqual(entry.effectiveValue, undefined, "effectiveValue should be undefined."); TKUnit.assertEqual(entry.valueSource, dependencyObservableModule.ValueSource.Default, "ValueSource.Default not working as expected."); // local value entry.localValue = 10; entry.resetValue(); // default value TKUnit.assertEqual(entry.effectiveValue, undefined, "ValueSource.Default not working as expected."); TKUnit.assertEqual(entry.valueSource, dependencyObservableModule.ValueSource.Default, "ValueSource.Default not working as expected."); } export function test_DependencyObservable_get_set_AreOverriden() { var dO = new TestDO(); dO.test = true; TKUnit.assert(dO.get("test") === true, "DependencyObservable should override Observable.get"); dO.set("test", false); TKUnit.assert(dO.test === false, "DependencyObservable should override Observable.set"); } export function test_DependencyObservable_getValue_setValue() { var dO = new TestDO(); // implicitly use ValueSource.Local when not specified dO._setValue(testProperty1, 5); TKUnit.assertEqual(dO.test1, 5, "expected true after _setValue."); TKUnit.assertEqual(dO._getValueSource(testProperty1), dependencyObservableModule.ValueSource.Local, "_setValue without third parameter should use ValueSource.Local"); dO._setValue(testProperty1, 10, dependencyObservableModule.ValueSource.Inherited); TKUnit.assertEqual(dO.test1, 5, "ValueSource.Local should have higher priority than Inherited."); // revert to default value dO._resetValue(testProperty1); TKUnit.assertEqual(dO.test1, 10, "_resetValue should revert to inheritedValue"); // test the ValueSource parameter dO._setValue(testProperty1, 10, dependencyObservableModule.ValueSource.Inherited); TKUnit.assertEqual(dO.test1, 10, "_setValue with three parameters not working as expected."); TKUnit.assertEqual(dO._getValueSource(testProperty1), dependencyObservableModule.ValueSource.Inherited, "expected ValueSource.Inherited"); dO._setValue(testProperty1, 20, dependencyObservableModule.ValueSource.Local); TKUnit.assertEqual(dO.test1, 20, "_setValue with three parameters not working as expected."); TKUnit.assertEqual(dO._getValueSource(testProperty1), dependencyObservableModule.ValueSource.Local, "expected ValueSource.Local"); // reset the Local value and verify the effectiveValue will be reverted to the Inherited one. dO._resetValue(testProperty1, dependencyObservableModule.ValueSource.Local); TKUnit.assertEqual(dO.test1, 10, "_resetValue for ValueSource not working as expected."); TKUnit.assertEqual(dO._getValueSource(testProperty1), dependencyObservableModule.ValueSource.Inherited, "expected ValueSource.Inherited"); } export function test_PropertyMetadata_onValueChanged_Callback_IsInvoked() { currentChangeData = undefined; var dO = new TestDO(); dO.test = true; validateDOChangeData(dO, testProperty, false, true); currentChangeData = undefined; var dO1 = new TestDO(); dO1.test1 = 10; validateDOChangeData(dO1, testProperty1, 0, 10); // reset value currentChangeData = undefined; dO1._resetValue(testProperty1); validateDOChangeData(dO1, testProperty1, 10, 0); currentChangeData = undefined; dO1._setValue(testProperty1, 10, dependencyObservableModule.ValueSource.Inherited); validateDOChangeData(dO1, testProperty1, 0, 10); currentChangeData = undefined; dO1._setValue(testProperty1, 20, dependencyObservableModule.ValueSource.Local); validateDOChangeData(dO1, testProperty1, 10, 20); currentChangeData = undefined; dO1._resetValue(testProperty1, dependencyObservableModule.ValueSource.Local); validateDOChangeData(dO1, testProperty1, 20, 10); } export function test_Observable_on_propertyChange_IsRaised() { var dO1 = new TestDO(); var changeData: observableModule.PropertyChangeData; dO1.on(observableModule.Observable.propertyChangeEvent, function (data: observableModule.PropertyChangeData) { changeData = data; }); dO1.test1 = 10; validateChangeData(changeData, dO1, "test1", 10); // reset value changeData = undefined; dO1._resetValue(testProperty1); validateChangeData(changeData, dO1, "test1", 0); changeData = undefined; dO1._setValue(testProperty1, 10, dependencyObservableModule.ValueSource.Inherited); validateChangeData(changeData, dO1, "test1", 10); changeData = undefined; dO1._setValue(testProperty1, 20, dependencyObservableModule.ValueSource.Local); validateChangeData(changeData, dO1, "test1", 20); changeData = undefined; dO1._resetValue(testProperty1, dependencyObservableModule.ValueSource.Local); validateChangeData(changeData, dO1, "test1", 10); } export function test_setValue_WillThrow_If_ValueIsInvalid() { var dO = new TestDO(); TKUnit.assertThrows( () => { dO.testWithValidation = -1; }, "Set invalid value should throw", "Invalid value -1 for property testWithValidation"); } export function test_setValue_WillNotThrow_If_ValueIsValid() { var dO = new TestDO(); // This should not throw. dO.testWithValidation = 1; }