feat(change_detection): added a directive lifecycle hook that is called after children are checked

This commit is contained in:
vsavkin
2015-03-26 14:36:25 -07:00
parent 507f7ea70a
commit 723e8fde93
13 changed files with 283 additions and 82 deletions

View File

@ -44,7 +44,8 @@ export function main() {
var dispatcher = new TestDispatcher();
var variableBindings = convertLocalsToVariableBindings(locals);
var cd = pcd.instantiate(dispatcher, [new BindingRecord(ast(exp), memo, memo)], variableBindings);
var records = [new BindingRecord(ast(exp), memo, new FakeDirectiveMemento(memo, false))];
var cd = pcd.instantiate(dispatcher, records, variableBindings, []);
cd.hydrate(context, locals);
return {"changeDetector" : cd, "dispatcher" : dispatcher};
@ -56,6 +57,10 @@ export function main() {
return res["dispatcher"].log;
}
function instantiate(protoChangeDetector, dispatcher, bindings) {
return protoChangeDetector.instantiate(dispatcher, bindings, null, []);
}
describe(`${name} change detection`, () => {
it('should do simple watching', () => {
var person = new Person("misko");
@ -193,7 +198,7 @@ export function main() {
var ast = parser.parseInterpolation("B{{a}}A", "location");
var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, [new BindingRecord(ast, "memo", "memo")], null);
var cd = instantiate(pcd, dispatcher, [new BindingRecord(ast, "memo", null)]);
cd.hydrate(new TestData("value"), null);
cd.detectChanges();
@ -239,15 +244,18 @@ export function main() {
});
describe("group changes", () => {
var dirMemento1 = new FakeDirectiveMemento(1);
var dirMemento2 = new FakeDirectiveMemento(2);
it("should notify the dispatcher when a group of records changes", () => {
var pcd = createProtoChangeDetector();
var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, [
new BindingRecord(ast("1 + 2"), "memo", "1"),
new BindingRecord(ast("10 + 20"), "memo", "1"),
new BindingRecord(ast("100 + 200"), "memo", "2")
], null);
var cd = instantiate(pcd, dispatcher, [
new BindingRecord(ast("1 + 2"), "memo", dirMemento1),
new BindingRecord(ast("10 + 20"), "memo", dirMemento1),
new BindingRecord(ast("100 + 200"), "memo", dirMemento2)
]);
cd.detectChanges();
@ -257,11 +265,11 @@ export function main() {
it("should notify the dispatcher before switching to the next group", () => {
var pcd = createProtoChangeDetector();
var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, [
new BindingRecord(ast("a()"), "a", "1"),
new BindingRecord(ast("b()"), "b", "2"),
new BindingRecord(ast("c()"), "c", "2")
], null);
var cd = instantiate(pcd, dispatcher, [
new BindingRecord(ast("a()"), "a", dirMemento1),
new BindingRecord(ast("b()"), "b", dirMemento2),
new BindingRecord(ast("c()"), "c", dirMemento2)
]);
var tr = new TestRecord();
tr.a = () => {
@ -283,6 +291,46 @@ export function main() {
expect(dispatcher.loggedValues).toEqual(['InvokeA', ['a'], 'InvokeB', 'InvokeC', ['b', 'c']]);
});
});
describe("onAllChangesDone", () => {
it("should notify the dispatcher about processing all the children", () => {
var pcd = createProtoChangeDetector();
var dispatcher = new TestDispatcher();
var memento1 = new FakeDirectiveMemento(1, false);
var memento2 = new FakeDirectiveMemento(2, true);
var cd = pcd.instantiate(dispatcher, [
new BindingRecord(ast("1"), "a", memento1),
new BindingRecord(ast("2"), "b", memento2)
], null, [memento1, memento2]);
cd.hydrate(null, null);
cd.detectChanges();
expect(dispatcher.loggedOnAllChangesDone).toEqual([memento2]);
});
it("should notify in reverse order so the child is always notified before the parent", () => {
var pcd = createProtoChangeDetector();
var dispatcher = new TestDispatcher();
var memento1 = new FakeDirectiveMemento(1, true);
var memento2 = new FakeDirectiveMemento(2, true);
var cd = pcd.instantiate(dispatcher, [
new BindingRecord(ast("1"), "a", memento1),
new BindingRecord(ast("2"), "b", memento2)
], null, [memento1, memento2]);
cd.hydrate(null, null);
cd.detectChanges();
expect(dispatcher.loggedOnAllChangesDone).toEqual([memento2, memento1]);
});
});
});
describe("enforce no new changes", () => {
@ -291,9 +339,9 @@ export function main() {
pcd.addAst(ast("a"), "a", 1);
var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, [
var cd = instantiate(pcd, dispatcher, [
new BindingRecord(ast("a"), "a", 1)
], null);
]);
cd.hydrate(new TestData('value'), null);
expect(() => {
@ -308,7 +356,7 @@ export function main() {
var pcd = createProtoChangeDetector();
var cd = pcd.instantiate(new TestDispatcher(), [
new BindingRecord(ast("invalidProp", "someComponent"), "a", 1)
], null);
], null, []);
cd.hydrate(null, null);
try {
@ -363,10 +411,10 @@ export function main() {
beforeEach(() => {
var protoParent = createProtoChangeDetector();
parent = protoParent.instantiate(null, [], null);
parent = instantiate(protoParent, null, []);
var protoChild = createProtoChangeDetector();
child = protoChild.instantiate(null, [], null);
child = instantiate(protoChild, null, []);
});
it("should add children", () => {
@ -409,7 +457,7 @@ export function main() {
});
it("should change CHECK_ONCE to CHECKED", () => {
var cd = createProtoChangeDetector().instantiate(null, [], null);
var cd = instantiate(createProtoChangeDetector(), null, []);
cd.mode = CHECK_ONCE;
cd.detectChanges();
@ -418,7 +466,7 @@ export function main() {
});
it("should not change the CHECK_ALWAYS", () => {
var cd = createProtoChangeDetector().instantiate(null, [], null);
var cd = instantiate(createProtoChangeDetector(), null, []);
cd.mode = CHECK_ALWAYS;
cd.detectChanges();
@ -429,7 +477,7 @@ export function main() {
describe("markPathToRootAsCheckOnce", () => {
function changeDetector(mode, parent) {
var cd = createProtoChangeDetector().instantiate(null, [], null);
var cd = instantiate(createProtoChangeDetector(), null, []);
cd.mode = mode;
if (isPresent(parent)) parent.addChild(cd);
return cd;
@ -700,16 +748,28 @@ class TestData {
}
}
class FakeDirectiveMemento {
value:any;
notifyOnAllChangesDone:boolean;
constructor(value, notifyOnAllChangesDone:boolean = false) {
this.value = value;
this.notifyOnAllChangesDone = notifyOnAllChangesDone;
}
}
class TestDispatcher extends ChangeDispatcher {
log:List;
loggedValues:List;
changeRecords:List;
loggedOnAllChangesDone:List;
onChange:Function;
constructor() {
super();
this.log = null;
this.loggedValues = null;
this.loggedOnAllChangesDone = null;
this.onChange = (_, __) => {};
this.clear();
}
@ -717,6 +777,7 @@ class TestDispatcher extends ChangeDispatcher {
clear() {
this.log = ListWrapper.create();
this.loggedValues = ListWrapper.create();
this.loggedOnAllChangesDone = ListWrapper.create();
this.changeRecords = ListWrapper.create();
}
@ -724,7 +785,7 @@ class TestDispatcher extends ChangeDispatcher {
ListWrapper.push(this.loggedValues, value);
}
onRecordChange(group, changeRecords:List) {
onRecordChange(directiveMemento, changeRecords:List) {
var value = changeRecords[0].change.currentValue;
var memento = changeRecords[0].bindingMemento;
ListWrapper.push(this.log, memento + '=' + this._asString(value));
@ -734,9 +795,12 @@ class TestDispatcher extends ChangeDispatcher {
ListWrapper.push(this.changeRecords, changeRecords);
this.onChange(group, changeRecords);
this.onChange(directiveMemento, changeRecords);
}
onAllChangesDone(directiveMemento) {
ListWrapper.push(this.loggedOnAllChangesDone, directiveMemento);
}
_asString(value) {
return (isBlank(value) ? 'null' : value.toString());