///
import {Observable, Subscription} from "rxjs/Rx";
import {ReactiveObject} from "../src/reactive-object";
import {ReactiveCommand} from "../src/reactive-command";
import {PropertyChangedEventArgs} from "../src/events/property-changed-event-args";
import {expect} from "chai";
import {MyObject} from "./models/my-object";
import {MyOtherObject} from "./models/my-other-object";
import {IViewBindingHelper} from "../src/view";
describe("ReactiveObject", () => {
class ViewBindingHelper implements IViewBindingHelper {
public observeProp(obj: any, prop: string, emitCurrentVal: boolean, callback: Function): Function {
obj.changed = (newVal: any) => {
callback(new PropertyChangedEventArgs(obj, prop, newVal));
};
return () => {
obj.changed = null;
}
}
}
describe("constructor()", () => {
it("should create a shallow copy of the given object", () => {
var first = {
prop: "Value",
other: 10,
float: 14.4,
obj: { hello: "world!" },
removed: undefined
};
var obj = new ReactiveObject(first);
expect(obj.get("prop")).to.equal("Value");
expect(obj.get("other")).to.equal(10);
expect(obj.get("float")).to.equal(14.4);
expect(obj.get("obj")).to.equal(first.obj);
});
it("should setup getters and setters for each of the properties in the given object", () => {
var obj: any = new ReactiveObject({
prop: "Value",
other: 10
});
expect(obj.prop).to.equal("Value");
expect(obj.other).to.equal(10);
var messages: string[] = [];
var sub = obj.whenAnyValue("prop").subscribe(v => messages.push(v));
obj.prop = "Other";
expect(messages.length).to.equal(2);
expect(messages[0]).to.equal("Value");
expect(messages[1]).to.equal("Other");
});
it("should setup getters and setters for each of the properties in the given array", () => {
var obj = new ReactiveObject(["prop", "other"]);
expect(obj.get("prop")).to.equal(null);
expect(obj.get("other")).to.equal(null);
var anyObj = obj;
expect(anyObj.prop).to.equal(null);
expect(anyObj.other).to.equal(null);
anyObj.prop = "Value";
expect(obj.get("prop")).to.equal("Value");
expect(anyObj.prop).to.equal("Value");
});
});
describe("#set()", () => {
it("should change the value retrieved by .get()", () => {
var obj: ReactiveObject = new ReactiveObject();
var firstVal = obj.get("prop");
var newVal = "NewValue";
obj.set("prop", newVal);
expect(obj.get("prop")).to.not.equal(firstVal);
expect(obj.get("prop")).to.equal(newVal);
});
it("should emit new PropertyChangedEventArgs after set() is called", (done) => {
var obj: ReactiveObject = new ReactiveObject();
obj.propertyChanged.first().subscribe(e => {
expect(e).to.not.be.null;
expect(e.propertyName).to.equal("message");
expect(e.sender).to.equal(obj);
expect(e.newPropertyValue).to.equal("Hello, World!");
}, err => done(err), () => done());
obj.set("message", "Hello, World!");
});
it("should handle deep properties with multiple reactive objects", () => {
var obj: ReactiveObject = new ReactiveObject();
var prop: ReactiveObject = new ReactiveObject();
var prop2: ReactiveObject = new ReactiveObject();
obj.set("prop", prop);
obj.set("prop.prop2", prop2);
expect(obj.get("prop.prop2")).to.equal(prop2);
});
it("should handle properties specified as lambdas", () => {
var obj = new MyObject();
var prop = new MyOtherObject();
var prop2 = new MyOtherObject();
obj.set(o => o.child, prop);
obj.set(o => o.child.child, prop2);
expect(obj.get(o => o.child.child)).to.equal(prop2);
});
it("should handle non reactive objects", () => {
var obj: ReactiveObject = new ReactiveObject();
var child = {
val: "stuff"
};
var child2 = {
otherValue: "other stuff"
};
obj.set("child", child);
obj.set("child.child2", child2);
expect(obj.get("child.child2")).to.equal(child2);
});
it("should work for falsy values", () => {
var values = [
false,
0,
"",
null,
NaN
];
var obj: ReactiveObject = new ReactiveObject();
values.forEach(v => {
obj.set("prop", v);
if (typeof v !== "number" || !isNaN(v)) {
expect(obj.get("prop")).to.equal(v);
} else {
expect(isNaN(obj.get("prop"))).to.be.true;
}
});
});
});
describe("#get(prop)", () => {
it("should return the value at obj.__data[prop]", () => {
var obj: ReactiveObject = new ReactiveObject();
(obj).__data["prop"] = "value";
var val = obj.get("prop");
expect(val).to.equal("value");
});
it("should return null when the property is undefined", () => {
var obj: ReactiveObject = new ReactiveObject();
var val = obj.get("prop");
expect(val).to.be.null;
});
it("should return null when trying to access property on already null property", () => {
var obj: ReactiveObject = new ReactiveObject();
expect(obj.get("prop.other.does.not.exist")).to.be.null;
});
});
describe(".set()", () => {
it("should call the defined setter on the object if it exists", () => {
var hit = false;
class TestClass extends ReactiveObject {
public get prop() {
return this.get("prop");
}
public set prop(val: string) {
hit = true;
this.set("prop", val);
}
}
var obj = new TestClass();
(ReactiveObject).set(obj, "prop", "Value");
expect(hit).to.be.true;
expect(obj.prop).to.equal("Value");
});
});
describe(".get()", () => {
it("should call the defined getter on the object if it exists", () => {
var hit = false;
class TestClass extends ReactiveObject {
public get prop() {
hit = true;
return "customValue";
}
}
var obj = new TestClass();
var ret = (ReactiveObject).get(obj, "prop");
expect(hit).to.be.true;
expect(ret).to.equal("customValue");
});
});
describe("#emitPropertyChanged()", () => {
it("should emit new PropertyChangedEventArgs when called", (done) => {
class MyReactiveObj extends ReactiveObject {
public triggerChangeEvent(): void {
this.emitPropertyChanged("prop", "newVal");
}
}
var obj: MyReactiveObj = new MyReactiveObj();
obj.propertyChanged.first().subscribe(e => {
expect(e).to.not.be.null;
expect(e.propertyName).to.equal("prop");
expect(e.sender).to.equal(obj);
expect(e.newPropertyValue).to.equal("newVal");
}, err => done(err), () => done());
obj.triggerChangeEvent();
});
it("should use value stored in object when new value is not provided", (done) => {
class MyReactiveObj extends ReactiveObject {
public triggerChangeEvent(): void {
this.emitPropertyChanged("prop");
}
}
var obj: MyReactiveObj = new MyReactiveObj();
obj.set("prop", "value");
obj.propertyChanged.subscribe(e => {
expect(e).to.not.be.null;
expect(e.propertyName).to.equal("prop");
expect(e.sender).to.equal(obj);
expect(e.newPropertyValue).to.equal("value");
done();
}, err => done(err));
obj.triggerChangeEvent();
});
});
describe("#whenAny(lambda)", () => {
it("should return an observable", () => {
var obj: MyObject = new MyObject();
var observable = obj.whenAny(o => o.otherProp);
expect(observable).to.be.instanceOf(Observable);
});
it("should observe property events for the given property", (done) => {
var obj: MyObject = new MyObject();
var observable = obj.whenAny(o => o.otherProp).skip(1).take(1).subscribe(e => {
expect(e).to.not.be.null;
expect(e.propertyName).to.equal("otherProp");
expect(e.sender).to.equal(obj);
expect(e.newPropertyValue).to.equal("value");
}, err => done(err), () => done());
obj.otherProp = "value";
});
it("should observe property events for child reactive objects", (done) => {
var obj: MyObject = new MyObject();
obj.child = new MyOtherObject();
var observable = obj.whenAny(o => o.child.prop).skip(1).take(1).subscribe(e => {
expect(e).to.not.be.null;
expect(e.propertyName).to.equal("prop");
expect(e.sender).to.equal(obj.child);
expect(e.newPropertyValue).to.equal("value");
}, err => done(err), () => done());
obj.child.prop = "value";
});
it("should observe property events for the entire child tree", (done) => {
var obj: MyObject = new MyObject();
var child: MyOtherObject = new MyOtherObject();
var child2: MyOtherObject = new MyOtherObject();
var child3: MyOtherObject = new MyOtherObject();
var child4: MyOtherObject = new MyOtherObject();
obj.child = child;
child.child = child2;
child2.child = child3;
child3.child = child4;
obj.whenAny(o => o.child.child.child.child.prop).skip(1).first().subscribe(e => {
expect(e).to.not.be.null;
expect(e.propertyName).to.equal("prop");
expect(e.sender).to.equal(child4);
expect(e.newPropertyValue).to.equal("value");
}, err => done(err), () => done());
obj.child.child.child.child.prop = "value";
});
it("should observe mapped property events for the entire child tree", (done) => {
var obj: MyObject = new MyObject();
var child: MyOtherObject = new MyOtherObject();
var child2: MyOtherObject = new MyOtherObject();
var child3: MyOtherObject = new MyOtherObject();
var child4: MyOtherObject = new MyOtherObject();
obj.child = child;
child.child = child2;
child2.child = child3;
child3.child = child4;
obj.whenAny(o => o.child.child.child.child.prop, e => e.newPropertyValue).skip(1).first().subscribe(value => {
expect(value).to.equal("value");
}, err => done(err), () => done());
obj.child.child.child.child.prop = "value";
});
it("should observe property events for swapped children", (done) => {
var obj: MyObject = new MyObject();
var child: MyOtherObject = new MyOtherObject();
var child2: MyOtherObject = new MyOtherObject();
var child3: MyOtherObject = new MyOtherObject();
var child4: MyOtherObject = new MyOtherObject();
var newChild2: MyOtherObject = new MyOtherObject();
var newChild3: MyOtherObject = new MyOtherObject();
var newChild4: MyOtherObject = new MyOtherObject();
obj.child = child;
child.child = child2;
child2.child = child3;
child3.child = child4;
newChild2.child = newChild3;
newChild3.child = newChild4;
newChild4.prop = "newValue";
obj.whenAny(o => o.child.child.child.child.prop).skip(1).take(2).bufferTime(10).subscribe(events => {
expect(events.length).to.equal(2);
expect(events[0]).to.not.be.null;
expect(events[0].propertyName).to.equal("prop");
expect(events[0].sender).to.equal(child4);
expect(events[0].newPropertyValue).to.equal("value");
expect(events[1]).to.not.be.null;
expect(events[1].propertyName).to.equal("prop");
expect(events[1].sender).to.equal(newChild4);
expect(events[1].newPropertyValue).to.equal("newValue");
}, err => done(err), () => done());
obj.child.child.child.child.prop = "value";
obj.child.child = newChild2;
child4.prop = "notObservedValue";
});
});
describe("#whenAny(prop)", () => {
it("should return an observable", () => {
var obj: ReactiveObject = new ReactiveObject();
var observable = obj.whenAny("prop");
expect(observable).to.be.instanceOf(Observable);
});
it("should observe property events for the given property name", (done) => {
var obj: ReactiveObject = new ReactiveObject();
obj.whenAny("prop").skip(1).first().subscribe((e: PropertyChangedEventArgs) => {
expect(e).to.not.be.null;
expect(e.propertyName).to.equal("prop");
expect(e.sender).to.equal(obj);
expect(e.newPropertyValue).to.equal("value");
}, err => done(err), () => done());
obj.set("prop", "value");
});
it("should observe property events for child reactive objects", (done) => {
var obj: ReactiveObject = new ReactiveObject();
var child: ReactiveObject = new ReactiveObject();
obj.set("child", child);
obj.whenAny("child.prop").skip(1).first().subscribe((e: PropertyChangedEventArgs) => {
expect(e).to.not.be.null;
expect(e.propertyName).to.equal("prop");
expect(e.sender).to.equal(child);
expect(e.newPropertyValue).to.equal("value");
}, err => done(err), () => done());
obj.get("child").set("prop", "value");
});
it("should observe property events for the entire child tree", (done) => {
var obj: ReactiveObject = new ReactiveObject();
var child: ReactiveObject = new ReactiveObject();
var child2: ReactiveObject = new ReactiveObject();
var child3: ReactiveObject = new ReactiveObject();
var child4: ReactiveObject = new ReactiveObject();
obj.set("child", child);
child.set("child2", child2);
child2.set("child3", child3);
child3.set("child4", child4);
obj.whenAny("child.child2.child3.child4.prop").skip(1).first().subscribe((e: PropertyChangedEventArgs) => {
expect(e).to.not.be.null;
expect(e.propertyName).to.equal("prop");
expect(e.sender).to.equal(child4);
expect(e.newPropertyValue).to.equal("value");
}, err => done(err), () => done());
obj.get("child").get("child2").get("child3").get("child4").set("prop", "value");
});
it("should observe property events for swapped children", (done) => {
var obj: ReactiveObject = new ReactiveObject();
var child: ReactiveObject = new ReactiveObject();
var child2: ReactiveObject = new ReactiveObject();
var child3: ReactiveObject = new ReactiveObject();
var child4: ReactiveObject = new ReactiveObject();
var newChild2: ReactiveObject = new ReactiveObject();
var newChild3: ReactiveObject = new ReactiveObject();
var newChild4: ReactiveObject = new ReactiveObject();
obj.set("child", child);
child.set("child2", child2);
child2.set("child3", child3);
child3.set("child4", child4);
newChild2.set("child3", newChild3);
newChild3.set("child4", newChild4);
newChild4.set("prop", "newValue");
obj.whenAny("child.child2.child3.child4.prop").skip(1).take(2).bufferTime(10).subscribe((events: PropertyChangedEventArgs[]) => {
expect(events.length).to.equal(2);
expect(events[0]).to.not.be.null;
expect(events[0].propertyName).to.equal("prop");
expect(events[0].sender).to.equal(child4);
expect(events[0].newPropertyValue).to.equal("value");
expect(events[1]).to.not.be.null;
expect(events[1].propertyName).to.equal("prop");
expect(events[1].sender).to.equal(newChild4);
expect(events[1].newPropertyValue).to.equal("newValue");
}, err => done(err), () => done());
child4.set("prop", "value");
obj.get("child").set("child2", newChild2);
});
});
describe("#whenAny(lambda1, lambda2, lambda3)", () => {
it("should observe property changes for multiple children", (done) => {
var obj: MyObject = new MyObject();
obj.prop1 = "prop1Value";
obj.prop2 = "prop2Value";
obj.prop3 = "prop3Value";
// The last parameter needs to be the map function.
// Otherwise the selector functions and map function could not be distinguished.
obj.whenAny(o => o.prop1, o => o.prop2, o => o.prop3, (prop1, prop2, prop3) => [prop1, prop2, prop3])
.skip(1)
.bufferTime(10)
.first()
.subscribe(events => {
expect(events.length).to.equal(3);
var firstEvents = events[0];
expect(firstEvents.length).to.equal(3);
expect(firstEvents[0].propertyName).to.equal("prop1");
expect(firstEvents[1].propertyName).to.equal("prop2");
expect(firstEvents[2].propertyName).to.equal("prop3");
expect(firstEvents[0].newPropertyValue).to.equal("newProp1Value");
expect(firstEvents[1].newPropertyValue).to.equal("prop2Value");
expect(firstEvents[2].newPropertyValue).to.equal("prop3Value");
expect(firstEvents[0].sender).to.equal(obj);
expect(firstEvents[1].sender).to.equal(obj);
expect(firstEvents[2].sender).to.equal(obj);
var secondEvents = events[1];
expect(secondEvents.length).to.equal(3);
expect(secondEvents[0].propertyName).to.equal("prop1");
expect(secondEvents[1].propertyName).to.equal("prop2");
expect(secondEvents[2].propertyName).to.equal("prop3");
expect(secondEvents[0].newPropertyValue).to.equal("newProp1Value");
expect(secondEvents[1].newPropertyValue).to.equal("newProp2Value");
expect(secondEvents[2].newPropertyValue).to.equal("prop3Value");
expect(secondEvents[0].sender).to.equal(obj);
expect(secondEvents[1].sender).to.equal(obj);
expect(secondEvents[2].sender).to.equal(obj);
var finalEvents = events[2];
expect(finalEvents.length).to.equal(3);
expect(finalEvents[0].propertyName).to.equal("prop1");
expect(finalEvents[1].propertyName).to.equal("prop2");
expect(finalEvents[2].propertyName).to.equal("prop3");
expect(finalEvents[0].newPropertyValue).to.equal("newProp1Value");
expect(finalEvents[1].newPropertyValue).to.equal("newProp2Value");
expect(finalEvents[2].newPropertyValue).to.equal("newProp3Value");
expect(finalEvents[0].sender).to.equal(obj);
expect(finalEvents[1].sender).to.equal(obj);
expect(finalEvents[2].sender).to.equal(obj);
}, err => done(err), () => done());
obj.prop1 = "newProp1Value";
obj.prop2 = "newProp2Value";
obj.prop3 = "newProp3Value";
});
it("should map observed property changes for multiple children", (done) => {
var obj: MyObject = new MyObject();
obj.prop1 = "prop1Value";
obj.prop2 = "prop2Value";
obj.prop3 = "prop3Value";
obj.whenAny(o => o.prop1, o => o.prop2, o => o.prop3, (prop1, prop2, prop3) => {
return {
prop1,
prop2,
prop3
}
}).skip(3).first().subscribe(e => {
expect(e.prop1.propertyName).to.equal("prop1");
expect(e.prop2.propertyName).to.equal("prop2");
expect(e.prop3.propertyName).to.equal("prop3");
expect(e.prop1.newPropertyValue).to.equal("newProp1Value");
expect(e.prop2.newPropertyValue).to.equal("newProp2Value");
expect(e.prop3.newPropertyValue).to.equal("newProp3Value");
expect(e.prop1.sender).to.equal(obj);
expect(e.prop2.sender).to.equal(obj);
expect(e.prop3.sender).to.equal(obj);
}, err => done(err), () => done());
obj.prop1 = "newProp1Value";
obj.prop2 = "newProp2Value";
obj.prop3 = "newProp3Value";
});
it("should handle recursive calls to #whenAny(lambda) via properties", () => {
class C extends ReactiveObject {
prop: string;
other: string;
get observable(): Observable {
return this.whenAnyValue(vm => vm.prop).map(p => p === "value");
}
get otherObservable(): Observable {
return this.whenAnyValue(vm => vm.other).map(p => p === "other");
}
constructor() {
super();
// Triggers a call to observable(),
// which in turn triggers whenAnyValue(),
// which in turn tries to build a ghost object for this one,
// which in turn triggers otherObservable()
// which in turn triggers whenAnyValue(),
// which repeats the process.
var command = ReactiveCommand.create(() => {
return true;
}, this.observable);
}
}
new C();
});
});
describe("#whenAny(prop1, prop2, prop3)", () => {
it("should observe property changes for multiple children", (done) => {
var obj: ReactiveObject = new ReactiveObject();
obj.set("prop1", "prop1Value");
obj.set("prop2", "prop2Value");
obj.set("prop3", "prop3Value");
obj.whenAny("prop1", "prop2", "prop3").skip(3).first().subscribe((events: PropertyChangedEventArgs[]) => {
expect(events.length).to.equal(3);
expect(events[0].propertyName).to.equal("prop1");
expect(events[1].propertyName).to.equal("prop2");
expect(events[2].propertyName).to.equal("prop3");
expect(events[0].newPropertyValue).to.equal("newProp1Value");
expect(events[1].newPropertyValue).to.equal("newProp2Value");
expect(events[2].newPropertyValue).to.equal("newProp3Value");
expect(events[0].sender).to.equal(obj);
expect(events[1].sender).to.equal(obj);
expect(events[2].sender).to.equal(obj);
}, err => done(err), () => done());
obj.set("prop1", "newProp1Value");
obj.set("prop2", "newProp2Value");
obj.set("prop3", "newProp3Value");
});
it("should map observed property changes for multiple children", (done) => {
var obj: ReactiveObject = new ReactiveObject();
obj.set("prop1", "prop1Value");
obj.set("prop2", "prop2Value");
obj.set("prop3", "prop3Value");
obj.whenAny("prop1", "prop2", "prop3", (prop1, prop2, prop3) => ({
prop1,
prop2,
prop3
})).skip(3).first().subscribe(e => {
expect(e.prop1.propertyName).to.equal("prop1");
expect(e.prop2.propertyName).to.equal("prop2");
expect(e.prop3.propertyName).to.equal("prop3");
expect(e.prop1.newPropertyValue).to.equal("newProp1Value");
expect(e.prop2.newPropertyValue).to.equal("newProp2Value");
expect(e.prop3.newPropertyValue).to.equal("newProp3Value");
expect(e.prop1.sender).to.equal(obj);
expect(e.prop2.sender).to.equal(obj);
expect(e.prop3.sender).to.equal(obj);
}, err => done(err), () => done());
obj.set("prop1", "newProp1Value");
obj.set("prop2", "newProp2Value");
obj.set("prop3", "newProp3Value");
});
});
describe("#whenAnyValue(lambda)", () => {
it("should return an observable", () => {
var obj: MyObject = new MyObject();
var observable = obj.whenAnyValue(o => o.otherProp);
expect(observable).to.be.instanceOf(Observable);
});
it("should observe property values for the given property name", (done) => {
var obj: MyObject = new MyObject();
obj.whenAnyValue(o => o.otherProp).skip(1).first().subscribe(value => {
expect(value).to.equal("value");
}, err => done(err), () => done());
obj.otherProp = "value";
});
it("should observe property values for the given property names", (done) => {
var obj: MyObject = new MyObject();
obj.whenAnyValue(o => o.prop1, o => o.prop2, (prop1, prop2) => [prop1, prop2]).skip(2).first().subscribe(values => {
expect(values.length).to.equal(2);
expect(values[0]).to.equal("value");
expect(values[1]).to.equal("value2");
}, err => done(err), () => done());
obj.prop1 = "value";
obj.prop2 = "value2";
});
it("should observe deep property value changes", (done) => {
var obj: MyObject = new MyObject();
var child: MyOtherObject = new MyOtherObject();
var child2: MyOtherObject = new MyOtherObject();
var child3: MyOtherObject = new MyOtherObject();
var child4: MyOtherObject = new MyOtherObject();
obj.child = child;
child.child = child2;
child2.child = child3;
child3.child = child4;
obj.whenAnyValue(o => o.child.child.child.child.prop).skip(1).first().subscribe(value => {
expect(value).to.equal("value");
}, err => done(err), () => done());
obj.child.child.child.child.prop = "value";
});
it("should observe mapped deep property value changes", (done) => {
var obj: MyObject = new MyObject();
var child: MyOtherObject = new MyOtherObject();
var child2: MyOtherObject = new MyOtherObject();
var child3: MyOtherObject = new MyOtherObject();
var child4: MyOtherObject = new MyOtherObject();
obj.child = child;
child.child = child2;
child2.child = child3;
child3.child = child4;
obj.whenAnyValue(o => o.child.child.child.child.prop, v => v === "value").skip(1).first().subscribe(value => {
expect(value).to.be.true;
}, err => done(err), () => done());
obj.child.child.child.child.prop = "value";
});
it("should handle properties that are currently null or undefined on the source object", (done) => {
class MySpecialObject extends ReactiveObject {
public get prop(): MyOtherObject {
return this.get("prop");
}
public set prop(val: MyOtherObject) {
this.set("prop", val);
}
// Notice no setting prop to null in the constructor.
}
var obj: MySpecialObject = new MySpecialObject();
obj.whenAnyValue(o => o.prop.child.child.prop, v => v === "value").skip(1).first().subscribe(value => {
expect(value).to.be.true;
}, err => done(err), () => done());
var other = new MyOtherObject();
other.child = new MyOtherObject();
other.child.child = new MyOtherObject();
other.child.child.prop = "value";
obj.prop = other;
});
it("should handle nulls along the entire path", () => {
class MySpecialObject extends ReactiveObject {
public get prop(): MyOtherObject {
return this.get("prop");
}
public set prop(val: MyOtherObject) {
this.set("prop", val);
}
// Notice no setting prop to null in the constructor.
}
var obj: MySpecialObject = new MySpecialObject();
var events: any[] = [];
obj.whenAnyValue(o => o.prop.child.child.prop)
.skip(1)
.subscribe(event => {
events.push(event);
}, err => { throw err });
var other = new MyOtherObject();
var child1 = new MyOtherObject();
var child2 = new MyOtherObject();
other.child = child1;
other.child.child = child2;
other.child.child.prop = "value";
obj.prop = other;
other.child = null;
child2.prop = "great!";
other.child = child1;
child2.prop = null;
expect(events.length).to.equal(4);
expect(events[0]).to.equal("value");
expect(events[1]).to.be.null;
expect(events[2]).to.equal("great!");
expect(events[3]).to.be.null;
});
});
describe("#whenAnyValue(prop)", () => {
it("should return an observable", () => {
var obj: ReactiveObject = new ReactiveObject();
var observable = obj.whenAnyValue("prop");
expect(observable).to.be.instanceOf(Observable);
});
it("should observe property values for the given property name", (done) => {
var obj: ReactiveObject = new ReactiveObject();
obj.whenAnyValue("prop").skip(1).first().subscribe(value => {
expect(value).to.equal("value");
}, err => done(err), () => done());
obj.set("prop", "value");
});
it("should resolve immediately with the current property value", (done) => {
var obj: ReactiveObject = new ReactiveObject();
obj.whenAnyValue("prop").first().subscribe(value => {
expect(value).to.equal(null);
}, err => done(err), () => done());
});
it("should observe multiple property values for the given property names", (done) => {
var obj: ReactiveObject = new ReactiveObject();
obj.whenAnyValue("prop", "prop2").skip(2).first().subscribe(values => {
expect(values.length).to.equal(2);
expect(values[0]).to.equal("value");
expect(values[1]).to.equal("value2");
}, err => done(err), () => done());
obj.set("prop", "value");
obj.set("prop2", "value2");
});
it("should observe deep property value changes", (done) => {
var obj: ReactiveObject = new ReactiveObject();
var child: ReactiveObject = new ReactiveObject();
var child2: ReactiveObject = new ReactiveObject();
var child3: ReactiveObject = new ReactiveObject();
var child4: ReactiveObject = new ReactiveObject();
obj.set("child", child);
child.set("child2", child2);
child2.set("child3", child3);
child3.set("child4", child4);
obj.whenAnyValue("child.child2.child3.child4.prop").skip(1).first().subscribe(value => {
expect(value).to.equal("value");
}, err => done(err), () => done());
obj.get("child").get("child2").get("child3").get("child4").set("prop", "value");
});
it("should handle deep property changes with parents changing", (done) => {
var obj: ReactiveObject = new ReactiveObject();
var child: ReactiveObject = new ReactiveObject();
var child2: ReactiveObject = new ReactiveObject();
var child3: ReactiveObject = new ReactiveObject();
var child4: ReactiveObject = new ReactiveObject();
obj.set("child", child);
child.set("child2", child2);
child2.set("child3", child3);
child3.set("child4", child4);
var newChild2: ReactiveObject = new ReactiveObject();
var newChild3: ReactiveObject = new ReactiveObject();
var newChild4: ReactiveObject = new ReactiveObject();
newChild2.set("child3", newChild3);
newChild3.set("child4", newChild4);
newChild4.set("prop", "value");
obj.whenAnyValue("child.child2.child3.child4.prop").skip(1).first().subscribe(value => {
expect(value).to.equal("value");
}, err => done(err), () => done());
obj.get("child").set("child2", newChild2);
});
});
describe("#bind(view, prop, prop)", () => {
class MyView extends ReactiveObject {
public get prop(): string {
return this.get("prop");
}
public set prop(val: string) {
this.set("prop", val);
}
}
it("should return a subscription", () => {
var vm = new MyObject();
var view = new MyView();
view.prop = "value";
var ret = vm.bind(view, vm => vm.prop1, view => view.prop);
expect(ret).to.be.instanceOf(Subscription);
});
it("should set the bound property on the view object when the view model property changes", () => {
var vm = new MyObject();
var view = new MyView();
var viewPropChanges: PropertyChangedEventArgs[] = [];
vm.prop1 = "Old Value";
view.prop = "View Value";
view.whenAny(v => v.prop).skip(1).subscribe(change => viewPropChanges.push(change));
expect(viewPropChanges.length).to.equal(0);
var sub = vm.bind(view, vm => vm.prop1, view => view.prop);
expect(viewPropChanges.length).to.equal(1);
vm.prop1 = "New Value";
expect(viewPropChanges.length).to.equal(2);
expect(viewPropChanges[0].newPropertyValue).to.equal("Old Value");
expect(viewPropChanges[1].newPropertyValue).to.equal("New Value");
sub.unsubscribe();
});
it("should set the bound property on the view model when the view property changes", () => {
var vm = new MyObject();
var view = new MyView();
var vmPropChanges: PropertyChangedEventArgs[] = [];
vm.prop1 = "Old Value";
view.prop = "View Value";
vm.whenAny(v => v.prop1).skip(1).subscribe(change => vmPropChanges.push(change));
var sub = vm.bind(view, vm => vm.prop1, view => view.prop);
expect(vmPropChanges.length).to.equal(0);
view.prop = "New Value";
expect(vmPropChanges.length).to.equal(1);
expect(vmPropChanges[0].newPropertyValue).to.equal("New Value");
sub.unsubscribe();
});
it("should stop binding values when unsubscribed from", () => {
var vm = new MyObject();
var view = new MyView();
var viewPropChanges: PropertyChangedEventArgs[] = [];
vm.prop1 = "Old Value";
view.prop = "View Value";
view.whenAny(v => v.prop).skip(1).subscribe(change => viewPropChanges.push(change));
var sub = vm.bind(view, vm => vm.prop1, view => view.prop);
vm.prop1 = "New Value";
var numChanges = viewPropChanges.length;
sub.unsubscribe();
vm.prop1 = "OtherNewValue";
expect(viewPropChanges.length).to.equal(numChanges);
});
it("should throw error when binding to regular object without __viewBindingHelper", () => {
var vm = new MyObject();
var view = {
customProp: "value"
};
expect(() => {
var sub = vm.bind(view, vm => vm.prop1, view => view.customProp);
}).to.throw("Unable to bind to objects that do not inherit from ReactiveObject or provide __viewBindingHelper");
});
it("should bind to non reactive object with __viewBindingHelper", () => {
var vm = new MyObject();
var helper = new ViewBindingHelper();
var view = {
__viewBindingHelper: helper,
changed: null,
prop: "property value"
};
vm.prop1 = "Old Value";
var sub = vm.bind(view, vm => vm.prop1, view => view.prop);
expect(view.prop).to.equal("Old Value");
expect(view.changed).to.not.be.null;
view.prop = "New Value";
view.changed(view.prop);
expect(vm.prop1).to.equal("New Value");
sub.unsubscribe();
expect(view.changed).to.be.null;
});
it("should recieve multiple changes from the view and apply them to the view model", () => {
var vm = new MyObject();
var helper = new ViewBindingHelper();
var view = {
__viewBindingHelper: helper,
changed: null,
prop: "property value"
};
vm.prop1 = "Old Value";
var sub = vm.bind(view, vm => vm.prop1, view => view.prop);
expect(view.prop).to.equal("Old Value");
expect(view.changed).to.not.be.null;
view.prop = "New Value";
view.changed(view.prop);
view.prop = "Newer Value";
view.changed(view.prop);
sub.unsubscribe();
expect(vm.prop1).to.equal("Newer Value");
});
it("should recieve multiple changes from the view model and apply them to the view", () => {
var vm = new MyObject();
var helper = new ViewBindingHelper();
var view = {
__viewBindingHelper: helper,
changed: null,
prop: "property value"
};
vm.prop1 = "Old Value";
var sub = vm.bind(view, vm => vm.prop1, view => view.prop);
expect(view.prop).to.equal("Old Value");
expect(view.changed).to.not.be.null;
vm.prop1 = "New Value";
vm.prop1 = "Newer Value";
sub.unsubscribe();
expect(view.prop).to.equal("Newer Value");
});
});
describe("#oneWayBind(view, prop, prop)", () => {
it("should one way bind to regular objects", () => {
var vm = new MyObject();
var view = {
customProp: "value"
};
var sub = vm.oneWayBind(view, vm => vm.prop1, view => view.customProp);
vm.prop1 = "New value";
expect(view.customProp).to.equal("New value");
});
it("should not propagate values from view", () => {
var vm = new MyObject();
var view = {
customProp: "value"
};
vm.prop1 = "customValue";
var sub = vm.oneWayBind(view, vm => vm.prop1, view => view.customProp);
view.customProp = "New value";
expect(vm.prop1).to.equal("customValue");
});
it("should return a subscription", () => {
var vm = new MyObject();
var view = {
customProp: "value"
};
var sub = vm.oneWayBind(view, vm => vm.prop1, view => view.customProp);
expect(sub).to.be.instanceOf(Subscription);
});
it("should recieve multiple values from the view model and apply them to the view", () => {
var vm = new MyObject();
var helper = new ViewBindingHelper();
var view = {
__viewBindingHelper: helper,
changed: null,
prop: "property value"
};
vm.prop1 = "Old Value";
var sub = vm.oneWayBind(view, vm => vm.prop1, view => view.prop);
expect(view.prop).to.equal("Old Value");
vm.prop1 = "New Value";
vm.prop1 = "Newer Value";
sub.unsubscribe();
expect(view.prop).to.equal("Newer Value");
});
});
describe(".bindObservable", () => {
it("should return a subscription", () => {
var observable = Observable.of(true, false);
var view = {
customProp: "value"
};
var sub = ReactiveObject.bindObservable(observable, view, view => view.customProp);
expect(sub).to.be.instanceOf(Subscription);
});
it("should pipe values from the observable to the view", () => {
var observable = Observable.of(true, false);
var view = {
customProp: "value"
};
var sub = ReactiveObject.bindObservable(observable, view, view => view.customProp);
// Both values are piped through immediately
expect(view.customProp).to.be.false;
});
});
describe("#toProperty(observable, prop)", () => {
it("should return a subscription", () => {
var obj = new ReactiveObject();
var sub = obj.toProperty(Observable.of(true), "prop");
expect(sub).to.be.instanceOf(Subscription);
});
it("should bind the latest value from the given observable to the given property", () => {
var obj = new ReactiveObject();
var sub = obj.toProperty(Observable.of(false, true), "prop");
expect(obj.get("prop")).to.be.true;
});
it("should raise a property changed event when the given observable resolves", () => {
var obj = new ReactiveObject();
var events: PropertyChangedEventArgs[] = [];
obj.whenAny("prop").skip(1).subscribe(e => events.push(e));
var sub = obj.toProperty(Observable.of(false, true), "prop");
expect(events.length).to.equal(2);
expect(events[0].newPropertyValue).to.be.false;
expect(events[1].newPropertyValue).to.be.true;
});
it("should ignore consecutive duplicate values", () => {
var obj = new ReactiveObject();
var events: PropertyChangedEventArgs[] = [];
obj.whenAny("prop").skip(1).subscribe(e => events.push(e));
var sub = obj.toProperty(Observable.of(false, false, false, true), "prop");
expect(events.length).to.equal(2);
expect(events[0].newPropertyValue).to.be.false;
expect(events[1].newPropertyValue).to.be.true;
});
it("should not support setting nested values", () => {
var obj = new ReactiveObject();
expect(() => {
var sub = obj.toProperty(Observable.of(false, true), "prop.val");
}).to.throw("Null Reference Exception. Cannot set a child property on a null or undefined property of this object.");
});
});
describe("#toJSON()", () => {
it("should return the data that has been set in the object", () => {
var obj = new ReactiveObject();
obj.set("prop", "Value");
obj.set("otherProp", 10);
obj.set("greatProp", 14.4);
obj.set("myProp", { hello: "World!" });
obj.set("null", null);
obj.set("undefined", undefined); // Should not be in the returned data.
var json = obj.toJSON();
expect(json).to.eql({
prop: "Value",
otherProp: 10,
greatProp: 14.4,
myProp: { hello: "World!" },
"null": null
});
});
it("should return a copy of the data that is in the object", () => {
var obj = new ReactiveObject();
obj.set("prop", "Value");
var json = obj.toJSON();
json["prop"] = "Other";
expect(obj.get("prop")).to.equal("Value");
});
});
describe("#toString()", () => {
it("should return the JSON representation of the object", () => {
var obj = new ReactiveObject();
obj.set("prop", "Value");
var str = obj.toString();
expect(str).to.equal('{"prop":"Value"}');
});
});
describe(".keys()", () => {
it("should return the names of the properties that have been set on the object", () => {
var obj = new ReactiveObject();
obj.set("prop", "Value");
obj.set("some-krazy_property+name!", null);
obj.set("undefinedProp", undefined); // undefined properties are not allowed to be set
var keys = ReactiveObject.keys(obj);
expect(keys).to.eql([
"prop",
"some-krazy_property+name!"
]);
});
it("should return the names of the properties set on a regular object", () => {
var obj = {
prop: "Value",
"some-krazy_property+name!": null,
undefinedProp: undefined // undefined properties exist on regular objects
};
var keys = ReactiveObject.keys(obj);
expect(keys).to.eql([
"prop",
"some-krazy_property+name!",
"undefinedProp"
]);
});
});
});