test(change detect): Port change detect tests for mode

More the change detect tests that exercise various detection modes to
use the Dart pre-generated change detectors in addition to the `dynamic`
and `JIT` change detectors.

See #502
This commit is contained in:
Tim Blasi 2015-06-08 18:07:06 -07:00
parent 2cc2196140
commit ddd5a235c3
3 changed files with 235 additions and 238 deletions

View File

@ -208,7 +208,7 @@ class _CodegenState {
var detectorFieldNames = _genGetDetectorFieldNames(); var detectorFieldNames = _genGetDetectorFieldNames();
for (var i = 0; i < detectorFieldNames.length; ++i) { for (var i = 0; i < detectorFieldNames.length; ++i) {
buf.writeln('${detectorFieldNames[i]} = directives.getDetectorFor(' buf.writeln('${detectorFieldNames[i]} = directives.getDetectorFor('
'$_DIRECTIVES_ACCESSOR[$i].directiveIndex)'); '$_DIRECTIVES_ACCESSOR[$i].directiveIndex);');
} }
return '$buf'; return '$buf';
} }

View File

@ -401,7 +401,95 @@ export function main() {
}); });
}); });
// TODO(kegluneq): Insert describe('mode', ...) testcases. describe('mode', () => {
var _createWithoutHydrate = function(expression: string) {
var registry = null;
return _getProtoChangeDetector(getDefinition(expression).cdDef, registry).instantiate(new TestDispatcher());
};
it('should set the mode to CHECK_ALWAYS when the default change detection is used',
() => {
var cd = _createWithoutHydrate('emptyUsingDefaultStrategy');
expect(cd.mode).toEqual(null);
cd.hydrate(_DEFAULT_CONTEXT, null, null);
expect(cd.mode).toEqual(CHECK_ALWAYS);
});
it('should set the mode to CHECK_ONCE when the push change detection is used', () => {
var cd = _createWithoutHydrate('emptyUsingOnPushStrategy');
cd.hydrate(_DEFAULT_CONTEXT, null, null);
expect(cd.mode).toEqual(CHECK_ONCE);
});
it('should not check a detached change detector', () => {
var val = _createChangeDetector('a', new TestData('value'));
val.changeDetector.hydrate(_DEFAULT_CONTEXT, null, null);
val.changeDetector.mode = DETACHED;
val.changeDetector.detectChanges();
expect(val.dispatcher.log).toEqual([]);
});
it('should not check a checked change detector', () => {
var val = _createChangeDetector('a', new TestData('value'));
val.changeDetector.hydrate(_DEFAULT_CONTEXT, null, null);
val.changeDetector.mode = CHECKED;
val.changeDetector.detectChanges();
expect(val.dispatcher.log).toEqual([]);
});
it('should change CHECK_ONCE to CHECKED', () => {
var cd = _createChangeDetector('10').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, null);
cd.mode = CHECK_ONCE;
cd.detectChanges();
expect(cd.mode).toEqual(CHECKED);
});
it('should not change the CHECK_ALWAYS', () => {
var cd = _createChangeDetector('10').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, null);
cd.mode = CHECK_ALWAYS;
cd.detectChanges();
expect(cd.mode).toEqual(CHECK_ALWAYS);
});
describe('marking ON_PUSH detectors as CHECK_ONCE after an update', () => {
var checkedDetector;
var directives;
beforeEach(() => {
checkedDetector = _createWithoutHydrate('emptyUsingOnPushStrategy');
checkedDetector.hydrate(_DEFAULT_CONTEXT, null, null);
checkedDetector.mode = CHECKED;
var targetDirective = new TestData(null);
directives = new FakeDirectives([targetDirective], [checkedDetector]);
});
it('should set the mode to CHECK_ONCE when a binding is updated', () => {
var cd = _createWithoutHydrate('onPushRecordsUsingDefaultStrategy');
cd.hydrate(_DEFAULT_CONTEXT, null, directives);
expect(checkedDetector.mode).toEqual(CHECKED);
// evaluate the record, update the targetDirective, and mark its detector as
// CHECK_ONCE
cd.detectChanges();
expect(checkedDetector.mode).toEqual(CHECK_ONCE);
});
});
});
describe('markPathToRootAsCheckOnce', () => { describe('markPathToRootAsCheckOnce', () => {
function changeDetector(mode, parent) { function changeDetector(mode, parent) {
@ -595,21 +683,6 @@ export function main() {
function dirs(directives: List<any>) { return new FakeDirectives(directives, []); } function dirs(directives: List<any>) { return new FakeDirectives(directives, []); }
function createChangeDetector(propName: string, exp: string, context = _DEFAULT_CONTEXT,
registry = null) {
var dispatcher = new TestDispatcher();
var locals = null;
var variableBindings = [];
var records = [BindingRecord.createForElement(ast(exp), 0, propName)];
var pcd = createProtoChangeDetector(records, variableBindings, [], registry);
var cd = pcd.instantiate(dispatcher);
cd.hydrate(context, locals, null);
return {"changeDetector": cd, "dispatcher": dispatcher};
}
describe(`${name} change detection`, () => { describe(`${name} change detection`, () => {
var dispatcher; var dispatcher;
@ -879,113 +952,6 @@ export function main() {
}); });
}); });
}); });
describe("mode", () => {
it("should set the mode to CHECK_ALWAYS when the default change detection is used",
() => {
var proto = createProtoChangeDetector([], [], [], null, DEFAULT);
var cd = proto.instantiate(null);
expect(cd.mode).toEqual(null);
cd.hydrate(_DEFAULT_CONTEXT, null, null);
expect(cd.mode).toEqual(CHECK_ALWAYS);
});
it("should set the mode to CHECK_ONCE when the push change detection is used", () => {
var proto = createProtoChangeDetector([], [], [], null, ON_PUSH);
var cd = proto.instantiate(null);
cd.hydrate(_DEFAULT_CONTEXT, null, null);
expect(cd.mode).toEqual(CHECK_ONCE);
});
it("should not check a detached change detector", () => {
var c = createChangeDetector('name', 'a', new TestData("value"));
var cd = c["changeDetector"];
var dispatcher = c["dispatcher"];
cd.hydrate(_DEFAULT_CONTEXT, null, null);
cd.mode = DETACHED;
cd.detectChanges();
expect(dispatcher.log).toEqual([]);
});
it("should not check a checked change detector", () => {
var c = createChangeDetector('name', 'a', new TestData("value"));
var cd = c["changeDetector"];
var dispatcher = c["dispatcher"];
cd.hydrate(_DEFAULT_CONTEXT, null, null);
cd.mode = CHECKED;
cd.detectChanges();
expect(dispatcher.log).toEqual([]);
});
it("should change CHECK_ONCE to CHECKED", () => {
var cd = createProtoChangeDetector([]).instantiate(null);
cd.hydrate(_DEFAULT_CONTEXT, null, null);
cd.mode = CHECK_ONCE;
cd.detectChanges();
expect(cd.mode).toEqual(CHECKED);
});
it("should not change the CHECK_ALWAYS", () => {
var cd = createProtoChangeDetector([]).instantiate(null);
cd.hydrate(_DEFAULT_CONTEXT, null, null);
cd.mode = CHECK_ALWAYS;
cd.detectChanges();
expect(cd.mode).toEqual(CHECK_ALWAYS);
});
describe("marking ON_PUSH detectors as CHECK_ONCE after an update", () => {
var checkedDetector;
var dirRecordWithOnPush;
var updateDirWithOnPushRecord;
var directives;
beforeEach(() => {
var proto = createProtoChangeDetector([], [], [], null, ON_PUSH);
checkedDetector = proto.instantiate(null);
checkedDetector.hydrate(_DEFAULT_CONTEXT, null, null);
checkedDetector.mode = CHECKED;
// this directive is a component with ON_PUSH change detection
dirRecordWithOnPush = new DirectiveRecord(
{directiveIndex: new DirectiveIndex(0, 0), changeDetection: ON_PUSH});
// a record updating a component
updateDirWithOnPushRecord = BindingRecord.createForDirective(
ast("42"), "a", (o, v) => o.a = v, dirRecordWithOnPush);
var targetDirective = new TestData(null);
directives = new FakeDirectives([targetDirective], [checkedDetector]);
});
it("should set the mode to CHECK_ONCE when a binding is updated", () => {
var proto = createProtoChangeDetector([updateDirWithOnPushRecord], [],
[dirRecordWithOnPush]);
var cd = proto.instantiate(null);
cd.hydrate(_DEFAULT_CONTEXT, null, directives);
expect(checkedDetector.mode).toEqual(CHECKED);
// evaluate the record, update the targetDirective, and mark its detector as
// CHECK_ONCE
cd.detectChanges();
expect(checkedDetector.mode).toEqual(CHECK_ONCE);
});
});
});
}); });
}); });
} }

