Files
NativeScript/tests/app/ui/bindable-tests.ts
Hristo Hristov a64bba62aa whiteSpace default value change to undefined (#3782)
TKUnit default message change to empty string
isSet method is now instance method of Property classes
fix detaching from parent bindingContext - were using oldParent.parent instead of parent
editable-text-base.android - onTextChanged implementation commented. Does nothing.
frame - onCreateView wrapped in try/catch and shows label with exception message if any
text-base.android - should support reset of nativeView. TransformationMethod won’t be set if TextField is secure
Change some types to their string couterparts
TextField.android won’t support multilines anymore in order to work as iOS
In android when page is removed from native backstack we won’t call tearDownUI again a second time
2017-03-14 10:26:45 +02:00

1332 lines
47 KiB
TypeScript

import { Observable, fromObject, fromObjectRecursive } from "tns-core-modules/data/observable";
import { ViewBase } from "tns-core-modules/ui/core/view-base";
import { BindingOptions } from "tns-core-modules/ui/core/bindable";
import * as TKUnit from "../TKUnit";
import * as types from "tns-core-modules/utils/types";
import * as helper from "../ui/helper";
import * as utils from "tns-core-modules/utils/utils";
import * as bindingBuilder from "tns-core-modules/ui/builder/binding-builder";
import * as fs from "tns-core-modules/file-system";
import * as appModule from "tns-core-modules/application";
import * as trace from "tns-core-modules/trace";
import { View } from "tns-core-modules/ui/core/view";
import { Button } from "tns-core-modules/ui/button";
import { Page } from "tns-core-modules/ui/page";
import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout";
import { Label } from "tns-core-modules/ui/label";
import { TextField } from "tns-core-modules/ui/text-field";
// <snippet module="ui/core/bindable" title="bindable">
// For information and examples how to use bindings please refer to special [**Data binding**](../../../../bindings.md) topic.
// </snippet>
// <snippet module="ui/core/weak-event-listener" title="weak-event-listener">
// For information and example how to use weak event listeners please refer to special [**Events**](../../../../events.md) topic which has a dedicated part about weak event listeners.
// </snippet>
export function test_Bindable_Members() {
const obj = new Label();
TKUnit.assert(types.isDefined(obj.bind), "Bindable.bind not defined");
TKUnit.assert(types.isDefined(obj.unbind), "Bindable.unbind not defined");
};
export function test_Bindable_Bind_ToTarget_OneWay() {
const model = new Observable();
model.set("name", "John");
const options: BindingOptions = {
sourceProperty: "name",
targetProperty: "text"
};
const obj = new Label();
obj.bind(options, model);
TKUnit.assert(obj.get("text") === "John", "Expected result after binding is [test value] === 'John'");
model.set("name", "Changed");
TKUnit.assert(obj.get("text") === "Changed", "Expected result after binding is [test value] === 'Changed'");
};
export function test_Bindable_Bind_ToTarget_TwoWay() {
const model = new Observable();
model.set("name", "John");
const options: BindingOptions = {
sourceProperty: "name",
targetProperty: "text",
twoWay: true
};
const obj = new Label();
obj.bind(options, model);
obj.set("text", "Changed");
TKUnit.assertEqual(model.get("name"), "Changed", "Two-way binding not updating the source when target is changed.");
model.set("name", "John");
TKUnit.assertEqual(obj.get("text"), "John", "Two-way binding not updating the target when source is changed.");
};
export function test_Bindable_Bind_ToBindingContext_OneWay() {
const model = new Observable();
model.set("name", "John");
const options: BindingOptions = {
sourceProperty: "name",
targetProperty: "text"
};
const obj = new Label();
obj.bind(options);
obj.set("text", "local");
obj.bindingContext = model;
TKUnit.assert(obj.get("text") === "John", "Binding to a context does not update the target property.");
};
export function test_Bindable_Bind_ToBindingContext_TwoWay() {
const model = new Observable();
model.set("name", "John");
const options: BindingOptions = {
sourceProperty: "name",
targetProperty: "text",
twoWay: true
};
const obj = new Label();
obj.bind(options);
obj.set("text", "local");
obj.bindingContext = model;
TKUnit.assertEqual(obj.get("text"), "John", "Binding to a context does not update the target property.");
obj.set("text", "local");
TKUnit.assertEqual(model.get("name"), "local", "Two-way binding to a context does not update the source property.");
};
export function test_Bindable_Unbind() {
const model = new Observable();
const options: BindingOptions = {
sourceProperty: "name",
targetProperty: "text"
};
const obj = new Label();
obj.bind(options, model);
model.set("name", "John");
TKUnit.assert(obj.get("text") === "John", "Binding does not updates target property.");
obj.unbind("text");
model.set("name", "Changed");
TKUnit.assert(obj.get("text") === "John", "Unbind does not remove binding.");
};
export function test_bind_NoSource_WillUse_BindingContext() {
const model = new Observable();
model.set("testProperty", "testValue");
const test = function (views: Array<View>) {
views[0].bindingContext = model;
views[1].bind({
sourceProperty: "testProperty",
targetProperty: "text"
});
const button = <Button>views[1];
TKUnit.assertEqual(button.text, model.get("testProperty"), "Bind method not working when no source is passed but a valid bindingContext is present.");
};
helper.do_PageTest_WithButton(test);
};
export function test_bindingContext_ValueSource_IsInherited() {
const test = function (views: Array<View>) {
const context = {};
views[0].bindingContext = context;
TKUnit.assert(views[1].bindingContext === context, "bindingContext not inherited.");
};
helper.do_PageTest_WithButton(test);
};
export function test_bindingContext_Change_IsReflected_Properly() {
const model = new Observable();
model.set("testProperty", "testValue");
const test = function (views: Array<View>) {
views[1].bind({
sourceProperty: "testProperty",
targetProperty: "text"
});
const button = <Button>views[1];
TKUnit.assert(button.text === "", "Bind should do nothing when no source and binding context are available.");
views[0].bindingContext = model;
TKUnit.assert(button.text === "testValue", "Binding not updated properly when a valid bindingContext is provided.");
views[0].bindingContext = undefined;
model.set("testProperty", "updatedValue");
TKUnit.assertEqual(button.text, "", "Binding not properly detached when bindingContext is cleared.");
};
helper.do_PageTest_WithButton(test);
};
export function test_WhenBindingIsSetToAnElement_AndElementIsRemoved_ShouldBeCollectedByGC(done) {
let testFinished = false;
let page = helper.getCurrentPage();
let stack = new StackLayout();
let expectedValue = "testValue";
let sourcePropertyName = "testProperty";
let targetPropertyName = "text";
stack.on(View.loadedEvent, () => {
const model = new Observable();
model.set(sourcePropertyName, expectedValue);
function createButton(bindContext) {
let button = new Button();
button.bind({
sourceProperty: sourcePropertyName,
targetProperty: targetPropertyName
}, bindContext);
return new WeakRef(button);
}
const weakRef = createButton(model);
try {
stack.addChild(weakRef.get());
TKUnit.assertEqual(weakRef.get().text, expectedValue, "Binding is not working properly!");
stack.removeChild(weakRef.get());
TKUnit.waitUntilReady(() => { return !weakRef.get().isLoaded; });
utils.GC();
TKUnit.assert(!weakRef.get(), "UIElement is still alive!");
testFinished = true;
}
catch (e) {
done(e);
}
});
page.content = stack;
TKUnit.waitUntilReady(() => { return testFinished; });
done(null);
}
export function test_OneBindableToBindMoreThanOneProperty_ToSameSource() {
const model = new Observable();
const firstPropertyOptions: BindingOptions = {
sourceProperty: "name",
targetProperty: "text"
};
const secondPropertyOptions: BindingOptions = {
sourceProperty: "sourceProperty",
targetProperty: "targetProperty"
};
const obj = new Label();
obj.bind(firstPropertyOptions, model);
obj.bind(secondPropertyOptions, model);
model.set("name", "John");
model.set("sourceProperty", "testValue");
TKUnit.assertEqual(obj.get("text"), "John", "Binding does not updates target property.");
TKUnit.assertEqual(obj.get("targetProperty"), "testValue", "Binding does not updates target property1.");
}
export function test_MoreThanOneBindables_BindToASameSourceAndProperty() {
const model = new Observable();
const bindingOptions: BindingOptions = {
sourceProperty: "sourceProperty",
targetProperty: "targetProperty"
};
const obj1 = new Label();
obj1.bind(bindingOptions, model);
const obj2 = new Label();
obj2.bind(bindingOptions, model);
model.set("sourceProperty", "testValue");
TKUnit.assertEqual(obj1.get("targetProperty"), "testValue", "Binding does not updates target property for first object.");
TKUnit.assertEqual(obj2.get("targetProperty"), "testValue", "Binding does not updates target property for second object.");
};
class TestClass extends ViewBase {
private _value: string;
get value(): string {
return this._value;
}
set value(v: string) {
if (v === "invalid") {
throw new Error("Trying to set invalid value.");
}
this._value = v;
}
};
export function test_WhenBindingSetsInvalidValue_NoExceptionIsThrown() {
const model = new Observable();
const options: BindingOptions = {
sourceProperty: "value",
targetProperty: "value"
};
const obj = new TestClass();
obj.bind(options, model);
model.set("value", "valid");
TKUnit.assert(obj.get("value") === "valid");
// Try set invalid value - no exception should be thrown and the value should remain the same.
model.set("value", "invalid");
TKUnit.assert(obj.get("value") === "valid");
}
export function test_binding_bindingContext_setRootContextFirst() {
const test = function (views: Array<View>) {
const rootContext = new Observable();
rootContext.set("selectedItem", "item 1");
views[0].bindingContext = rootContext;
const stack = <StackLayout>views[1];
const options: BindingOptions = {
sourceProperty: "selectedItem",
targetProperty: "bindingContext"
};
stack.bind(options);
TKUnit.assertEqual(stack.bindingContext, "item 1", "Initial binding value");
TKUnit.assertEqual(views[2].bindingContext, "item 1", "Initial binding value");
rootContext.set("selectedItem", "item 2");
TKUnit.assertEqual(stack.bindingContext, "item 2", "Changed binding value");
TKUnit.assertEqual(views[2].bindingContext, "item 2", "Changed binding value");
};
helper.do_PageTest_WithStackLayout_AndButton(test);
};
export function test_binding_bindingContext_setBindingFirst() {
const test = function (views: Array<View>) {
const rootContext = new Observable();
rootContext.set("selectedItem", "item 1");
const stack = <StackLayout>views[1];
const options: BindingOptions = {
twoWay: true,
sourceProperty: "selectedItem",
targetProperty: "bindingContext"
};
stack.bind(options);
views[0].bindingContext = rootContext;
TKUnit.assertEqual(stack.bindingContext, "item 1", "Initial binding value");
TKUnit.assertEqual(views[2].bindingContext, "item 1", "Initial binding value");
rootContext.set("selectedItem", "item 2");
TKUnit.assertEqual(stack.bindingContext, "item 2", "Changed binding value");
TKUnit.assertEqual(views[2].bindingContext, "item 2", "Changed binding value");
};
helper.do_PageTest_WithStackLayout_AndButton(test);
};
export function test_Bindable_BindingContext_Number_DoesNotThrow() {
const obj = new Label();
obj.bindingContext = 42;
};
export function test_Bindable_BindingContext_Boolean_DoesNotThrow() {
const obj = new Label();
obj.bindingContext = true;
};
export function test_Bindable_BindingContext_String_DoesNotThrow() {
const options: BindingOptions = {
sourceProperty: "length",
targetProperty: "text"
};
const obj = new Label();
obj.bind(options);
obj.set("text", "local");
obj.bindingContext = "string";
TKUnit.assert(obj.get("text") === 6, "Expected: 6; Actual: " + obj.get("text"));
};
export function test_getBindableOptionsFromStringFullFormat() {
const bindingExpression = "bindProperty, bindProperty * 2, false";
const bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression);
TKUnit.assert(bindOptions.sourceProperty === "bindProperty", "Expected: bindProperty, Actual: " + bindOptions.sourceProperty);
TKUnit.assert(bindOptions.targetProperty === "targetBindProperty", "Expected: targetBindProperty, Actual: " + bindOptions.targetProperty);
TKUnit.assert(bindOptions.expression === "bindProperty * 2", "Expected: bindProperty * 2, Actual:" + bindOptions.expression);
TKUnit.assert(bindOptions.twoWay === false, "Expected: false, Actual: " + bindOptions.twoWay);
};
export function test_getBindableOptionsFromStringShortFormatExpression() {
const bindingExpression = "bindProperty * 2";
const bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression);
TKUnit.assert(bindOptions.sourceProperty === "$value", "Expected: bindProperty, Actual: " + bindOptions.sourceProperty);
TKUnit.assert(bindOptions.targetProperty === "targetBindProperty", "Expected: targetBindProperty, Actual: " + bindOptions.targetProperty);
TKUnit.assert(bindOptions.expression === "bindProperty * 2", "Expected: bindProperty * 2, Actual: " + bindOptions.expression);
TKUnit.assert(bindOptions.twoWay === true, "Expected: true, Actual: " + bindOptions.twoWay);
};
export function test_getBindableOptionsFromStringShortFormatProperty() {
const bindingExpression = "bindProperty";
const bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression);
TKUnit.assert(bindOptions.sourceProperty === "bindProperty", "Expected: bindProperty, Actual: " + bindOptions.sourceProperty);
TKUnit.assert(bindOptions.targetProperty === "targetBindProperty", "Expected: targetBindProperty, Actual: " + bindOptions.targetProperty);
TKUnit.assert(bindOptions.expression === undefined, "Expected: null, Actual: " + bindOptions.expression);
TKUnit.assert(bindOptions.twoWay === true, "Expected: true, Actual: " + bindOptions.twoWay);
};
export function test_getBindableOptionsFromStringTwoParamsFormat() {
const bindingExpression = "bindProperty, bindProperty * 2";
const bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression);
TKUnit.assert(bindOptions.sourceProperty === "bindProperty", "Expected: bindProperty, Actual: " + bindOptions.sourceProperty);
TKUnit.assert(bindOptions.targetProperty === "targetBindProperty", "Expected: targetBindProperty, Actual: " + bindOptions.targetProperty);
TKUnit.assert(bindOptions.expression === "bindProperty * 2", "Expected: bindProperty * 2, Actual:" + bindOptions.expression);
TKUnit.assert(bindOptions.twoWay === true, "Expected: true, Actual: " + bindOptions.twoWay);
};
export function test_getBindableOptionsFromStringFullNamedFormat() {
const bindingExpression = "bindProperty, expression = bindProperty * 2, twoWay = false";
const bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression);
TKUnit.assert(bindOptions.sourceProperty === "bindProperty", "Expected: bindProperty, Actual: " + bindOptions.sourceProperty);
TKUnit.assert(bindOptions.targetProperty === "targetBindProperty", "Expected: targetBindProperty, Actual: " + bindOptions.targetProperty);
TKUnit.assert(bindOptions.expression === "bindProperty * 2", "Expected: bindProperty * 2, Actual:" + bindOptions.expression);
TKUnit.assert(bindOptions.twoWay === false, "Expected: false, Actual: " + bindOptions.twoWay);
};
export function test_getBindableOptionsFromStringShortNamedFormatExpression() {
const bindingExpression = "sourceProperty = bindProperty, expression = bindProperty * 2";
const bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression);
TKUnit.assert(bindOptions.sourceProperty === "bindProperty", "Expected: bindProperty, Actual: " + bindOptions.sourceProperty);
TKUnit.assert(bindOptions.targetProperty === "targetBindProperty", "Expected: targetBindProperty, Actual: " + bindOptions.targetProperty);
TKUnit.assert(bindOptions.expression === "bindProperty * 2", "Expected: bindProperty * 2, Actual: " + bindOptions.expression);
TKUnit.assert(bindOptions.twoWay === true, "Expected: true, Actual: " + bindOptions.twoWay);
};
export function test_getBindableOptionsFromStringShortNamedFormatProperty() {
const bindingExpression = "sourceProperty = bindProperty";
const bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression);
TKUnit.assert(bindOptions.sourceProperty === "bindProperty", "Expected: bindProperty, Actual: " + bindOptions.sourceProperty);
TKUnit.assert(bindOptions.targetProperty === "targetBindProperty", "Expected: targetBindProperty, Actual: " + bindOptions.targetProperty);
TKUnit.assert(bindOptions.expression === undefined, "Expected: null, Actual: " + bindOptions.expression);
TKUnit.assert(bindOptions.twoWay === true, "Expected: true, Actual: " + bindOptions.twoWay);
};
export function test_getBindableOptionsFromStringTwoParamsNamedFormat() {
const bindingExpression = "bindProperty, expression = bindProperty * 2";
const bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression);
TKUnit.assert(bindOptions.sourceProperty === "bindProperty", "Expected: bindProperty, Actual: " + bindOptions.sourceProperty);
TKUnit.assert(bindOptions.targetProperty === "targetBindProperty", "Expected: targetBindProperty, Actual: " + bindOptions.targetProperty);
TKUnit.assert(bindOptions.expression === "bindProperty * 2", "Expected: bindProperty * 2, Actual:" + bindOptions.expression);
TKUnit.assert(bindOptions.twoWay === true, "Expected: true, Actual: " + bindOptions.twoWay);
};
export function test_getBindingOptionsFromStringWithFunctionWitnMoreParams() {
const bindingExpression = "bindProperty, converter(bindProperty, param1)";
const bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression);
TKUnit.assertEqual(bindOptions.sourceProperty, "bindProperty");
TKUnit.assertEqual(bindOptions.targetProperty, "targetBindProperty");
TKUnit.assertEqual(bindOptions.expression, "converter(bindProperty, param1)");
TKUnit.assertEqual(bindOptions.twoWay, true);
};
export function test_getBindingOptionsFromStringWithFunctionArrayParams() {
const bindingExpression = "bindProperty, converter(bindProperty, [param1, param2])";
const bindOptions = bindingBuilder.getBindingOptions("targetBindProperty", bindingExpression);
TKUnit.assertEqual(bindOptions.sourceProperty, "bindProperty");
TKUnit.assertEqual(bindOptions.targetProperty, "targetBindProperty");
TKUnit.assertEqual(bindOptions.expression, "converter(bindProperty, [param1, param2])");
TKUnit.assertEqual(bindOptions.twoWay, true);
};
export function test_bindingToNestedPropertyWithValueSyntax() {
const bindingSource = new Observable();
bindingSource.set("testProperty", "testValue");
const testElement = new Label();
testElement.bind({
sourceProperty: "$value.testProperty",
targetProperty: "targetPropertyName"
}, bindingSource);
TKUnit.assertEqual(testElement.get("targetPropertyName"), "testValue");
TKUnit.assertTrue(bindingSource['$value'] === undefined, "We should not add $value to bindingSource.");
};
export function test_TwoElementsBindingToSameBindingContext() {
const testFunc = function (page: Page) {
const upperStackLabel = <Label>(page.getViewById("upperStackLabel"));
const label1 = <Label>(page.getViewById("label1"));
const label2 = <Label>(page.getViewById("label2"));
TKUnit.assertEqual(upperStackLabel.text, label1.text, "label1");
TKUnit.assertEqual(upperStackLabel.text, label2.text, "label2");
};
const moduleName = __dirname.substr(fs.knownFolders.currentApp().path.length);
helper.navigateToModuleAndRunTest(("." + moduleName + "/bindingContext_testPage"), null, testFunc);
};
export function test_BindingContext_NavigatingForwardAndBack() {
const expectedValue = "Tralala";
const moduleName = __dirname.substr(fs.knownFolders.currentApp().path.length);
const testFunc = function (page: Page) {
const innerTestFunc = function (childPage: Page) {
const testTextField: TextField = <TextField>(childPage.getViewById("testTextField"));
testTextField.text = expectedValue;
};
helper.navigateToModuleAndRunTest(("." + moduleName + "/bindingContext_testPage2"), page.bindingContext, innerTestFunc);
const testLabel: Label = <Label>(page.getViewById("testLabel"));
TKUnit.assertEqual(testLabel.text, expectedValue);
};
helper.navigateToModuleAndRunTest(("." + moduleName + "/bindingContext_testPage1"), null, testFunc);
};
export function test_BindingToSource_FailsAfterBindingContextChange() {
const createLabel = function () {
const label = new Label();
return label;
};
const labelViewModel = new Observable();
const expectedValue = "testValue";
labelViewModel.set("testProperty", expectedValue);
const testFunc = function (views: Array<View>) {
const testLabel = <Label>(views[0]);
testLabel.bind({ sourceProperty: "testProperty", targetProperty: "text" }, labelViewModel);
const page = <Page>(views[1]);
page.bindingContext = new Observable;
TKUnit.assertEqual(testLabel.text, expectedValue);
};
helper.buildUIAndRunTest(createLabel(), testFunc);
};
export function test_BindingToParentView_ShouldNotLeaveGarbageInViewModel() {
const createStack = function () {
const stack = new StackLayout();
const label = new Label();
stack.addChild(label);
return stack;
};
const stackViewModel = new Observable();
const expectedValue = "testValue";
stackViewModel.set("testProperty", expectedValue);
const testFunc = function (views: Array<View>) {
let testStack = <StackLayout>(views[0]);
testStack.bindingContext = stackViewModel;
let testLabel = <Label>(testStack.getChildAt(0));
testLabel.bind({ sourceProperty: "$parent.testProperty", targetProperty: "text", expression: "$parent.testProperty" });
TKUnit.assertEqual(testLabel.text, expectedValue);
TKUnit.assertTrue(stackViewModel['$parent'] === undefined, "stackViewModel['$parent'] should be removed from parent binding context.");
TKUnit.assertTrue(testLabel.bindingContext['$parent'] === undefined, "testLabel.bindingContext['$parent'] should be removed from parent binding context.");
};
helper.buildUIAndRunTest(createStack(), testFunc);
};
export function test_BindingToParentsView_ShouldNotLeaveGarbageInViewModel() {
const createStack = function () {
const stack = new StackLayout();
const label = new Label();
stack.addChild(label);
return stack;
};
const stackViewModel = new Observable();
const expectedValue = "testValue";
stackViewModel.set("testProperty", expectedValue);
const testFunc = function (views: Array<View>) {
let testStack = <StackLayout>(views[0]);
testStack.bindingContext = stackViewModel;
let testLabel = <Label>(testStack.getChildAt(0));
testLabel.bind({ sourceProperty: "$parents['StackLayout'].testProperty", targetProperty: "text", expression: "$parents['StackLayout'].testProperty" });
TKUnit.assertEqual(testLabel.text, expectedValue);
TKUnit.assertTrue(stackViewModel['$parent'] === undefined, "stackViewModel['$parent'] should be removed from parent binding context.");
TKUnit.assertTrue(testLabel.bindingContext['$parents'] === undefined, "testLabel.bindingContext['$parents'] should be removed from parent binding context.");
};
helper.buildUIAndRunTest(createStack(), testFunc);
};
export function test_BindingToDictionaryAtAppLevel() {
const createLabel = function () {
const label = new Label();
return label;
};
const pageViewModel = new Observable();
const testPropertyName = "testValue";
const expectedValue = "expectedValue";
pageViewModel.set("testProperty", testPropertyName);
const dict = {};
dict[testPropertyName] = expectedValue;
appModule.resources["dict"] = dict;
const testFunc = function (views: Array<View>) {
const testLabel = <Label>(views[0]);
testLabel.bind({ sourceProperty: "testProperty", targetProperty: "text", expression: "dict[testProperty]" });
const page = <Page>(views[1]);
page.bindingContext = pageViewModel;
TKUnit.assertEqual(testLabel.text, expectedValue);
TKUnit.assertTrue(testLabel.bindingContext["dict"] === undefined, "BindingContext should not contain properties from application resources.");
};
helper.buildUIAndRunTest(createLabel(), testFunc);
}
export function test_BindingConverterCalledEvenWithNullValue() {
const createLabel = function () {
const label = new Label();
return label;
};
const pageViewModel = new Observable();
const testPropertyValue = null;
const expectedValue = "collapsed";
pageViewModel.set("testProperty", testPropertyValue);
appModule.resources["converter"] = function (value) {
if (value) {
return "visible";
} else {
return "collapsed";
}
};
const testFunc = function (views: Array<View>) {
const testLabel = <Label>(views[0]);
testLabel.bind({ sourceProperty: "testProperty", targetProperty: "text", expression: "testProperty | converter" });
const page = <Page>(views[1]);
page.bindingContext = pageViewModel;
TKUnit.assertEqual(testLabel.text, expectedValue);
};
helper.buildUIAndRunTest(createLabel(), testFunc);
}
export function test_UpdatingNestedPropertyViaBinding() {
const expectedValue1 = "Alabala";
const expectedValue2 = "Tralala";
const viewModel = new Observable();
const parentViewModel = new Observable();
viewModel.set("parentView", parentViewModel);
parentViewModel.set("name", expectedValue1);
const testElement = new Label();
testElement.bind({
sourceProperty: "parentView.name",
targetProperty: "text",
twoWay: true
}, viewModel);
const testElement2 = new Label();
testElement2.bind({
sourceProperty: "parentView.name",
targetProperty: "text",
twoWay: true
}, viewModel);
TKUnit.assertEqual(testElement.get("text"), expectedValue1);
testElement.set("text", expectedValue2);
TKUnit.assertEqual(parentViewModel.get("name"), expectedValue2);
TKUnit.assertEqual(testElement2.get("text"), expectedValue2);
}
class Person extends Observable {
private _firstName: string;
private _lastName: string;
public get FirstName(): string {
return this._firstName;
}
public set FirstName(value: string) {
if (this._firstName !== value) {
this._firstName = value;
this.notifyPropertyChange("FirstName", value);
}
}
public get LastName(): string {
return this._lastName;
}
public set LastName(value: string) {
if (this._lastName !== value) {
this._lastName = value;
this.notifyPropertyChange("LastName", value);
}
}
}
class Activity extends Observable {
private _text: string;
private _owner: Person;
public get Text(): string {
return this._text;
}
public set Text(value: string) {
if (this._text !== value) {
this._text = value;
this.notifyPropertyChange("Text", value);
}
}
public get Owner(): Person {
return this._owner;
}
public set Owner(value: Person) {
if (this._owner !== value) {
this._owner = value;
this.notifyPropertyChange("Owner", value);
}
}
constructor(text: string, firstName: string, lastName: string) {
super();
this._text = text;
const owner = new Person();
owner.FirstName = firstName;
owner.LastName = lastName;
this.Owner = owner;
}
}
export function test_NestedPropertiesBinding() {
const expectedValue = "Default Text";
const viewModel = new Observable();
viewModel.set("activity", new Activity(expectedValue, "Default First Name", "Default Last Name"));
const target1 = new Label();
target1.bind({
sourceProperty: "activity.Text",
targetProperty: "targetProperty",
twoWay: true
}, viewModel);
TKUnit.assertEqual(target1.get("targetProperty"), expectedValue);
const newExpectedValue = "Alabala";
const act = new Activity(newExpectedValue, "Default First Name", "Default Last Name");
viewModel.set("activity", act);
TKUnit.assertEqual(target1.get("targetProperty"), newExpectedValue);
}
export function test_WrongNestedPropertiesBinding() {
const expectedValue = "Default Text";
const viewModel = new Observable();
viewModel.set("activity", new Activity(expectedValue, "Default First Name", "Default Last Name"));
let errorMessage;
let traceWriter = {
write: function (message, category, type?) {
errorMessage = message;
}
};
trace.addWriter(traceWriter);
const target1 = new Label();
target1.bind({
sourceProperty: "activity.",
targetProperty: "targetProperty",
twoWay: true
}, viewModel);
TKUnit.assertNotEqual(errorMessage, undefined);
trace.removeWriter(traceWriter);
}
export function test_NestedPropertiesBindingTwoTargets() {
const expectedText = "Default Text";
const expectedFirstName = "Default First Name";
const expectedLastName = "Default Last Name";
const viewModel = new Observable();
viewModel.set("activity", new Activity(expectedText, expectedFirstName, expectedLastName));
const target1 = new Label();
target1.bind({
sourceProperty: "activity.Text",
targetProperty: "targetProperty",
twoWay: true
}, viewModel);
const target2 = new Label();
target2.bind({
sourceProperty: "activity.Owner.FirstName",
targetProperty: "targetProp",
twoWay: true
}, viewModel);
TKUnit.assertEqual(target1.get("targetProperty"), expectedText);
TKUnit.assertEqual(target2.get("targetProp"), expectedFirstName);
const newExpectedText = "Alabala";
const newExpectedFirstName = "First Tralala";
const newExpectedLastName = "Last Tralala";
const act = new Activity(newExpectedText, newExpectedFirstName, newExpectedLastName);
viewModel.set("activity", act);
TKUnit.assertEqual(target1.get("targetProperty"), newExpectedText);
TKUnit.assertEqual(target2.get("targetProp"), newExpectedFirstName);
}
export function test_NestedPropertiesBindingTwoTargetsAndSecondChange() {
const expectedText = "Default Text";
const expectedFirstName = "Default First Name";
const expectedLastName = "Default Last Name";
const viewModel = new Observable();
viewModel.set("activity", new Activity(expectedText, expectedFirstName, expectedLastName));
const target1 = new Label();
target1.bind({
sourceProperty: "activity.Text",
targetProperty: "targetProperty",
twoWay: true
}, viewModel);
const target2 = new Label();
target2.bind({
sourceProperty: "activity.Owner.FirstName",
targetProperty: "targetProp",
twoWay: true
}, viewModel);
TKUnit.assertEqual(target1.get("targetProperty"), expectedText);
TKUnit.assertEqual(target2.get("targetProp"), expectedFirstName);
const newExpectedText = "Alabala";
const newExpectedFirstName = "First Tralala";
const newExpectedLastName = "Last Tralala";
const act = new Activity(newExpectedText, newExpectedFirstName, newExpectedLastName);
viewModel.set("activity", act);
TKUnit.assertEqual(target1.get("targetProperty"), newExpectedText);
TKUnit.assertEqual(target2.get("targetProp"), newExpectedFirstName);
const secondExpectedText = "Second expected text";
const secondExpectedFirstName = "Second expected first name";
const secondExpectedLastName = "Second expected last name";
const act1 = new Activity(secondExpectedText, secondExpectedFirstName, secondExpectedLastName);
viewModel.set("activity", act1);
TKUnit.assertEqual(target1.get("targetProperty"), secondExpectedText);
TKUnit.assertEqual(target2.get("targetProp"), secondExpectedFirstName);
}
export function test_NestedPropertiesBindingTwoTargetsAndRegularChange() {
const expectedText = "Default Text";
const expectedFirstName = "Default First Name";
const expectedLastName = "Default Last Name";
const viewModel = new Observable();
viewModel.set("activity", new Activity(expectedText, expectedFirstName, expectedLastName));
const target1 = new Label();
target1.bind({
sourceProperty: "activity.Text",
targetProperty: "targetProperty",
twoWay: true
}, viewModel);
const target2 = new Label();
target2.bind({
sourceProperty: "activity.Owner.FirstName",
targetProperty: "targetProp",
twoWay: true
}, viewModel);
TKUnit.assertEqual(target1.get("targetProperty"), expectedText);
TKUnit.assertEqual(target2.get("targetProp"), expectedFirstName);
const newExpectedText = "Alabala";
const newExpectedFirstName = "First Tralala";
const newExpectedLastName = "Last Tralala";
const act = new Activity(newExpectedText, newExpectedFirstName, newExpectedLastName);
viewModel.set("activity", act);
TKUnit.assertEqual(target1.get("targetProperty"), newExpectedText);
TKUnit.assertEqual(target2.get("targetProp"), newExpectedFirstName);
const newAct = viewModel.get("activity");
const secondExpectedText = "Second expected text";
newAct.Text = secondExpectedText;
const secondExpectedFirstName = "Second expected First Name";
newAct.Owner.FirstName = secondExpectedFirstName;
TKUnit.assertEqual(target1.get("targetProperty"), secondExpectedText);
TKUnit.assertEqual(target2.get("targetProp"), secondExpectedFirstName);
}
export function test_NestedPropertiesBindingTwoTargetsAndReplacingSomeNestedObject() {
const expectedText = "Default Text";
const expectedFirstName = "Default First Name";
const expectedLastName = "Default Last Name";
const viewModel = new Observable();
viewModel.set("activity", new Activity(expectedText, expectedFirstName, expectedLastName));
const target1 = new Label();
target1.bind({
sourceProperty: "activity.Text",
targetProperty: "targetProperty",
twoWay: true
}, viewModel);
const target2 = new Label();
target2.bind({
sourceProperty: "activity.Owner.FirstName",
targetProperty: "targetProp",
twoWay: true
}, viewModel);
TKUnit.assertEqual(target1.get("targetProperty"), expectedText);
TKUnit.assertEqual(target2.get("targetProp"), expectedFirstName);
const newExpectedText = "Alabala";
const newExpectedFirstName = "First Tralala";
const newExpectedLastName = "Last Tralala";
const act = new Activity(newExpectedText, newExpectedFirstName, newExpectedLastName);
viewModel.set("activity", act);
TKUnit.assertEqual(target1.get("targetProperty"), newExpectedText);
TKUnit.assertEqual(target2.get("targetProp"), newExpectedFirstName);
const secondExpectedFirstName = "Second expected first name";
const newPerson = new Person();
newPerson.FirstName = secondExpectedFirstName;
newPerson.LastName = "Last Name";
const act1 = viewModel.get("activity");
(<Activity>act1).Owner = newPerson;
TKUnit.assertEqual(target2.get("targetProp"), secondExpectedFirstName);
}
export function test_NullSourcePropertyShouldNotCrash() {
const expectedValue = "Expected Value";
const target = new Label();
const convFunc = function (value) {
return value + "Converted";
};
const model = new Observable();
model.set("field", expectedValue);
model.set("convFunc", convFunc);
target.bind({
sourceProperty: null,
targetProperty: "targetProp",
expression: "convFunc(field)"
}, model);
TKUnit.assertEqual(target.get("targetProp"), convFunc(expectedValue));
}
export function test_BindingContextOfAChildElementIsNotOverwrittenBySettingTheBindingContextOfPage() {
let testFinished = false;
let page = helper.getCurrentPage();
let child = new StackLayout();
let childModel = new Observable();
child.bindingContext = childModel;
TKUnit.assertEqual(child.bindingContext, childModel);
child.on(StackLayout.loadedEvent, (args) => {
TKUnit.assertEqual(child.bindingContext, childModel);
page.bindingContext = new Observable();
TKUnit.assertEqual(child.bindingContext, childModel);
child.off(StackLayout.loadedEvent);
testFinished = true;
});
page.content = child;
TKUnit.waitUntilReady(() => testFinished);
}
export function test_BindingHitsGetterTooManyTimes() {
let counter = 0;
class Dummy extends Observable {
private _dummyProperty: string;
public get dummyProperty(): string {
counter++;
return this._dummyProperty;
}
public set dummyProperty(value: string) {
if (this._dummyProperty !== value) {
this._dummyProperty = value;
this.notifyPropertyChange("dummyProperty", value);
}
}
}
const model = new Dummy();
model.dummyProperty = "OPA";
const bindableObj = new Label();
bindableObj.bind({ sourceProperty: "dummyProperty", targetProperty: "dummyTarget" }, model);
TKUnit.assertEqual(counter, 1, "Property getter should be hit only once!");
};
export function test_SupportFunctionsInExpressions() {
const model = fromObject({
"anyColor": "red",
"isVisible": function () {
return this.get("anyColor") === "red";
}
});
const bindableObj = new Label();
bindableObj.bind({
"sourceProperty": "$value",
"targetProperty": "text",
"expression": "isVisible() ? 'visible' : 'collapsed'"
}, model);
model.set("anyColor", "blue");
TKUnit.assertEqual(bindableObj.get("text"), "collapsed", "When anyColor is blue test property should be collapsed.");
model.set("anyColor", "red");
TKUnit.assertEqual(bindableObj.get("text"), "visible", "When anyColor is red test property should be visible.");
}
export function test_$ValueSupportWithinExpression() {
const model = fromObject({
"anyColor": "red",
"isVisible": function () {
return this.get("anyColor") === "red";
}
});
const bindableObj = new Label();
bindableObj.bind({
"sourceProperty": "$value",
"targetProperty": "text",
"expression": "$value.anyColor === 'red' ? 'red' : 'blue'"
}, model);
model.set("anyColor", "blue");
TKUnit.assertEqual(bindableObj.get("text"), "blue", "When anyColor is blue test property should be blue too.");
model.set("anyColor", "red");
TKUnit.assertEqual(bindableObj.get("text"), "red", "When anyColor is red test property should be red too.");
TKUnit.assertTrue(model['$value'] === undefined, "We should not add $value to binding context.");
}
class DummyNestedClass extends Observable {
private _secondsobject: number;
public get secondsobject(): number {
return this._secondsobject;
}
public set secondsobject(value: number) {
if (this._secondsobject !== value) {
this._secondsobject = value;
this.notifyPropertyChange('secondsobject', value);
}
}
}
class DummyClassWithSamePropertyNames extends Observable {
private _seconds: number;
private _secondsobject: DummyNestedClass;
public get seconds(): number {
return this._seconds;
}
public set seconds(value: number) {
if (this._seconds !== value) {
this._seconds = value;
this.notifyPropertyChange('seconds', value);
}
}
public get secondsobject(): DummyNestedClass {
return this._secondsobject;
}
public set secondsobject(value: DummyNestedClass) {
if (this._secondsobject !== value) {
this._secondsobject = value;
this.notifyPropertyChange('secondsobject', value);
}
}
}
class DummyModel extends Observable {
private _item: DummyClassWithSamePropertyNames;
public get item(): DummyClassWithSamePropertyNames {
return this._item;
}
public set item(value: DummyClassWithSamePropertyNames) {
if (this._item !== value) {
this._item = value;
this.notifyPropertyChange("item", value);
}
}
}
export function test_BindingToPropertiesWithSameNames() {
const model = new DummyModel();
model.item = new DummyClassWithSamePropertyNames();
model.item.seconds = 1;
const secondsobject = new DummyNestedClass();
secondsobject.secondsobject = 1;
model.item.secondsobject = secondsobject;
const target1 = new Label();
target1.bind({
sourceProperty: "item.seconds",
targetProperty: "targetProperty",
twoWay: true
}, model);
const target2 = new Label();
target2.bind({
sourceProperty: "item.secondsobject.secondsobject",
targetProperty: "targetProp",
twoWay: true
}, model);
model.item.set("seconds", model.item.seconds + 1);
let newValue = (<any>model).item.secondsobject.secondsobject + 1;
model.item.secondsobject.set("secondsobject", newValue);
TKUnit.assertEqual(target1.get("targetProperty"), model.item.get("seconds"));
TKUnit.assertEqual(target2.get("targetProp"), newValue);
// calling this two times in order to ensure that adding and removing weak event listeners is working fine.
newValue = model.item.secondsobject.secondsobject + 1;
model.item.secondsobject.set("secondsobject", newValue);
TKUnit.assertEqual(target1.get("targetProperty"), model.item.get("seconds"));
TKUnit.assertEqual(target2.get("targetProp"), newValue);
}
export function test_BindingToPropertiesWithSameNamesSecondCase() {
const model = new DummyModel();
model.item = new DummyClassWithSamePropertyNames();
model.item.seconds = 1;
const secondsobject = new DummyNestedClass();
secondsobject.secondsobject = 1;
model.item.secondsobject = secondsobject;
const target1 = new Label();
target1.bind({
sourceProperty: "item.seconds",
targetProperty: "targetProperty",
twoWay: true
}, model);
const target2 = new Label();
target2.bind({
sourceProperty: "item.secondsobject.secondsobject",
targetProperty: "targetProp",
twoWay: true
}, model);
model.item.set("seconds", model.item.seconds + 1);
let newValue = model.item.secondsobject.secondsobject + 1;
model.item.set("secondsobject", { secondsobject: newValue });
TKUnit.assertEqual(target1.get("targetProperty"), model.item.get("seconds"));
TKUnit.assertEqual(target2.get("targetProp"), newValue);
// calling this two times in order to ensure that adding and removing weak event listeners is working fine.
newValue = model.item.secondsobject.secondsobject + 1;
model.item.set("secondsobject", { secondsobject: newValue });
TKUnit.assertEqual(target1.get("targetProperty"), model.item.get("seconds"));
TKUnit.assertEqual(target2.get("targetProp"), newValue);
}
class RelatedPropsClass extends Observable {
private _prop1: boolean;
private _prop2: string;
public get prop1(): boolean {
return this._prop1;
}
public set prop1(value: boolean) {
if (this._prop1 !== value) {
this._prop1 = value;
this.notifyPropertyChange("prop1", value);
}
}
public get prop2(): string {
this.prop1 = !this._prop1;
this.notifyPropertyChange("prop2", this._prop2);
return this._prop2;
}
public set prop2(value: string) {
if (this._prop2 !== value) {
this._prop2 = value;
this.notifyPropertyChange("prop2", value);
}
}
}
export function test_BindingToRelatedProps() {
let model = new RelatedPropsClass();
model.prop1 = false;
model.prop2 = "Alabala";
let target1 = new Label();
target1.bind({ sourceProperty: 'prop1', targetProperty: 'targetProp1' }, model);
let target2 = new Label();
target2.bind({ sourceProperty: 'prop2', targetProperty: 'targetProp2' }, model);
model.prop2 = "Tralala";
TKUnit.assertEqual(target2.get('targetProp2'), "Tralala");
}
export function test_only_Bindable_BindingContext_Null_DoesNotThrow() {
const options: BindingOptions = {
sourceProperty: "a.b",
targetProperty: "text"
};
const obj = new Label();
obj.bind(options);
obj.bindingContext = fromObject({ a: "b" });
obj.bindingContext = null;
}
export function test_Observable_from_nested_json_binds_correctly() {
let expectedValue = "Test";
let model = fromObjectRecursive({
"firstObject": {
"secondObject": {
"dummyProperty": "text"
}
}
});
const obj = new Label();
obj.bind({
sourceProperty: "firstObject.secondObject.dummyProperty",
targetProperty: "text"
}, model);
model.get("firstObject").get("secondObject").set("dummyProperty", expectedValue);
TKUnit.assertEqual(obj.get("text"), expectedValue);
}
export function test_Observable_from_nested_json_binds_correctly_when_upper_object_is_changed() {
let expectedValue = "Test";
let model = fromObjectRecursive({
"firstObject": {
"secondObject": {
"dummyProperty": "text"
}
}
});
const obj = new Label();
obj.bind({
sourceProperty: "firstObject.secondObject.dummyProperty",
targetProperty: "text"
}, model);
model.get("firstObject").set("secondObject", fromObject({ "dummyProperty": expectedValue }));
TKUnit.assertEqual(obj.get("text"), expectedValue);
}
export function test_BindingToBindingContextProperty_ShouldUseNewContext() {
let stackLayout = new StackLayout();
let label = new Label();
stackLayout.addChild(label);
label.bind({
sourceProperty: 'context',
targetProperty: 'bindingContext'
});
label.bind({
sourceProperty: 'text',
targetProperty: 'text'
});
let testBindingContext = fromObjectRecursive({
context: {
text: 'Alabala'
}
});
stackLayout.bindingContext = testBindingContext;
(<any>testBindingContext).context.text = "Tralala";
TKUnit.assertEqual(label.text, "Tralala");
}