View File

@ -1,8 +1,12 @@
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent} from 'angular2/src/facade/lang'; import {isBlank, isPresent} from 'angular2/src/facade/lang';
import { import {
DEFAULT,
ON_PUSH,
BindingRecord, BindingRecord,
ChangeDetectorDefinition, ChangeDetectorDefinition,
DirectiveIndex,
DirectiveRecord,
Lexer, Lexer,
Locals, Locals,
Parser Parser
@ -34,26 +38,31 @@ export var PROP_NAME = 'propName';
* In this case, we expect `id` and `expression` to be the same string. * In this case, we expect `id` and `expression` to be the same string.
*/ */
export function getDefinition(id: string): TestDefinition { export function getDefinition(id: string): TestDefinition {
var expression = null; var testDef = null;
var locals = null; if (StringMapWrapper.contains(_ExpressionWithLocals.availableDefinitions, id)) {
if (MapWrapper.contains(_availableDefinitionsWithLocals, id)) { let val = StringMapWrapper.get(_ExpressionWithLocals.availableDefinitions, id);
var val = MapWrapper.get(_availableDefinitionsWithLocals, id); let cdDef = val.createChangeDetectorDefinition();
expression = val.expression; cdDef.id = id;
locals = val.locals; testDef = new TestDefinition(id, cdDef, val.locals);
} else if (StringMapWrapper.contains(_ExpressionWithMode.availableDefinitions, id)) {
let val = StringMapWrapper.get(_ExpressionWithMode.availableDefinitions, id);
let cdDef = val.createChangeDetectorDefinition();
cdDef.id = id;
testDef = new TestDefinition(id, cdDef, null);
} else if (ListWrapper.indexOf(_availableDefinitions, id) >= 0) { } else if (ListWrapper.indexOf(_availableDefinitions, id) >= 0) {
expression = id; var strategy = null;
var variableBindings = [];
var bindingRecords = _createBindingRecords(id);
var directiveRecords = [];
let cdDef = new ChangeDetectorDefinition(id, strategy, variableBindings, bindingRecords,
directiveRecords);
testDef = new TestDefinition(id, cdDef, null);
} }
if (isBlank(expression)) { if (isBlank(testDef)) {
throw `No ChangeDetectorDefinition for ${id} available. Please modify this file if necessary.`; throw `No ChangeDetectorDefinition for ${id} available. Please modify this file if necessary.`;
} }
var strategy = null; return testDef;
var variableBindings = isPresent(locals) ? _convertLocalsToVariableBindings(locals) : [];
var bindingRecords = _createBindingRecords(expression);
var directiveRecords = [];
var cdDef = new ChangeDetectorDefinition(id, strategy, variableBindings, bindingRecords,
directiveRecords);
return new TestDefinition(id, cdDef, locals);
} }
export class TestDefinition { export class TestDefinition {
@ -68,12 +77,80 @@ export function getAllDefinitions(): List<TestDefinition> {
return ListWrapper.map(_availableDefinitions, (id) => getDefinition(id)); return ListWrapper.map(_availableDefinitions, (id) => getDefinition(id));
} }
class _ExpressionWithLocals {
constructor(private _expression: string, public locals: Locals) {}
createChangeDetectorDefinition(): ChangeDetectorDefinition {
var strategy = null;
var variableBindings = _convertLocalsToVariableBindings(this.locals);
var bindingRecords = _createBindingRecords(this._expression);
var directiveRecords = [];
return new ChangeDetectorDefinition('(empty id)', strategy, variableBindings, bindingRecords,
directiveRecords);
}
/**
* Map from test id to _ExpressionWithLocals.
* Tests in this map define an expression and local values which those expressions refer to.
*/
static availableDefinitions: StringMap<string, _ExpressionWithLocals> = {
'valueFromLocals': new _ExpressionWithLocals(
'key', new Locals(null, MapWrapper.createFromPairs([['key', 'value']]))),
'functionFromLocals': new _ExpressionWithLocals(
'key()', new Locals(null, MapWrapper.createFromPairs([['key', () => 'value']]))),
'nestedLocals': new _ExpressionWithLocals(
'key', new Locals(new Locals(null, MapWrapper.createFromPairs([['key', 'value']])),
MapWrapper.create())),
'fallbackLocals': new _ExpressionWithLocals(
'name', new Locals(null, MapWrapper.createFromPairs([['key', 'value']]))),
'contextNestedPropertyWithLocals': new _ExpressionWithLocals(
'address.city', new Locals(null, MapWrapper.createFromPairs([['city', 'MTV']]))),
'localPropertyWithSimilarContext': new _ExpressionWithLocals(
'city', new Locals(null, MapWrapper.createFromPairs([['city', 'MTV']])))
};
}
class _ExpressionWithMode {
constructor(private _strategy: string, private _withRecords: boolean) {}
createChangeDetectorDefinition(): ChangeDetectorDefinition {
var variableBindings = [];
var bindingRecords = null;
var directiveRecords = null;
if (this._withRecords) {
var dirRecordWithOnPush =
new DirectiveRecord({directiveIndex: new DirectiveIndex(0, 0), changeDetection: ON_PUSH});
var updateDirWithOnPushRecord =
BindingRecord.createForDirective(_parser.parseBinding('42', 'location'), 'a',
(o, v) => (<any>o).a = v, dirRecordWithOnPush);
bindingRecords = [updateDirWithOnPushRecord];
directiveRecords = [dirRecordWithOnPush];
} else {
bindingRecords = [];
directiveRecords = [];
}
return new ChangeDetectorDefinition('(empty id)', this._strategy, variableBindings,
bindingRecords, directiveRecords);
}
/**
* Map from test id to _ExpressionWithMode.
* Definitions in this map define conditions which allow testing various change detector modes.
*/
static availableDefinitions: StringMap<string, _ExpressionWithMode> = {
'emptyUsingDefaultStrategy': new _ExpressionWithMode(DEFAULT, false),
'emptyUsingOnPushStrategy': new _ExpressionWithMode(ON_PUSH, false),
'onPushRecordsUsingDefaultStrategy': new _ExpressionWithMode(DEFAULT, true)
};
}
/** /**
* The list of all test definitions this config supplies. * The list of all test definitions this config supplies.
* Items in this list that do not appear in other structures define tests with expressions * Items in this list that do not appear in other structures define tests with expressions
* equivalent to their ids. * equivalent to their ids.
*/ */
var _availableDefinitions = [ var _availableDefinitions = ListWrapper.concat(
[
'10', '10',
'"str"', '"str"',
'"a\n\nb"', '"a\n\nb"',
@ -121,53 +198,7 @@ var _availableDefinitions = [
'address?.toString()', 'address?.toString()',
'sayHi("Jim")', 'sayHi("Jim")',
'a()(99)', 'a()(99)',
'a.sayHi("Jim")', 'a.sayHi("Jim")'
'valueFromLocals',
'functionFromLocals',
'nestedLocals',
'fallbackLocals',
'contextNestedPropertyWithLocals',
'localPropertyWithSimilarContext'
];
class _ExpressionWithLocals {
constructor(public expression: string, public locals: Locals) {}
}
/**
* Map from test id to _ExpressionWithLocals.
* Tests in this map define an expression and local values which those expressions refer to.
*/
var _availableDefinitionsWithLocals = MapWrapper.createFromPairs([
[
'valueFromLocals',
new _ExpressionWithLocals('key',
new Locals(null, MapWrapper.createFromPairs([['key', 'value']])))
], ],
[ ListWrapper.concat(StringMapWrapper.keys(_ExpressionWithLocals.availableDefinitions),
'functionFromLocals', StringMapWrapper.keys(_ExpressionWithMode.availableDefinitions)));
new _ExpressionWithLocals(
'key()', new Locals(null, MapWrapper.createFromPairs([['key', () => 'value']])))
],
[
'nestedLocals',
new _ExpressionWithLocals(
'key', new Locals(new Locals(null, MapWrapper.createFromPairs([['key', 'value']])),
MapWrapper.create()))
],
[
'fallbackLocals',
new _ExpressionWithLocals('name',
new Locals(null, MapWrapper.createFromPairs([['key', 'value']])))
],
[
'contextNestedPropertyWithLocals',
new _ExpressionWithLocals('address.city',
new Locals(null, MapWrapper.createFromPairs([['city', 'MTV']])))
],
[
'localPropertyWithSimilarContext',
new _ExpressionWithLocals('city',
new Locals(null, MapWrapper.createFromPairs([['city', 'MTV']])))
]
]);