refactor(view_compiler): codegen DI and Queries
BREAKING CHANGE: - Renderer: * renderComponent method is removed form `Renderer`, only present on `RootRenderer` * Renderer.setDebugInfo is removed. Renderer.createElement / createText / createTemplateAnchor now take the DebugInfo directly. - Query semantics: * Queries don't work with dynamically loaded components. * e.g. for router-outlet: loaded components can't be queries via @ViewQuery, but router-outlet emits an event `activate` now that emits the activated component - Exception classes and the context inside changed (renamed fields) - DebugElement.attributes is an Object and not a Map in JS any more - ChangeDetectorGenConfig was renamed into CompilerConfig - AppViewManager.createEmbeddedViewInContainer / AppViewManager.createHostViewInContainer are removed, use the methods in ViewContainerRef instead - Change detection order changed: * 1. dirty check component inputs * 2. dirty check content children * 3. update render nodes Closes #6301 Closes #6567
This commit is contained in:
@ -1,427 +0,0 @@
|
||||
import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
BindingRecord,
|
||||
ChangeDetectorDefinition,
|
||||
DirectiveIndex,
|
||||
DirectiveRecord,
|
||||
Lexer,
|
||||
Locals,
|
||||
Parser,
|
||||
ChangeDetectorGenConfig
|
||||
} from 'angular2/src/core/change_detection/change_detection';
|
||||
import {reflector} from 'angular2/src/core/reflection/reflection';
|
||||
import {ReflectionCapabilities} from 'angular2/src/core/reflection/reflection_capabilities';
|
||||
|
||||
/*
|
||||
* This file defines `ChangeDetectorDefinition` objects which are used in the tests defined in
|
||||
* the change_detector_spec library. Please see that library for more information.
|
||||
*/
|
||||
|
||||
var _parser = new Parser(new Lexer());
|
||||
|
||||
function _getParser() {
|
||||
reflector.reflectionCapabilities = new ReflectionCapabilities();
|
||||
return _parser;
|
||||
}
|
||||
|
||||
function _createBindingRecords(expression: string): BindingRecord[] {
|
||||
var ast = _getParser().parseBinding(expression, 'location');
|
||||
return [BindingRecord.createForElementProperty(ast, 0, PROP_NAME)];
|
||||
}
|
||||
|
||||
function _createEventRecords(expression: string): BindingRecord[] {
|
||||
var eq = expression.indexOf("=");
|
||||
var eventName = expression.substring(1, eq - 1);
|
||||
var exp = expression.substring(eq + 2, expression.length - 1);
|
||||
var ast = _getParser().parseAction(exp, 'location');
|
||||
return [BindingRecord.createForEvent(ast, eventName, 0)];
|
||||
}
|
||||
|
||||
function _createHostEventRecords(expression: string,
|
||||
directiveRecord: DirectiveRecord): BindingRecord[] {
|
||||
var parts = expression.split("=");
|
||||
var eventName = parts[0].substring(1, parts[0].length - 1);
|
||||
var exp = parts[1].substring(1, parts[1].length - 1);
|
||||
|
||||
var ast = _getParser().parseAction(exp, 'location');
|
||||
return [BindingRecord.createForHostEvent(ast, eventName, directiveRecord)];
|
||||
}
|
||||
|
||||
function _convertLocalsToVariableBindings(locals: Locals): any[] {
|
||||
var variableBindings = [];
|
||||
var loc = locals;
|
||||
while (isPresent(loc) && isPresent(loc.current)) {
|
||||
loc.current.forEach((v, k) => variableBindings.push(k));
|
||||
loc = loc.parent;
|
||||
}
|
||||
return variableBindings;
|
||||
}
|
||||
|
||||
export const PROP_NAME = 'propName';
|
||||
|
||||
/**
|
||||
* In this case, we expect `id` and `expression` to be the same string.
|
||||
*/
|
||||
export function getDefinition(id: string): TestDefinition {
|
||||
var genConfig = new ChangeDetectorGenConfig(true, true, true);
|
||||
var testDef = null;
|
||||
if (StringMapWrapper.contains(_ExpressionWithLocals.availableDefinitions, id)) {
|
||||
let val = StringMapWrapper.get(_ExpressionWithLocals.availableDefinitions, id);
|
||||
let cdDef = val.createChangeDetectorDefinition();
|
||||
cdDef.id = id;
|
||||
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 (StringMapWrapper.contains(_DirectiveUpdating.availableDefinitions, id)) {
|
||||
let val = StringMapWrapper.get(_DirectiveUpdating.availableDefinitions, id);
|
||||
let cdDef = val.createChangeDetectorDefinition();
|
||||
cdDef.id = id;
|
||||
testDef = new TestDefinition(id, cdDef, null);
|
||||
|
||||
} else if (ListWrapper.indexOf(_availableDefinitions, id) >= 0) {
|
||||
var strategy = null;
|
||||
var variableBindings = [];
|
||||
var eventRecords = _createBindingRecords(id);
|
||||
var directiveRecords = [];
|
||||
let cdDef = new ChangeDetectorDefinition(id, strategy, variableBindings, eventRecords, [],
|
||||
directiveRecords, genConfig);
|
||||
testDef = new TestDefinition(id, cdDef, null);
|
||||
|
||||
} else if (ListWrapper.indexOf(_availableEventDefinitions, id) >= 0) {
|
||||
var eventRecords = _createEventRecords(id);
|
||||
let cdDef = new ChangeDetectorDefinition(id, null, [], [], eventRecords, [], genConfig);
|
||||
testDef = new TestDefinition(id, cdDef, null);
|
||||
|
||||
} else if (ListWrapper.indexOf(_availableHostEventDefinitions, id) >= 0) {
|
||||
var eventRecords = _createHostEventRecords(id, _DirectiveUpdating.basicRecords[0]);
|
||||
let cdDef = new ChangeDetectorDefinition(
|
||||
id, null, [], [], eventRecords,
|
||||
[_DirectiveUpdating.basicRecords[0], _DirectiveUpdating.basicRecords[1]], genConfig);
|
||||
testDef = new TestDefinition(id, cdDef, null);
|
||||
|
||||
} else if (id == "updateElementProduction") {
|
||||
var genConfig = new ChangeDetectorGenConfig(false, false, true);
|
||||
var records = _createBindingRecords("name");
|
||||
let cdDef = new ChangeDetectorDefinition(id, null, [], records, [], [], genConfig);
|
||||
testDef = new TestDefinition(id, cdDef, null);
|
||||
}
|
||||
|
||||
if (isBlank(testDef)) {
|
||||
throw `No ChangeDetectorDefinition for ${id} available. Please modify this file if necessary.`;
|
||||
}
|
||||
|
||||
return testDef;
|
||||
}
|
||||
|
||||
export class TestDefinition {
|
||||
constructor(public id: string, public cdDef: ChangeDetectorDefinition, public locals: Locals) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available ChangeDetectorDefinition objects. Used to pre-generate Dart
|
||||
* `ChangeDetector` classes.
|
||||
*/
|
||||
export function getAllDefinitions(): TestDefinition[] {
|
||||
var allDefs = _availableDefinitions;
|
||||
allDefs = ListWrapper.concat(allDefs,
|
||||
StringMapWrapper.keys(_ExpressionWithLocals.availableDefinitions));
|
||||
allDefs = allDefs.concat(StringMapWrapper.keys(_ExpressionWithMode.availableDefinitions));
|
||||
allDefs = allDefs.concat(StringMapWrapper.keys(_DirectiveUpdating.availableDefinitions));
|
||||
allDefs = allDefs.concat(_availableEventDefinitions);
|
||||
allDefs = allDefs.concat(_availableHostEventDefinitions);
|
||||
allDefs = allDefs.concat(["updateElementProduction"]);
|
||||
return allDefs.map(getDefinition);
|
||||
}
|
||||
|
||||
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 = [];
|
||||
var genConfig = new ChangeDetectorGenConfig(true, true, true);
|
||||
return new ChangeDetectorDefinition('(empty id)', strategy, variableBindings, bindingRecords,
|
||||
[], directiveRecords, genConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map from test id to _ExpressionWithLocals.
|
||||
* Tests in this map define an expression and local values which those expressions refer to.
|
||||
*/
|
||||
static availableDefinitions: {[key: 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']])), new Map())),
|
||||
'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: ChangeDetectionStrategy, private _withRecords: boolean,
|
||||
private _withEvents: boolean) {}
|
||||
|
||||
createChangeDetectorDefinition(): ChangeDetectorDefinition {
|
||||
var variableBindings = [];
|
||||
var bindingRecords = [];
|
||||
var directiveRecords = [];
|
||||
var eventRecords = [];
|
||||
|
||||
var dirRecordWithDefault = new DirectiveRecord({
|
||||
directiveIndex: new DirectiveIndex(0, 0),
|
||||
changeDetection: ChangeDetectionStrategy.Default
|
||||
});
|
||||
var dirRecordWithOnPush = new DirectiveRecord({
|
||||
directiveIndex: new DirectiveIndex(0, 1),
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
});
|
||||
|
||||
if (this._withRecords) {
|
||||
var updateDirWithOnDefaultRecord =
|
||||
BindingRecord.createForDirective(_getParser().parseBinding('42', 'location'), 'a',
|
||||
(o, v) => (<any>o).a = v, dirRecordWithDefault);
|
||||
var updateDirWithOnPushRecord =
|
||||
BindingRecord.createForDirective(_getParser().parseBinding('42', 'location'), 'a',
|
||||
(o, v) => (<any>o).a = v, dirRecordWithOnPush);
|
||||
|
||||
directiveRecords = [dirRecordWithDefault, dirRecordWithOnPush];
|
||||
bindingRecords = [updateDirWithOnDefaultRecord, updateDirWithOnPushRecord];
|
||||
}
|
||||
|
||||
if (this._withEvents) {
|
||||
directiveRecords = [dirRecordWithDefault, dirRecordWithOnPush];
|
||||
eventRecords =
|
||||
ListWrapper.concat(_createEventRecords("(event)='false'"),
|
||||
_createHostEventRecords("(host-event)='false'", dirRecordWithOnPush))
|
||||
}
|
||||
|
||||
var genConfig = new ChangeDetectorGenConfig(true, true, true);
|
||||
|
||||
return new ChangeDetectorDefinition('(empty id)', this._strategy, variableBindings,
|
||||
bindingRecords, eventRecords, directiveRecords, genConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map from test id to _ExpressionWithMode.
|
||||
* Definitions in this map define conditions which allow testing various change detector modes.
|
||||
*/
|
||||
static availableDefinitions: {[key: string]: _ExpressionWithMode} = {
|
||||
'emptyUsingDefaultStrategy':
|
||||
new _ExpressionWithMode(ChangeDetectionStrategy.Default, false, false),
|
||||
'emptyUsingOnPushStrategy':
|
||||
new _ExpressionWithMode(ChangeDetectionStrategy.OnPush, false, false),
|
||||
'onPushRecordsUsingDefaultStrategy':
|
||||
new _ExpressionWithMode(ChangeDetectionStrategy.Default, true, false),
|
||||
'onPushWithEvent': new _ExpressionWithMode(ChangeDetectionStrategy.OnPush, false, true),
|
||||
'onPushWithHostEvent': new _ExpressionWithMode(ChangeDetectionStrategy.OnPush, false, true)
|
||||
};
|
||||
}
|
||||
|
||||
class _DirectiveUpdating {
|
||||
constructor(private _bindingRecords: BindingRecord[],
|
||||
private _directiveRecords: DirectiveRecord[]) {}
|
||||
|
||||
createChangeDetectorDefinition(): ChangeDetectorDefinition {
|
||||
var strategy = null;
|
||||
var variableBindings = [];
|
||||
var genConfig = new ChangeDetectorGenConfig(true, true, true);
|
||||
|
||||
return new ChangeDetectorDefinition('(empty id)', strategy, variableBindings,
|
||||
this._bindingRecords, [], this._directiveRecords,
|
||||
genConfig);
|
||||
}
|
||||
|
||||
static updateA(expression: string, dirRecord): BindingRecord {
|
||||
return BindingRecord.createForDirective(_getParser().parseBinding(expression, 'location'), 'a',
|
||||
(o, v) => (<any>o).a = v, dirRecord);
|
||||
}
|
||||
|
||||
static updateB(expression: string, dirRecord): BindingRecord {
|
||||
return BindingRecord.createForDirective(_getParser().parseBinding(expression, 'location'), 'b',
|
||||
(o, v) => (<any>o).b = v, dirRecord);
|
||||
}
|
||||
|
||||
static basicRecords: DirectiveRecord[] = [
|
||||
new DirectiveRecord({
|
||||
directiveIndex: new DirectiveIndex(0, 0),
|
||||
callOnChanges: true,
|
||||
callDoCheck: true,
|
||||
callOnInit: true,
|
||||
callAfterContentInit: true,
|
||||
callAfterContentChecked: true,
|
||||
callAfterViewInit: true,
|
||||
callAfterViewChecked: true,
|
||||
callOnDestroy: true,
|
||||
outputs: [['eventEmitter', 'host-event']]
|
||||
}),
|
||||
new DirectiveRecord({
|
||||
directiveIndex: new DirectiveIndex(0, 1),
|
||||
callOnChanges: true,
|
||||
callDoCheck: true,
|
||||
callOnInit: true,
|
||||
callAfterContentInit: true,
|
||||
callAfterContentChecked: true,
|
||||
callAfterViewInit: true,
|
||||
callAfterViewChecked: true,
|
||||
callOnDestroy: true,
|
||||
outputs: [['eventEmitter', 'host-event']]
|
||||
})
|
||||
];
|
||||
|
||||
static recordNoCallbacks = new DirectiveRecord({
|
||||
directiveIndex: new DirectiveIndex(0, 0),
|
||||
callOnChanges: false,
|
||||
callDoCheck: false,
|
||||
callOnInit: false,
|
||||
callAfterContentInit: false,
|
||||
callAfterContentChecked: false,
|
||||
callAfterViewInit: false,
|
||||
callAfterViewChecked: false
|
||||
});
|
||||
|
||||
/**
|
||||
* Map from test id to _DirectiveUpdating.
|
||||
* Definitions in this map define definitions which allow testing directive updating.
|
||||
*/
|
||||
static availableDefinitions: {[key: string]: _DirectiveUpdating} = {
|
||||
'directNoDispatcher': new _DirectiveUpdating(
|
||||
[_DirectiveUpdating.updateA('42', _DirectiveUpdating.basicRecords[0])],
|
||||
[_DirectiveUpdating.basicRecords[0]]),
|
||||
'groupChanges':
|
||||
new _DirectiveUpdating(
|
||||
[
|
||||
_DirectiveUpdating.updateA('1', _DirectiveUpdating.basicRecords[0]),
|
||||
_DirectiveUpdating.updateB('2', _DirectiveUpdating.basicRecords[0]),
|
||||
BindingRecord.createDirectiveOnChanges(_DirectiveUpdating.basicRecords[0]),
|
||||
_DirectiveUpdating.updateA('3', _DirectiveUpdating.basicRecords[1]),
|
||||
BindingRecord.createDirectiveOnChanges(_DirectiveUpdating.basicRecords[1])
|
||||
],
|
||||
[_DirectiveUpdating.basicRecords[0], _DirectiveUpdating.basicRecords[1]]),
|
||||
'directiveDoCheck': new _DirectiveUpdating(
|
||||
[BindingRecord.createDirectiveDoCheck(_DirectiveUpdating.basicRecords[0])],
|
||||
[_DirectiveUpdating.basicRecords[0]]),
|
||||
'directiveOnInit': new _DirectiveUpdating(
|
||||
[BindingRecord.createDirectiveOnInit(_DirectiveUpdating.basicRecords[0])],
|
||||
[_DirectiveUpdating.basicRecords[0]]),
|
||||
'emptyWithDirectiveRecords': new _DirectiveUpdating(
|
||||
[], [_DirectiveUpdating.basicRecords[0], _DirectiveUpdating.basicRecords[1]]),
|
||||
'noCallbacks': new _DirectiveUpdating(
|
||||
[_DirectiveUpdating.updateA('1', _DirectiveUpdating.recordNoCallbacks)],
|
||||
[_DirectiveUpdating.recordNoCallbacks]),
|
||||
'readingDirectives':
|
||||
new _DirectiveUpdating(
|
||||
[
|
||||
BindingRecord.createForHostProperty(
|
||||
new DirectiveIndex(0, 0), _getParser().parseBinding('a', 'location'), PROP_NAME)
|
||||
],
|
||||
[_DirectiveUpdating.basicRecords[0]]),
|
||||
'interpolation':
|
||||
new _DirectiveUpdating(
|
||||
[
|
||||
BindingRecord.createForElementProperty(
|
||||
_getParser().parseInterpolation('B{{a}}A', 'location'), 0, PROP_NAME)
|
||||
],
|
||||
[])
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of all test definitions this config supplies.
|
||||
* Items in this list that do not appear in other structures define tests with expressions
|
||||
* equivalent to their ids.
|
||||
*/
|
||||
var _availableDefinitions = [
|
||||
'"$"',
|
||||
'10',
|
||||
'"str"',
|
||||
'"a\n\nb"',
|
||||
'10 + 2',
|
||||
'10 - 2',
|
||||
'10 * 2',
|
||||
'10 / 2',
|
||||
'11 % 2',
|
||||
'1 == 1',
|
||||
'1 != 1',
|
||||
'1 == true',
|
||||
'1 === 1',
|
||||
'1 !== 1',
|
||||
'1 === true',
|
||||
'1 < 2',
|
||||
'2 < 1',
|
||||
'1 > 2',
|
||||
'2 > 1',
|
||||
'1 <= 2',
|
||||
'2 <= 2',
|
||||
'2 <= 1',
|
||||
'2 >= 1',
|
||||
'2 >= 2',
|
||||
'1 >= 2',
|
||||
'true && true',
|
||||
'true && false',
|
||||
'true || false',
|
||||
'false || false',
|
||||
'!true',
|
||||
'!!true',
|
||||
'1 < 2 ? 1 : 2',
|
||||
'1 > 2 ? 1 : 2',
|
||||
'["foo", "bar"][0]',
|
||||
'{"foo": "bar"}["foo"]',
|
||||
'name',
|
||||
'[1, 2]',
|
||||
'[1, a]',
|
||||
'{z: 1}',
|
||||
'{z: a}',
|
||||
'name | pipe',
|
||||
'(name | pipe).length',
|
||||
"name | pipe:'one':address.city",
|
||||
"name | pipe:'a':'b' | pipe:0:1:2",
|
||||
'value',
|
||||
'a',
|
||||
'address.city',
|
||||
'address?.city',
|
||||
'address?.toString()',
|
||||
'sayHi("Jim")',
|
||||
'a()(99)',
|
||||
'a.sayHi("Jim")',
|
||||
'passThrough([12])',
|
||||
'invalidFn(1)',
|
||||
'age',
|
||||
'true ? city : zipcode',
|
||||
'false ? city : zipcode',
|
||||
'getTrue() && getTrue()',
|
||||
'getFalse() && getTrue()',
|
||||
'getFalse() || getFalse()',
|
||||
'getTrue() || getFalse()',
|
||||
'name == "Victor" ? (true ? address.city : address.zipcode) : address.zipcode'
|
||||
];
|
||||
|
||||
var _availableEventDefinitions = [
|
||||
'(event)="onEvent(\$event)"',
|
||||
'(event)="b=a=\$event"',
|
||||
'(event)="a[0]=\$event"',
|
||||
// '(event)="\$event=1"',
|
||||
'(event)="a=a+1; a=a+1;"',
|
||||
'(event)="true; false"',
|
||||
'(event)="false"',
|
||||
'(event)="true"',
|
||||
'(event)="true ? a = a + 1 : a = a + 1"',
|
||||
];
|
||||
|
||||
var _availableHostEventDefinitions = ['(host-event)="onEvent(\$event)"'];
|
@ -1,31 +0,0 @@
|
||||
import {
|
||||
ddescribe,
|
||||
describe,
|
||||
it,
|
||||
iit,
|
||||
xit,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
tick,
|
||||
fakeAsync
|
||||
} from 'angular2/testing_internal';
|
||||
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
ChangeDetectorRef_
|
||||
} from 'angular2/src/core/change_detection/change_detector_ref';
|
||||
import {SpyChangeDetector} from '../spies';
|
||||
|
||||
|
||||
export function main() {
|
||||
describe('ChangeDetectorRef', () => {
|
||||
it('should delegate detectChanges()', () => {
|
||||
var changeDetector = new SpyChangeDetector();
|
||||
changeDetector.spy('detectChanges');
|
||||
var changeDetectorRef = new ChangeDetectorRef_(<any>changeDetector);
|
||||
changeDetectorRef.detectChanges();
|
||||
expect(changeDetector.spy('detectChanges')).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -9,52 +9,51 @@ import {
|
||||
afterEach
|
||||
} from 'angular2/testing_internal';
|
||||
|
||||
import {ChangeDetectionUtil} from 'angular2/src/core/change_detection/change_detection_util';
|
||||
import {devModeEqual} from 'angular2/src/core/change_detection/change_detection_util';
|
||||
|
||||
export function main() {
|
||||
describe("ChangeDetectionUtil", () => {
|
||||
describe("devModeEqual", () => {
|
||||
it("should do the deep comparison of iterables", () => {
|
||||
expect(ChangeDetectionUtil.devModeEqual([['one']], [['one']])).toBe(true);
|
||||
expect(ChangeDetectionUtil.devModeEqual(['one'], ['one', 'two'])).toBe(false);
|
||||
expect(ChangeDetectionUtil.devModeEqual(['one', 'two'], ['one'])).toBe(false);
|
||||
expect(ChangeDetectionUtil.devModeEqual(['one'], 'one')).toBe(false);
|
||||
expect(ChangeDetectionUtil.devModeEqual(['one'], new Object())).toBe(false);
|
||||
expect(ChangeDetectionUtil.devModeEqual('one', ['one'])).toBe(false);
|
||||
expect(ChangeDetectionUtil.devModeEqual(new Object(), ['one'])).toBe(false);
|
||||
expect(devModeEqual([['one']], [['one']])).toBe(true);
|
||||
expect(devModeEqual(['one'], ['one', 'two'])).toBe(false);
|
||||
expect(devModeEqual(['one', 'two'], ['one'])).toBe(false);
|
||||
expect(devModeEqual(['one'], 'one')).toBe(false);
|
||||
expect(devModeEqual(['one'], new Object())).toBe(false);
|
||||
expect(devModeEqual('one', ['one'])).toBe(false);
|
||||
expect(devModeEqual(new Object(), ['one'])).toBe(false);
|
||||
});
|
||||
|
||||
it("should compare primitive numbers", () => {
|
||||
expect(ChangeDetectionUtil.devModeEqual(1, 1)).toBe(true);
|
||||
expect(ChangeDetectionUtil.devModeEqual(1, 2)).toBe(false);
|
||||
expect(ChangeDetectionUtil.devModeEqual(new Object(), 2)).toBe(false);
|
||||
expect(ChangeDetectionUtil.devModeEqual(1, new Object())).toBe(false);
|
||||
expect(devModeEqual(1, 1)).toBe(true);
|
||||
expect(devModeEqual(1, 2)).toBe(false);
|
||||
expect(devModeEqual(new Object(), 2)).toBe(false);
|
||||
expect(devModeEqual(1, new Object())).toBe(false);
|
||||
});
|
||||
|
||||
it("should compare primitive strings", () => {
|
||||
expect(ChangeDetectionUtil.devModeEqual('one', 'one')).toBe(true);
|
||||
expect(ChangeDetectionUtil.devModeEqual('one', 'two')).toBe(false);
|
||||
expect(ChangeDetectionUtil.devModeEqual(new Object(), 'one')).toBe(false);
|
||||
expect(ChangeDetectionUtil.devModeEqual('one', new Object())).toBe(false);
|
||||
expect(devModeEqual('one', 'one')).toBe(true);
|
||||
expect(devModeEqual('one', 'two')).toBe(false);
|
||||
expect(devModeEqual(new Object(), 'one')).toBe(false);
|
||||
expect(devModeEqual('one', new Object())).toBe(false);
|
||||
});
|
||||
|
||||
it("should compare primitive booleans", () => {
|
||||
expect(ChangeDetectionUtil.devModeEqual(true, true)).toBe(true);
|
||||
expect(ChangeDetectionUtil.devModeEqual(true, false)).toBe(false);
|
||||
expect(ChangeDetectionUtil.devModeEqual(new Object(), true)).toBe(false);
|
||||
expect(ChangeDetectionUtil.devModeEqual(true, new Object())).toBe(false);
|
||||
expect(devModeEqual(true, true)).toBe(true);
|
||||
expect(devModeEqual(true, false)).toBe(false);
|
||||
expect(devModeEqual(new Object(), true)).toBe(false);
|
||||
expect(devModeEqual(true, new Object())).toBe(false);
|
||||
});
|
||||
|
||||
it("should compare null", () => {
|
||||
expect(ChangeDetectionUtil.devModeEqual(null, null)).toBe(true);
|
||||
expect(ChangeDetectionUtil.devModeEqual(null, 1)).toBe(false);
|
||||
expect(ChangeDetectionUtil.devModeEqual(new Object(), null)).toBe(false);
|
||||
expect(ChangeDetectionUtil.devModeEqual(null, new Object())).toBe(false);
|
||||
expect(devModeEqual(null, null)).toBe(true);
|
||||
expect(devModeEqual(null, 1)).toBe(false);
|
||||
expect(devModeEqual(new Object(), null)).toBe(false);
|
||||
expect(devModeEqual(null, new Object())).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true for other objects", () => {
|
||||
expect(ChangeDetectionUtil.devModeEqual(new Object(), new Object())).toBe(true);
|
||||
});
|
||||
it("should return true for other objects",
|
||||
() => { expect(devModeEqual(new Object(), new Object())).toBe(true); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -1,235 +0,0 @@
|
||||
import {
|
||||
ddescribe,
|
||||
describe,
|
||||
it,
|
||||
iit,
|
||||
xit,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach
|
||||
} from 'angular2/testing_internal';
|
||||
import {isBlank} from 'angular2/src/facade/lang';
|
||||
|
||||
import {coalesce} from 'angular2/src/core/change_detection/coalesce';
|
||||
import {RecordType, ProtoRecord} from 'angular2/src/core/change_detection/proto_record';
|
||||
import {DirectiveIndex} from 'angular2/src/core/change_detection/directive_record';
|
||||
|
||||
export function main() {
|
||||
function r(funcOrValue, args, contextIndex, selfIndex,
|
||||
{lastInBinding, mode, name, directiveIndex, argumentToPureFunction, fixedArgs}: {
|
||||
lastInBinding?: any,
|
||||
mode?: any,
|
||||
name?: any,
|
||||
directiveIndex?: any,
|
||||
argumentToPureFunction?: boolean,
|
||||
fixedArgs?: any[]
|
||||
} = {}) {
|
||||
if (isBlank(lastInBinding)) lastInBinding = false;
|
||||
if (isBlank(mode)) mode = RecordType.PropertyRead;
|
||||
if (isBlank(name)) name = "name";
|
||||
if (isBlank(directiveIndex)) directiveIndex = null;
|
||||
if (isBlank(argumentToPureFunction)) argumentToPureFunction = false;
|
||||
if (isBlank(fixedArgs)) fixedArgs = null;
|
||||
|
||||
return new ProtoRecord(mode, name, funcOrValue, args, fixedArgs, contextIndex, directiveIndex,
|
||||
selfIndex, null, lastInBinding, false, argumentToPureFunction, false, 0);
|
||||
}
|
||||
|
||||
describe("change detection - coalesce", () => {
|
||||
it("should work with an empty list", () => { expect(coalesce([])).toEqual([]); });
|
||||
|
||||
it("should remove non-terminal duplicate records and update the context indices referencing them",
|
||||
() => {
|
||||
var rs = coalesce(
|
||||
[r("user", [], 0, 1), r("first", [], 1, 2), r("user", [], 0, 3), r("last", [], 3, 4)]);
|
||||
|
||||
expect(rs).toEqual([r("user", [], 0, 1), r("first", [], 1, 2), r("last", [], 1, 3)]);
|
||||
});
|
||||
|
||||
it("should update indices of other records", () => {
|
||||
var rs = coalesce(
|
||||
[r("dup", [], 0, 1), r("dup", [], 0, 2), r("user", [], 0, 3), r("first", [3], 3, 4)]);
|
||||
|
||||
expect(rs).toEqual([r("dup", [], 0, 1), r("user", [], 0, 2), r("first", [2], 2, 3)]);
|
||||
});
|
||||
|
||||
it("should remove non-terminal duplicate records and update the args indices referencing them",
|
||||
() => {
|
||||
var rs = coalesce([
|
||||
r("user1", [], 0, 1),
|
||||
r("user2", [], 0, 2),
|
||||
r("hi", [1], 0, 3),
|
||||
r("hi", [1], 0, 4),
|
||||
r("hi", [2], 0, 5)
|
||||
]);
|
||||
|
||||
expect(rs).toEqual(
|
||||
[r("user1", [], 0, 1), r("user2", [], 0, 2), r("hi", [1], 0, 3), r("hi", [2], 0, 4)]);
|
||||
});
|
||||
|
||||
it("should replace duplicate terminal records with self records", () => {
|
||||
var rs = coalesce(
|
||||
[r("user", [], 0, 1, {lastInBinding: true}), r("user", [], 0, 2, {lastInBinding: true})]);
|
||||
|
||||
expect(rs[1]).toEqual(new ProtoRecord(RecordType.Self, "self", null, [], null, 1, null, 2,
|
||||
null, true, false, false, false, 0));
|
||||
});
|
||||
|
||||
it("should set referencedBySelf", () => {
|
||||
var rs = coalesce(
|
||||
[r("user", [], 0, 1, {lastInBinding: true}), r("user", [], 0, 2, {lastInBinding: true})]);
|
||||
|
||||
expect(rs[0].referencedBySelf).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should not coalesce directive lifecycle records", () => {
|
||||
var rs = coalesce([
|
||||
r("ngDoCheck", [], 0, 1, {mode: RecordType.DirectiveLifecycle}),
|
||||
r("ngDoCheck", [], 0, 1, {mode: RecordType.DirectiveLifecycle})
|
||||
]);
|
||||
|
||||
expect(rs.length).toEqual(2);
|
||||
});
|
||||
|
||||
it("should not coalesce protos with different names but same value", () => {
|
||||
var nullFunc = () => {};
|
||||
var rs = coalesce([
|
||||
r(nullFunc, [], 0, 1, {name: "foo"}),
|
||||
r(nullFunc, [], 0, 1, {name: "bar"}),
|
||||
]);
|
||||
expect(rs.length).toEqual(2);
|
||||
});
|
||||
|
||||
it("should not coalesce protos with the same context index but different directive indices",
|
||||
() => {
|
||||
var nullFunc = () => {};
|
||||
var rs = coalesce([
|
||||
r(nullFunc, [], 0, 1, {directiveIndex: new DirectiveIndex(0, 0)}),
|
||||
r(nullFunc, [], 0, 1, {directiveIndex: new DirectiveIndex(0, 1)}),
|
||||
r(nullFunc, [], 0, 1, {directiveIndex: new DirectiveIndex(1, 0)}),
|
||||
r(nullFunc, [], 0, 1, {directiveIndex: null}),
|
||||
]);
|
||||
expect(rs.length).toEqual(4);
|
||||
});
|
||||
|
||||
it('should preserve the argumentToPureFunction property', () => {
|
||||
var rs = coalesce([
|
||||
r("user", [], 0, 1),
|
||||
r("user", [], 0, 2, {argumentToPureFunction: true}),
|
||||
r("user", [], 0, 3),
|
||||
r("name", [], 3, 4)
|
||||
]);
|
||||
expect(rs)
|
||||
.toEqual([r("user", [], 0, 1, {argumentToPureFunction: true}), r("name", [], 1, 2)]);
|
||||
});
|
||||
|
||||
it('should preserve the argumentToPureFunction property (the original record)', () => {
|
||||
var rs = coalesce([
|
||||
r("user", [], 0, 1, {argumentToPureFunction: true}),
|
||||
r("user", [], 0, 2),
|
||||
r("name", [], 2, 3)
|
||||
]);
|
||||
expect(rs)
|
||||
.toEqual([r("user", [], 0, 1, {argumentToPureFunction: true}), r("name", [], 1, 2)]);
|
||||
});
|
||||
|
||||
describe('short-circuit', () => {
|
||||
it('should not use short-circuitable records', () => {
|
||||
var records = [
|
||||
r("sknot", [], 0, 1, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [3]}),
|
||||
r("a", [], 0, 2),
|
||||
r("sk", [], 0, 3, {mode: RecordType.SkipRecords, fixedArgs: [4]}),
|
||||
r("b", [], 0, 4),
|
||||
r("cond", [2, 4], 0, 5),
|
||||
r("a", [], 0, 6),
|
||||
r("b", [], 0, 7),
|
||||
];
|
||||
|
||||
expect(coalesce(records)).toEqual(records);
|
||||
});
|
||||
|
||||
it('should not use short-circuitable records from nested short-circuits', () => {
|
||||
var records = [
|
||||
r("sknot outer", [], 0, 1, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [7]}),
|
||||
r("sknot inner", [], 0, 2, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [4]}),
|
||||
r("a", [], 0, 3),
|
||||
r("sk inner", [], 0, 4, {mode: RecordType.SkipRecords, fixedArgs: [5]}),
|
||||
r("b", [], 0, 5),
|
||||
r("cond-inner", [3, 5], 0, 6),
|
||||
r("sk outer", [], 0, 7, {mode: RecordType.SkipRecords, fixedArgs: [8]}),
|
||||
r("c", [], 0, 8),
|
||||
r("cond-outer", [6, 8], 0, 9),
|
||||
r("a", [], 0, 10),
|
||||
r("b", [], 0, 11),
|
||||
r("c", [], 0, 12),
|
||||
];
|
||||
|
||||
expect(coalesce(records)).toEqual(records);
|
||||
});
|
||||
|
||||
it('should collapse the true branch', () => {
|
||||
var rs = coalesce([
|
||||
r("a", [], 0, 1),
|
||||
r("sknot", [], 0, 2, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [4]}),
|
||||
r("a", [], 0, 3),
|
||||
r("sk", [], 0, 4, {mode: RecordType.SkipRecords, fixedArgs: [6]}),
|
||||
r("a", [], 0, 5),
|
||||
r("b", [], 5, 6),
|
||||
r("cond", [3, 6], 0, 7),
|
||||
]);
|
||||
|
||||
expect(rs).toEqual([
|
||||
r("a", [], 0, 1),
|
||||
r("sknot", [], 0, 2, {mode: RecordType.SkipRecordsIf, fixedArgs: [3]}),
|
||||
r("b", [], 1, 3),
|
||||
r("cond", [1, 3], 0, 4),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should collapse the false branch', () => {
|
||||
var rs = coalesce([
|
||||
r("a", [], 0, 1),
|
||||
r("sknot", [], 0, 2, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [5]}),
|
||||
r("a", [], 0, 3),
|
||||
r("b", [], 3, 4),
|
||||
r("sk", [], 0, 5, {mode: RecordType.SkipRecords, fixedArgs: [6]}),
|
||||
r("a", [], 0, 6),
|
||||
r("cond", [4, 6], 0, 7),
|
||||
]);
|
||||
|
||||
expect(rs).toEqual([
|
||||
r("a", [], 0, 1),
|
||||
r("sknot", [], 0, 2, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [3]}),
|
||||
r("b", [], 1, 3),
|
||||
r("cond", [3, 1], 0, 4),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should optimize skips', () => {
|
||||
var rs = coalesce([
|
||||
// skipIfNot(1) + skip(N) -> skipIf(+N)
|
||||
r("sknot", [], 0, 1, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [2]}),
|
||||
r("sk", [], 0, 2, {mode: RecordType.SkipRecords, fixedArgs: [3]}),
|
||||
r("a", [], 0, 3),
|
||||
// skipIf(1) + skip(N) -> skipIfNot(N)
|
||||
r("skif", [], 0, 4, {mode: RecordType.SkipRecordsIf, fixedArgs: [5]}),
|
||||
r("sk", [], 0, 5, {mode: RecordType.SkipRecords, fixedArgs: [6]}),
|
||||
r("b", [], 0, 6),
|
||||
// remove empty skips
|
||||
r("sknot", [], 0, 7, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [7]}),
|
||||
r("skif", [], 0, 8, {mode: RecordType.SkipRecordsIf, fixedArgs: [8]}),
|
||||
r("sk", [], 0, 9, {mode: RecordType.SkipRecords, fixedArgs: [9]}),
|
||||
r("end", [], 0, 10),
|
||||
]);
|
||||
|
||||
expect(rs).toEqual([
|
||||
r("sknot", [], 0, 1, {mode: RecordType.SkipRecordsIf, fixedArgs: [2]}),
|
||||
r("a", [], 0, 2),
|
||||
r("skif", [], 0, 3, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [4]}),
|
||||
r("b", [], 0, 4),
|
||||
r("end", [], 0, 5),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
// Ignore me, needed to support Angular 2 Dart.
|
||||
// See ../change_detector_spec for more details.
|
||||
|
||||
export function getFactoryById(id: string) {
|
||||
return null;
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
library angular2.test.core.change_detection.generator;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dart_style/dart_style.dart';
|
||||
import 'package:angular2/src/transform/template_compiler/change_detector_codegen.dart';
|
||||
import '../change_detector_config.dart';
|
||||
|
||||
/// This tool consumes pre-defined `ChangeDetectorDefinition` objects and
|
||||
/// outputs code defining `AbstractChangeDetector` implementations corresponding
|
||||
/// to those definitions. These are used by the tests in
|
||||
/// ../change_detector_spec. Please see that library for more details.
|
||||
void main(List<String> args) {
|
||||
var buf = new StringBuffer('var $_MAP_NAME = {');
|
||||
var codegen = new Codegen();
|
||||
var allDefs = getAllDefinitions();
|
||||
for (var i = 0; i < allDefs.length; ++i) {
|
||||
var className = 'ChangeDetector${i}';
|
||||
codegen.generate('dynamic', className, allDefs[i].cdDef);
|
||||
if (i > 0) {
|
||||
buf.write(',');
|
||||
}
|
||||
buf.write(" '''${_escape(allDefs[i].cdDef.id)}''': "
|
||||
"$className.$CHANGE_DETECTOR_FACTORY_METHOD");
|
||||
}
|
||||
buf.write('};');
|
||||
print(new DartFormatter().format('''
|
||||
library dart_gen_change_detectors;
|
||||
|
||||
${codegen.imports}
|
||||
|
||||
$codegen
|
||||
$buf
|
||||
|
||||
getFactoryById(String id) => $_MAP_NAME[id];
|
||||
'''));
|
||||
}
|
||||
|
||||
String _escape(String id) => id.replaceAll(r'$', r'\$');
|
||||
|
||||
const _MAP_NAME = '_idToProtoMap';
|
@ -1,249 +0,0 @@
|
||||
import {ddescribe, describe, it, expect} from 'angular2/testing_internal';
|
||||
|
||||
import {Lexer, Token} from 'angular2/src/core/change_detection/parser/lexer';
|
||||
|
||||
import {StringWrapper} from "angular2/src/facade/lang";
|
||||
|
||||
function lex(text: string): any[] {
|
||||
return new Lexer().tokenize(text);
|
||||
}
|
||||
|
||||
function expectToken(token, index) {
|
||||
expect(token instanceof Token).toBe(true);
|
||||
expect(token.index).toEqual(index);
|
||||
}
|
||||
|
||||
function expectCharacterToken(token, index, character) {
|
||||
expect(character.length).toBe(1);
|
||||
expectToken(token, index);
|
||||
expect(token.isCharacter(StringWrapper.charCodeAt(character, 0))).toBe(true);
|
||||
}
|
||||
|
||||
function expectOperatorToken(token, index, operator) {
|
||||
expectToken(token, index);
|
||||
expect(token.isOperator(operator)).toBe(true);
|
||||
}
|
||||
|
||||
function expectNumberToken(token, index, n) {
|
||||
expectToken(token, index);
|
||||
expect(token.isNumber()).toBe(true);
|
||||
expect(token.toNumber()).toEqual(n);
|
||||
}
|
||||
|
||||
function expectStringToken(token, index, str) {
|
||||
expectToken(token, index);
|
||||
expect(token.isString()).toBe(true);
|
||||
expect(token.toString()).toEqual(str);
|
||||
}
|
||||
|
||||
function expectIdentifierToken(token, index, identifier) {
|
||||
expectToken(token, index);
|
||||
expect(token.isIdentifier()).toBe(true);
|
||||
expect(token.toString()).toEqual(identifier);
|
||||
}
|
||||
|
||||
function expectKeywordToken(token, index, keyword) {
|
||||
expectToken(token, index);
|
||||
expect(token.isKeyword()).toBe(true);
|
||||
expect(token.toString()).toEqual(keyword);
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('lexer', function() {
|
||||
describe('token', function() {
|
||||
it('should tokenize a simple identifier', function() {
|
||||
var tokens: number[] = lex("j");
|
||||
expect(tokens.length).toEqual(1);
|
||||
expectIdentifierToken(tokens[0], 0, 'j');
|
||||
});
|
||||
|
||||
it('should tokenize a dotted identifier', function() {
|
||||
var tokens: number[] = lex("j.k");
|
||||
expect(tokens.length).toEqual(3);
|
||||
expectIdentifierToken(tokens[0], 0, 'j');
|
||||
expectCharacterToken(tokens[1], 1, '.');
|
||||
expectIdentifierToken(tokens[2], 2, 'k');
|
||||
});
|
||||
|
||||
it('should tokenize an operator', function() {
|
||||
var tokens: number[] = lex("j-k");
|
||||
expect(tokens.length).toEqual(3);
|
||||
expectOperatorToken(tokens[1], 1, '-');
|
||||
});
|
||||
|
||||
it('should tokenize an indexed operator', function() {
|
||||
var tokens: number[] = lex("j[k]");
|
||||
expect(tokens.length).toEqual(4);
|
||||
expectCharacterToken(tokens[1], 1, "[");
|
||||
expectCharacterToken(tokens[3], 3, "]");
|
||||
});
|
||||
|
||||
it('should tokenize numbers', function() {
|
||||
var tokens: number[] = lex("88");
|
||||
expect(tokens.length).toEqual(1);
|
||||
expectNumberToken(tokens[0], 0, 88);
|
||||
});
|
||||
|
||||
it('should tokenize numbers within index ops',
|
||||
function() { expectNumberToken(lex("a[22]")[2], 2, 22); });
|
||||
|
||||
it('should tokenize simple quoted strings',
|
||||
function() { expectStringToken(lex('"a"')[0], 0, "a"); });
|
||||
|
||||
it('should tokenize quoted strings with escaped quotes',
|
||||
function() { expectStringToken(lex('"a\\""')[0], 0, 'a"'); });
|
||||
|
||||
it('should tokenize a string', function() {
|
||||
var tokens: Token[] = lex("j-a.bc[22]+1.3|f:'a\\\'c':\"d\\\"e\"");
|
||||
expectIdentifierToken(tokens[0], 0, 'j');
|
||||
expectOperatorToken(tokens[1], 1, '-');
|
||||
expectIdentifierToken(tokens[2], 2, 'a');
|
||||
expectCharacterToken(tokens[3], 3, '.');
|
||||
expectIdentifierToken(tokens[4], 4, 'bc');
|
||||
expectCharacterToken(tokens[5], 6, '[');
|
||||
expectNumberToken(tokens[6], 7, 22);
|
||||
expectCharacterToken(tokens[7], 9, ']');
|
||||
expectOperatorToken(tokens[8], 10, '+');
|
||||
expectNumberToken(tokens[9], 11, 1.3);
|
||||
expectOperatorToken(tokens[10], 14, '|');
|
||||
expectIdentifierToken(tokens[11], 15, 'f');
|
||||
expectCharacterToken(tokens[12], 16, ':');
|
||||
expectStringToken(tokens[13], 17, "a'c");
|
||||
expectCharacterToken(tokens[14], 23, ':');
|
||||
expectStringToken(tokens[15], 24, 'd"e');
|
||||
});
|
||||
|
||||
it('should tokenize undefined', function() {
|
||||
var tokens: Token[] = lex("undefined");
|
||||
expectKeywordToken(tokens[0], 0, "undefined");
|
||||
expect(tokens[0].isKeywordUndefined()).toBe(true);
|
||||
});
|
||||
|
||||
it('should ignore whitespace', function() {
|
||||
var tokens: Token[] = lex("a \t \n \r b");
|
||||
expectIdentifierToken(tokens[0], 0, 'a');
|
||||
expectIdentifierToken(tokens[1], 8, 'b');
|
||||
});
|
||||
|
||||
it('should tokenize quoted string', () => {
|
||||
var str = "['\\'', \"\\\"\"]";
|
||||
var tokens: Token[] = lex(str);
|
||||
expectStringToken(tokens[1], 1, "'");
|
||||
expectStringToken(tokens[3], 7, '"');
|
||||
});
|
||||
|
||||
it('should tokenize escaped quoted string', () => {
|
||||
var str = '"\\"\\n\\f\\r\\t\\v\\u00A0"';
|
||||
var tokens: Token[] = lex(str);
|
||||
expect(tokens.length).toEqual(1);
|
||||
expect(tokens[0].toString()).toEqual('"\n\f\r\t\v\u00A0');
|
||||
});
|
||||
|
||||
it('should tokenize unicode', function() {
|
||||
var tokens: Token[] = lex('"\\u00A0"');
|
||||
expect(tokens.length).toEqual(1);
|
||||
expect(tokens[0].toString()).toEqual('\u00a0');
|
||||
});
|
||||
|
||||
it('should tokenize relation', function() {
|
||||
var tokens: Token[] = lex("! == != < > <= >= === !==");
|
||||
expectOperatorToken(tokens[0], 0, '!');
|
||||
expectOperatorToken(tokens[1], 2, '==');
|
||||
expectOperatorToken(tokens[2], 5, '!=');
|
||||
expectOperatorToken(tokens[3], 8, '<');
|
||||
expectOperatorToken(tokens[4], 10, '>');
|
||||
expectOperatorToken(tokens[5], 12, '<=');
|
||||
expectOperatorToken(tokens[6], 15, '>=');
|
||||
expectOperatorToken(tokens[7], 18, '===');
|
||||
expectOperatorToken(tokens[8], 22, '!==');
|
||||
});
|
||||
|
||||
it('should tokenize statements', function() {
|
||||
var tokens: Token[] = lex("a;b;");
|
||||
expectIdentifierToken(tokens[0], 0, 'a');
|
||||
expectCharacterToken(tokens[1], 1, ';');
|
||||
expectIdentifierToken(tokens[2], 2, 'b');
|
||||
expectCharacterToken(tokens[3], 3, ';');
|
||||
});
|
||||
|
||||
it('should tokenize function invocation', function() {
|
||||
var tokens: Token[] = lex("a()");
|
||||
expectIdentifierToken(tokens[0], 0, 'a');
|
||||
expectCharacterToken(tokens[1], 1, '(');
|
||||
expectCharacterToken(tokens[2], 2, ')');
|
||||
});
|
||||
|
||||
it('should tokenize simple method invocations', function() {
|
||||
var tokens: Token[] = lex("a.method()");
|
||||
expectIdentifierToken(tokens[2], 2, 'method');
|
||||
});
|
||||
|
||||
it('should tokenize method invocation', function() {
|
||||
var tokens: Token[] = lex("a.b.c (d) - e.f()");
|
||||
expectIdentifierToken(tokens[0], 0, 'a');
|
||||
expectCharacterToken(tokens[1], 1, '.');
|
||||
expectIdentifierToken(tokens[2], 2, 'b');
|
||||
expectCharacterToken(tokens[3], 3, '.');
|
||||
expectIdentifierToken(tokens[4], 4, 'c');
|
||||
expectCharacterToken(tokens[5], 6, '(');
|
||||
expectIdentifierToken(tokens[6], 7, 'd');
|
||||
expectCharacterToken(tokens[7], 8, ')');
|
||||
expectOperatorToken(tokens[8], 10, '-');
|
||||
expectIdentifierToken(tokens[9], 12, 'e');
|
||||
expectCharacterToken(tokens[10], 13, '.');
|
||||
expectIdentifierToken(tokens[11], 14, 'f');
|
||||
expectCharacterToken(tokens[12], 15, '(');
|
||||
expectCharacterToken(tokens[13], 16, ')');
|
||||
});
|
||||
|
||||
it('should tokenize number', function() {
|
||||
var tokens: Token[] = lex("0.5");
|
||||
expectNumberToken(tokens[0], 0, 0.5);
|
||||
});
|
||||
|
||||
// NOTE(deboer): NOT A LEXER TEST
|
||||
// it('should tokenize negative number', () => {
|
||||
// var tokens:Token[] = lex("-0.5");
|
||||
// expectNumberToken(tokens[0], 0, -0.5);
|
||||
// });
|
||||
|
||||
it('should tokenize number with exponent', function() {
|
||||
var tokens: Token[] = lex("0.5E-10");
|
||||
expect(tokens.length).toEqual(1);
|
||||
expectNumberToken(tokens[0], 0, 0.5E-10);
|
||||
tokens = lex("0.5E+10");
|
||||
expectNumberToken(tokens[0], 0, 0.5E+10);
|
||||
});
|
||||
|
||||
it('should throws exception for invalid exponent', function() {
|
||||
expect(() => { lex("0.5E-"); })
|
||||
.toThrowError('Lexer Error: Invalid exponent at column 4 in expression [0.5E-]');
|
||||
|
||||
expect(() => { lex("0.5E-A"); })
|
||||
.toThrowError('Lexer Error: Invalid exponent at column 4 in expression [0.5E-A]');
|
||||
});
|
||||
|
||||
it('should tokenize number starting with a dot', function() {
|
||||
var tokens: Token[] = lex(".5");
|
||||
expectNumberToken(tokens[0], 0, 0.5);
|
||||
});
|
||||
|
||||
it('should throw error on invalid unicode', function() {
|
||||
expect(() => { lex("'\\u1''bla'"); })
|
||||
.toThrowError(
|
||||
"Lexer Error: Invalid unicode escape [\\u1''b] at column 2 in expression ['\\u1''bla']");
|
||||
});
|
||||
|
||||
it('should tokenize hash as operator', function() {
|
||||
var tokens: Token[] = lex("#");
|
||||
expectOperatorToken(tokens[0], 0, '#');
|
||||
});
|
||||
|
||||
it('should tokenize ?. as operator', () => {
|
||||
var tokens: Token[] = lex('?.');
|
||||
expectOperatorToken(tokens[0], 0, '?.');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
import {ddescribe, describe, it, xit, iit, expect, beforeEach} from 'angular2/testing_internal';
|
||||
|
||||
import {Locals} from 'angular2/src/core/change_detection/parser/locals';
|
||||
|
||||
import {MapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
export function main() {
|
||||
describe('Locals', () => {
|
||||
var locals: Locals;
|
||||
beforeEach(() => {
|
||||
locals = new Locals(null, MapWrapper.createFromPairs([['key', 'value'], ['nullKey', null]]));
|
||||
});
|
||||
|
||||
it('should support getting values', () => {
|
||||
expect(locals.get('key')).toBe('value');
|
||||
expect(() => locals.get('notPresent')).toThrowError(new RegExp("Cannot find"));
|
||||
});
|
||||
|
||||
it('should support checking if key is present', () => {
|
||||
expect(locals.contains('key')).toBe(true);
|
||||
expect(locals.contains('nullKey')).toBe(true);
|
||||
expect(locals.contains('notPresent')).toBe(false);
|
||||
});
|
||||
|
||||
it('should support setting keys', () => {
|
||||
locals.set('key', 'bar');
|
||||
expect(locals.get('key')).toBe('bar');
|
||||
});
|
||||
|
||||
it('should not support setting keys that are not present already',
|
||||
() => { expect(() => locals.set('notPresent', 'bar')).toThrowError(); });
|
||||
|
||||
it('should clearValues', () => {
|
||||
locals.clearLocalValues();
|
||||
expect(locals.get('key')).toBe(null);
|
||||
});
|
||||
})
|
||||
}
|
@ -1,466 +0,0 @@
|
||||
import {ddescribe, describe, it, xit, iit, expect, beforeEach} from 'angular2/testing_internal';
|
||||
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||
import {reflector} from 'angular2/src/core/reflection/reflection';
|
||||
import {Parser} from 'angular2/src/core/change_detection/parser/parser';
|
||||
import {Unparser} from './unparser';
|
||||
import {Lexer} from 'angular2/src/core/change_detection/parser/lexer';
|
||||
import {BindingPipe, LiteralPrimitive, AST} from 'angular2/src/core/change_detection/parser/ast';
|
||||
|
||||
export function main() {
|
||||
function createParser() { return new Parser(new Lexer(), reflector); }
|
||||
|
||||
function parseAction(text, location = null): any {
|
||||
return createParser().parseAction(text, location);
|
||||
}
|
||||
|
||||
function parseBinding(text, location = null): any {
|
||||
return createParser().parseBinding(text, location);
|
||||
}
|
||||
|
||||
function parseTemplateBindings(text, location = null): any {
|
||||
return createParser().parseTemplateBindings(text, location);
|
||||
}
|
||||
|
||||
function parseInterpolation(text, location = null): any {
|
||||
return createParser().parseInterpolation(text, location);
|
||||
}
|
||||
|
||||
function parseSimpleBinding(text, location = null): any {
|
||||
return createParser().parseSimpleBinding(text, location);
|
||||
}
|
||||
|
||||
function unparse(ast: AST): string { return new Unparser().unparse(ast); }
|
||||
|
||||
function checkInterpolation(exp: string, expected?: string) {
|
||||
var ast = parseInterpolation(exp);
|
||||
if (isBlank(expected)) expected = exp;
|
||||
expect(unparse(ast)).toEqual(expected);
|
||||
}
|
||||
|
||||
function checkBinding(exp: string, expected?: string) {
|
||||
var ast = parseBinding(exp);
|
||||
if (isBlank(expected)) expected = exp;
|
||||
expect(unparse(ast)).toEqual(expected);
|
||||
}
|
||||
|
||||
function checkAction(exp: string, expected?: string) {
|
||||
var ast = parseAction(exp);
|
||||
if (isBlank(expected)) expected = exp;
|
||||
expect(unparse(ast)).toEqual(expected);
|
||||
}
|
||||
|
||||
function expectActionError(text) { return expect(() => parseAction(text)); }
|
||||
|
||||
function expectBindingError(text) { return expect(() => parseBinding(text)); }
|
||||
|
||||
describe("parser", () => {
|
||||
describe("parseAction", () => {
|
||||
it('should parse numbers', () => { checkAction("1"); });
|
||||
|
||||
it('should parse strings', () => {
|
||||
checkAction("'1'", '"1"');
|
||||
checkAction('"1"');
|
||||
});
|
||||
|
||||
it('should parse null', () => { checkAction("null"); });
|
||||
|
||||
it('should parse unary - expressions', () => {
|
||||
checkAction("-1", "0 - 1");
|
||||
checkAction("+1", "1");
|
||||
});
|
||||
|
||||
it('should parse unary ! expressions', () => {
|
||||
checkAction("!true");
|
||||
checkAction("!!true");
|
||||
checkAction("!!!true");
|
||||
});
|
||||
|
||||
it('should parse multiplicative expressions',
|
||||
() => { checkAction("3*4/2%5", "3 * 4 / 2 % 5"); });
|
||||
|
||||
it('should parse additive expressions', () => { checkAction("3 + 6 - 2"); });
|
||||
|
||||
it('should parse relational expressions', () => {
|
||||
checkAction("2 < 3");
|
||||
checkAction("2 > 3");
|
||||
checkAction("2 <= 2");
|
||||
checkAction("2 >= 2");
|
||||
});
|
||||
|
||||
it('should parse equality expressions', () => {
|
||||
checkAction("2 == 3");
|
||||
checkAction("2 != 3");
|
||||
});
|
||||
|
||||
it('should parse strict equality expressions', () => {
|
||||
checkAction("2 === 3");
|
||||
checkAction("2 !== 3");
|
||||
});
|
||||
|
||||
it('should parse expressions', () => {
|
||||
checkAction("true && true");
|
||||
checkAction("true || false");
|
||||
});
|
||||
|
||||
it('should parse grouped expressions', () => { checkAction("(1 + 2) * 3", "1 + 2 * 3"); });
|
||||
|
||||
it('should ignore comments in expressions', () => { checkAction('a //comment', 'a'); });
|
||||
|
||||
it('should parse an empty string', () => { checkAction(''); });
|
||||
|
||||
describe("literals", () => {
|
||||
it('should parse array', () => {
|
||||
checkAction("[1][0]");
|
||||
checkAction("[[1]][0][0]");
|
||||
checkAction("[]");
|
||||
checkAction("[].length");
|
||||
checkAction("[1, 2].length");
|
||||
});
|
||||
|
||||
it('should parse map', () => {
|
||||
checkAction("{}");
|
||||
checkAction("{a: 1}[2]");
|
||||
checkAction("{}[\"a\"]");
|
||||
});
|
||||
|
||||
it('should only allow identifier, string, or keyword as map key', () => {
|
||||
expectActionError('{(:0}')
|
||||
.toThrowError(new RegExp('expected identifier, keyword, or string'));
|
||||
expectActionError('{1234:0}')
|
||||
.toThrowError(new RegExp('expected identifier, keyword, or string'));
|
||||
});
|
||||
});
|
||||
|
||||
describe("member access", () => {
|
||||
it("should parse field access", () => {
|
||||
checkAction("a");
|
||||
checkAction("a.a");
|
||||
});
|
||||
|
||||
it('should only allow identifier or keyword as member names', () => {
|
||||
expectActionError('x.(').toThrowError(new RegExp('identifier or keyword'));
|
||||
expectActionError('x. 1234').toThrowError(new RegExp('identifier or keyword'));
|
||||
expectActionError('x."foo"').toThrowError(new RegExp('identifier or keyword'));
|
||||
});
|
||||
|
||||
it('should parse safe field access', () => {
|
||||
checkAction('a?.a');
|
||||
checkAction('a.a?.a');
|
||||
});
|
||||
});
|
||||
|
||||
describe("method calls", () => {
|
||||
it("should parse method calls", () => {
|
||||
checkAction("fn()");
|
||||
checkAction("add(1, 2)");
|
||||
checkAction("a.add(1, 2)");
|
||||
checkAction("fn().add(1, 2)");
|
||||
});
|
||||
});
|
||||
|
||||
describe("functional calls",
|
||||
() => { it("should parse function calls", () => { checkAction("fn()(1, 2)"); }); });
|
||||
|
||||
describe("conditional", () => {
|
||||
it('should parse ternary/conditional expressions', () => {
|
||||
checkAction("7 == 3 + 4 ? 10 : 20");
|
||||
checkAction("false ? 10 : 20");
|
||||
});
|
||||
|
||||
it('should throw on incorrect ternary operator syntax', () => {
|
||||
expectActionError("true?1").toThrowError(new RegExp(
|
||||
'Parser Error: Conditional expression true\\?1 requires all 3 expressions'));
|
||||
});
|
||||
});
|
||||
|
||||
describe("assignment", () => {
|
||||
it("should support field assignments", () => {
|
||||
checkAction("a = 12");
|
||||
checkAction("a.a.a = 123");
|
||||
checkAction("a = 123; b = 234;");
|
||||
});
|
||||
|
||||
it("should throw on safe field assignments", () => {
|
||||
expectActionError("a?.a = 123")
|
||||
.toThrowError(new RegExp('cannot be used in the assignment'));
|
||||
});
|
||||
|
||||
it("should support array updates", () => { checkAction("a[0] = 200"); });
|
||||
});
|
||||
|
||||
it("should error when using pipes",
|
||||
() => { expectActionError('x|blah').toThrowError(new RegExp('Cannot have a pipe')); });
|
||||
|
||||
it('should store the source in the result',
|
||||
() => { expect(parseAction('someExpr').source).toBe('someExpr'); });
|
||||
|
||||
it('should store the passed-in location',
|
||||
() => { expect(parseAction('someExpr', 'location').location).toBe('location'); });
|
||||
|
||||
it("should throw when encountering interpolation", () => {
|
||||
expectActionError("{{a()}}")
|
||||
.toThrowErrorWith('Got interpolation ({{}}) where expression was expected');
|
||||
});
|
||||
});
|
||||
|
||||
describe("general error handling", () => {
|
||||
it("should throw on an unexpected token", () => {
|
||||
expectActionError("[1,2] trac").toThrowError(new RegExp('Unexpected token \'trac\''));
|
||||
});
|
||||
|
||||
it('should throw a reasonable error for unconsumed tokens', () => {
|
||||
expectActionError(")")
|
||||
.toThrowError(new RegExp("Unexpected token \\) at column 1 in \\[\\)\\]"));
|
||||
});
|
||||
|
||||
it('should throw on missing expected token', () => {
|
||||
expectActionError("a(b").toThrowError(
|
||||
new RegExp("Missing expected \\) at the end of the expression \\[a\\(b\\]"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseBinding", () => {
|
||||
describe("pipes", () => {
|
||||
it("should parse pipes", () => {
|
||||
checkBinding('a(b | c)', 'a((b | c))');
|
||||
checkBinding('a.b(c.d(e) | f)', 'a.b((c.d(e) | f))');
|
||||
checkBinding('[1, 2, 3] | a', '([1, 2, 3] | a)');
|
||||
checkBinding('{a: 1} | b', '({a: 1} | b)');
|
||||
checkBinding('a[b] | c', '(a[b] | c)');
|
||||
checkBinding('a?.b | c', '(a?.b | c)');
|
||||
checkBinding('true | a', '(true | a)');
|
||||
checkBinding('a | b:c | d', '((a | b:c) | d)');
|
||||
checkBinding('a | b:(c | d)', '(a | b:(c | d))');
|
||||
});
|
||||
|
||||
it('should only allow identifier or keyword as formatter names', () => {
|
||||
expectBindingError('"Foo"|(').toThrowError(new RegExp('identifier or keyword'));
|
||||
expectBindingError('"Foo"|1234').toThrowError(new RegExp('identifier or keyword'));
|
||||
expectBindingError('"Foo"|"uppercase"').toThrowError(new RegExp('identifier or keyword'));
|
||||
});
|
||||
|
||||
it('should parse quoted expressions', () => { checkBinding('a:b', 'a:b'); });
|
||||
|
||||
it('should not crash when prefix part is not tokenizable',
|
||||
() => { checkBinding('"a:b"', '"a:b"'); });
|
||||
|
||||
it('should ignore whitespace around quote prefix', () => { checkBinding(' a :b', 'a:b'); });
|
||||
|
||||
it('should refuse prefixes that are not single identifiers', () => {
|
||||
expectBindingError('a + b:c').toThrowError();
|
||||
expectBindingError('1:c').toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
it('should store the source in the result',
|
||||
() => { expect(parseBinding('someExpr').source).toBe('someExpr'); });
|
||||
|
||||
it('should store the passed-in location',
|
||||
() => { expect(parseBinding('someExpr', 'location').location).toBe('location'); });
|
||||
|
||||
it('should throw on chain expressions', () => {
|
||||
expect(() => parseBinding("1;2")).toThrowError(new RegExp("contain chained expression"));
|
||||
});
|
||||
|
||||
it('should throw on assignment', () => {
|
||||
expect(() => parseBinding("a=2")).toThrowError(new RegExp("contain assignments"));
|
||||
});
|
||||
|
||||
it('should throw when encountering interpolation', () => {
|
||||
expectBindingError("{{a.b}}")
|
||||
.toThrowErrorWith('Got interpolation ({{}}) where expression was expected');
|
||||
});
|
||||
|
||||
it('should parse conditional expression', () => { checkBinding('a < b ? a : b'); });
|
||||
|
||||
it('should ignore comments in bindings', () => { checkBinding('a //comment', 'a'); });
|
||||
});
|
||||
|
||||
describe('parseTemplateBindings', () => {
|
||||
|
||||
function keys(templateBindings: any[]) {
|
||||
return templateBindings.map(binding => binding.key);
|
||||
}
|
||||
|
||||
function keyValues(templateBindings: any[]) {
|
||||
return templateBindings.map(binding => {
|
||||
if (binding.keyIsVar) {
|
||||
return '#' + binding.key + (isBlank(binding.name) ? '=null' : '=' + binding.name);
|
||||
} else {
|
||||
return binding.key + (isBlank(binding.expression) ? '' : `=${binding.expression}`)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function exprSources(templateBindings: any[]) {
|
||||
return templateBindings.map(
|
||||
binding => isPresent(binding.expression) ? binding.expression.source : null);
|
||||
}
|
||||
|
||||
it('should parse an empty string', () => { expect(parseTemplateBindings('')).toEqual([]); });
|
||||
|
||||
it('should parse a string without a value',
|
||||
() => { expect(keys(parseTemplateBindings('a'))).toEqual(['a']); });
|
||||
|
||||
it('should only allow identifier, string, or keyword including dashes as keys', () => {
|
||||
var bindings = parseTemplateBindings("a:'b'");
|
||||
expect(keys(bindings)).toEqual(['a']);
|
||||
|
||||
bindings = parseTemplateBindings("'a':'b'");
|
||||
expect(keys(bindings)).toEqual(['a']);
|
||||
|
||||
bindings = parseTemplateBindings("\"a\":'b'");
|
||||
expect(keys(bindings)).toEqual(['a']);
|
||||
|
||||
bindings = parseTemplateBindings("a-b:'c'");
|
||||
expect(keys(bindings)).toEqual(['a-b']);
|
||||
|
||||
expect(() => { parseTemplateBindings('(:0'); })
|
||||
.toThrowError(new RegExp('expected identifier, keyword, or string'));
|
||||
|
||||
expect(() => { parseTemplateBindings('1234:0'); })
|
||||
.toThrowError(new RegExp('expected identifier, keyword, or string'));
|
||||
});
|
||||
|
||||
it('should detect expressions as value', () => {
|
||||
var bindings = parseTemplateBindings("a:b");
|
||||
expect(exprSources(bindings)).toEqual(['b']);
|
||||
|
||||
bindings = parseTemplateBindings("a:1+1");
|
||||
expect(exprSources(bindings)).toEqual(['1+1']);
|
||||
});
|
||||
|
||||
it('should detect names as value', () => {
|
||||
var bindings = parseTemplateBindings("a:#b");
|
||||
expect(keyValues(bindings)).toEqual(['a', '#b=\$implicit']);
|
||||
});
|
||||
|
||||
it('should allow space and colon as separators', () => {
|
||||
var bindings = parseTemplateBindings("a:b");
|
||||
expect(keys(bindings)).toEqual(['a']);
|
||||
expect(exprSources(bindings)).toEqual(['b']);
|
||||
|
||||
bindings = parseTemplateBindings("a b");
|
||||
expect(keys(bindings)).toEqual(['a']);
|
||||
expect(exprSources(bindings)).toEqual(['b']);
|
||||
});
|
||||
|
||||
it('should allow multiple pairs', () => {
|
||||
var bindings = parseTemplateBindings("a 1 b 2");
|
||||
expect(keys(bindings)).toEqual(['a', 'aB']);
|
||||
expect(exprSources(bindings)).toEqual(['1 ', '2']);
|
||||
});
|
||||
|
||||
it('should store the sources in the result', () => {
|
||||
var bindings = parseTemplateBindings("a 1,b 2");
|
||||
expect(bindings[0].expression.source).toEqual('1');
|
||||
expect(bindings[1].expression.source).toEqual('2');
|
||||
});
|
||||
|
||||
it('should store the passed-in location', () => {
|
||||
var bindings = parseTemplateBindings("a 1,b 2", 'location');
|
||||
expect(bindings[0].expression.location).toEqual('location');
|
||||
});
|
||||
|
||||
it('should support var/# notation', () => {
|
||||
var bindings = parseTemplateBindings("var i");
|
||||
expect(keyValues(bindings)).toEqual(['#i=\$implicit']);
|
||||
|
||||
bindings = parseTemplateBindings("#i");
|
||||
expect(keyValues(bindings)).toEqual(['#i=\$implicit']);
|
||||
|
||||
bindings = parseTemplateBindings("var a; var b");
|
||||
expect(keyValues(bindings)).toEqual(['#a=\$implicit', '#b=\$implicit']);
|
||||
|
||||
bindings = parseTemplateBindings("#a; #b;");
|
||||
expect(keyValues(bindings)).toEqual(['#a=\$implicit', '#b=\$implicit']);
|
||||
|
||||
bindings = parseTemplateBindings("var i-a = k-a");
|
||||
expect(keyValues(bindings)).toEqual(['#i-a=k-a']);
|
||||
|
||||
bindings = parseTemplateBindings("keyword var item; var i = k");
|
||||
expect(keyValues(bindings)).toEqual(['keyword', '#item=\$implicit', '#i=k']);
|
||||
|
||||
bindings = parseTemplateBindings("keyword: #item; #i = k");
|
||||
expect(keyValues(bindings)).toEqual(['keyword', '#item=\$implicit', '#i=k']);
|
||||
|
||||
bindings = parseTemplateBindings("directive: var item in expr; var a = b", 'location');
|
||||
expect(keyValues(bindings))
|
||||
.toEqual(['directive', '#item=\$implicit', 'directiveIn=expr in location', '#a=b']);
|
||||
});
|
||||
|
||||
it('should parse pipes', () => {
|
||||
var bindings = parseTemplateBindings('key value|pipe');
|
||||
var ast = bindings[0].expression.ast;
|
||||
expect(ast).toBeAnInstanceOf(BindingPipe);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseInterpolation', () => {
|
||||
it('should return null if no interpolation',
|
||||
() => { expect(parseInterpolation('nothing')).toBe(null); });
|
||||
|
||||
it('should parse no prefix/suffix interpolation', () => {
|
||||
var ast = parseInterpolation('{{a}}').ast;
|
||||
expect(ast.strings).toEqual(['', '']);
|
||||
expect(ast.expressions.length).toEqual(1);
|
||||
expect(ast.expressions[0].name).toEqual('a');
|
||||
});
|
||||
|
||||
it('should parse prefix/suffix with multiple interpolation', () => {
|
||||
var originalExp = 'before {{ a }} middle {{ b }} after';
|
||||
var ast = parseInterpolation(originalExp).ast;
|
||||
expect(new Unparser().unparse(ast)).toEqual(originalExp);
|
||||
});
|
||||
|
||||
it("should throw on empty interpolation expressions", () => {
|
||||
expect(() => parseInterpolation("{{}}"))
|
||||
.toThrowErrorWith(
|
||||
"Parser Error: Blank expressions are not allowed in interpolated strings");
|
||||
|
||||
expect(() => parseInterpolation("foo {{ }}"))
|
||||
.toThrowErrorWith(
|
||||
"Parser Error: Blank expressions are not allowed in interpolated strings");
|
||||
});
|
||||
|
||||
it('should parse conditional expression',
|
||||
() => { checkInterpolation('{{ a < b ? a : b }}'); });
|
||||
|
||||
it('should parse expression with newline characters', () => {
|
||||
checkInterpolation(`{{ 'foo' +\n 'bar' +\r 'baz' }}`, `{{ "foo" + "bar" + "baz" }}`);
|
||||
});
|
||||
|
||||
it('should ignore comments in interpolation expressions',
|
||||
() => { checkInterpolation('{{a //comment}}', '{{ a }}'); });
|
||||
});
|
||||
|
||||
describe("parseSimpleBinding", () => {
|
||||
it("should parse a field access", () => {
|
||||
var p = parseSimpleBinding("name");
|
||||
expect(unparse(p)).toEqual("name");
|
||||
});
|
||||
|
||||
it("should parse a constant", () => {
|
||||
var p = parseSimpleBinding("[1, 2]");
|
||||
expect(unparse(p)).toEqual("[1, 2]");
|
||||
});
|
||||
|
||||
it("should throw when the given expression is not just a field name", () => {
|
||||
expect(() => parseSimpleBinding("name + 1"))
|
||||
.toThrowErrorWith(
|
||||
'Host binding expression can only contain field access and constants');
|
||||
});
|
||||
|
||||
it('should throw when encountering interpolation', () => {
|
||||
expect(() => parseSimpleBinding('{{exp}}'))
|
||||
.toThrowErrorWith('Got interpolation ({{}}) where expression was expected');
|
||||
});
|
||||
});
|
||||
|
||||
describe('wrapLiteralPrimitive', () => {
|
||||
it('should wrap a literal primitive', () => {
|
||||
expect(unparse(createParser().wrapLiteralPrimitive("foo", null))).toEqual('"foo"');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,194 +0,0 @@
|
||||
import {
|
||||
AST,
|
||||
AstVisitor,
|
||||
PropertyRead,
|
||||
PropertyWrite,
|
||||
Binary,
|
||||
Chain,
|
||||
Conditional,
|
||||
EmptyExpr,
|
||||
BindingPipe,
|
||||
FunctionCall,
|
||||
ImplicitReceiver,
|
||||
Interpolation,
|
||||
KeyedRead,
|
||||
KeyedWrite,
|
||||
LiteralArray,
|
||||
LiteralMap,
|
||||
LiteralPrimitive,
|
||||
MethodCall,
|
||||
PrefixNot,
|
||||
Quote,
|
||||
SafePropertyRead,
|
||||
SafeMethodCall
|
||||
} from 'angular2/src/core/change_detection/parser/ast';
|
||||
|
||||
|
||||
import {StringWrapper, isPresent, isString} from 'angular2/src/facade/lang';
|
||||
|
||||
export class Unparser implements AstVisitor {
|
||||
private static _quoteRegExp = /"/g;
|
||||
private _expression: string;
|
||||
|
||||
unparse(ast: AST) {
|
||||
this._expression = '';
|
||||
this._visit(ast);
|
||||
return this._expression;
|
||||
}
|
||||
|
||||
visitPropertyRead(ast: PropertyRead) {
|
||||
this._visit(ast.receiver);
|
||||
this._expression += ast.receiver instanceof ImplicitReceiver ? `${ast.name}` : `.${ast.name}`;
|
||||
}
|
||||
|
||||
visitPropertyWrite(ast: PropertyWrite) {
|
||||
this._visit(ast.receiver);
|
||||
this._expression +=
|
||||
ast.receiver instanceof ImplicitReceiver ? `${ast.name} = ` : `.${ast.name} = `;
|
||||
this._visit(ast.value);
|
||||
}
|
||||
|
||||
visitBinary(ast: Binary) {
|
||||
this._visit(ast.left);
|
||||
this._expression += ` ${ast.operation} `;
|
||||
this._visit(ast.right);
|
||||
}
|
||||
|
||||
visitChain(ast: Chain) {
|
||||
var len = ast.expressions.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
this._visit(ast.expressions[i]);
|
||||
this._expression += i == len - 1 ? ';' : '; ';
|
||||
}
|
||||
}
|
||||
|
||||
visitConditional(ast: Conditional) {
|
||||
this._visit(ast.condition);
|
||||
this._expression += ' ? ';
|
||||
this._visit(ast.trueExp);
|
||||
this._expression += ' : ';
|
||||
this._visit(ast.falseExp);
|
||||
}
|
||||
|
||||
visitPipe(ast: BindingPipe) {
|
||||
this._expression += '(';
|
||||
this._visit(ast.exp);
|
||||
this._expression += ` | ${ast.name}`;
|
||||
ast.args.forEach(arg => {
|
||||
this._expression += ':';
|
||||
this._visit(arg);
|
||||
});
|
||||
this._expression += ')';
|
||||
}
|
||||
|
||||
visitFunctionCall(ast: FunctionCall) {
|
||||
this._visit(ast.target);
|
||||
this._expression += '(';
|
||||
var isFirst = true;
|
||||
ast.args.forEach(arg => {
|
||||
if (!isFirst) this._expression += ', ';
|
||||
isFirst = false;
|
||||
this._visit(arg);
|
||||
});
|
||||
this._expression += ')';
|
||||
}
|
||||
|
||||
visitImplicitReceiver(ast: ImplicitReceiver) {}
|
||||
|
||||
visitInterpolation(ast: Interpolation) {
|
||||
for (let i = 0; i < ast.strings.length; i++) {
|
||||
this._expression += ast.strings[i];
|
||||
if (i < ast.expressions.length) {
|
||||
this._expression += '{{ ';
|
||||
this._visit(ast.expressions[i]);
|
||||
this._expression += ' }}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visitKeyedRead(ast: KeyedRead) {
|
||||
this._visit(ast.obj);
|
||||
this._expression += '[';
|
||||
this._visit(ast.key);
|
||||
this._expression += ']';
|
||||
}
|
||||
|
||||
visitKeyedWrite(ast: KeyedWrite) {
|
||||
this._visit(ast.obj);
|
||||
this._expression += '[';
|
||||
this._visit(ast.key);
|
||||
this._expression += '] = ';
|
||||
this._visit(ast.value);
|
||||
}
|
||||
|
||||
visitLiteralArray(ast: LiteralArray) {
|
||||
this._expression += '[';
|
||||
var isFirst = true;
|
||||
ast.expressions.forEach(expression => {
|
||||
if (!isFirst) this._expression += ', ';
|
||||
isFirst = false;
|
||||
this._visit(expression);
|
||||
});
|
||||
|
||||
this._expression += ']';
|
||||
}
|
||||
|
||||
visitLiteralMap(ast: LiteralMap) {
|
||||
this._expression += '{';
|
||||
var isFirst = true;
|
||||
for (let i = 0; i < ast.keys.length; i++) {
|
||||
if (!isFirst) this._expression += ', ';
|
||||
isFirst = false;
|
||||
this._expression += `${ast.keys[i]}: `;
|
||||
this._visit(ast.values[i]);
|
||||
}
|
||||
|
||||
this._expression += '}';
|
||||
}
|
||||
|
||||
visitLiteralPrimitive(ast: LiteralPrimitive) {
|
||||
if (isString(ast.value)) {
|
||||
this._expression += `"${StringWrapper.replaceAll(ast.value, Unparser._quoteRegExp, '\"')}"`;
|
||||
} else {
|
||||
this._expression += `${ast.value}`;
|
||||
}
|
||||
}
|
||||
|
||||
visitMethodCall(ast: MethodCall) {
|
||||
this._visit(ast.receiver);
|
||||
this._expression += ast.receiver instanceof ImplicitReceiver ? `${ast.name}(` : `.${ast.name}(`;
|
||||
var isFirst = true;
|
||||
ast.args.forEach(arg => {
|
||||
if (!isFirst) this._expression += ', ';
|
||||
isFirst = false;
|
||||
this._visit(arg);
|
||||
});
|
||||
this._expression += ')';
|
||||
}
|
||||
|
||||
visitPrefixNot(ast: PrefixNot) {
|
||||
this._expression += '!';
|
||||
this._visit(ast.expression);
|
||||
}
|
||||
|
||||
visitSafePropertyRead(ast: SafePropertyRead) {
|
||||
this._visit(ast.receiver);
|
||||
this._expression += `?.${ast.name}`;
|
||||
}
|
||||
|
||||
visitSafeMethodCall(ast: SafeMethodCall) {
|
||||
this._visit(ast.receiver);
|
||||
this._expression += `?.${ast.name}(`;
|
||||
var isFirst = true;
|
||||
ast.args.forEach(arg => {
|
||||
if (!isFirst) this._expression += ', ';
|
||||
isFirst = false;
|
||||
this._visit(arg);
|
||||
});
|
||||
this._expression += ')';
|
||||
}
|
||||
|
||||
visitQuote(ast: Quote) { this._expression += `${ast.prefix}:${ast.uninterpretedExpression}`; }
|
||||
|
||||
private _visit(ast: AST) { ast.visit(this); }
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
import {
|
||||
ddescribe,
|
||||
describe,
|
||||
it,
|
||||
iit,
|
||||
xit,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
inject
|
||||
} from 'angular2/testing_internal';
|
||||
|
||||
import {ProtoRecordBuilder} from 'angular2/src/core/change_detection/proto_change_detector';
|
||||
import {BindingRecord} from 'angular2/src/core/change_detection/binding_record';
|
||||
import {Parser} from 'angular2/src/core/change_detection/parser/parser';
|
||||
|
||||
export function main() {
|
||||
describe("ProtoRecordBuilder", () => {
|
||||
it('should set argumentToPureFunction flag', inject([Parser], (p: Parser) => {
|
||||
var builder = new ProtoRecordBuilder();
|
||||
var ast = p.parseBinding("[1,2]", "location"); // collection literal is a pure function
|
||||
builder.add(BindingRecord.createForElementProperty(ast, 0, "property"), [], 0);
|
||||
|
||||
var isPureFunc = builder.records.map(r => r.argumentToPureFunction);
|
||||
expect(isPureFunc).toEqual([true, true, false]);
|
||||
}));
|
||||
|
||||
it('should not set argumentToPureFunction flag when not needed',
|
||||
inject([Parser], (p: Parser) => {
|
||||
var builder = new ProtoRecordBuilder();
|
||||
var ast = p.parseBinding("f(1,2)", "location");
|
||||
builder.add(BindingRecord.createForElementProperty(ast, 0, "property"), [], 0);
|
||||
|
||||
var isPureFunc = builder.records.map(r => r.argumentToPureFunction);
|
||||
expect(isPureFunc).toEqual([false, false, false]);
|
||||
}));
|
||||
});
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
import {
|
||||
ddescribe,
|
||||
describe,
|
||||
it,
|
||||
iit,
|
||||
xit,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach
|
||||
} from 'angular2/testing_internal';
|
||||
import {isBlank} from 'angular2/src/facade/lang';
|
||||
|
||||
import {RecordType, ProtoRecord} from 'angular2/src/core/change_detection/proto_record';
|
||||
|
||||
export function main() {
|
||||
function r(
|
||||
{lastInBinding, mode, name, directiveIndex, argumentToPureFunction, referencedBySelf}: {
|
||||
lastInBinding?: any,
|
||||
mode?: any,
|
||||
name?: any,
|
||||
directiveIndex?: any,
|
||||
argumentToPureFunction?: boolean,
|
||||
referencedBySelf?: boolean
|
||||
} = {}) {
|
||||
if (isBlank(lastInBinding)) lastInBinding = false;
|
||||
if (isBlank(mode)) mode = RecordType.PropertyRead;
|
||||
if (isBlank(name)) name = "name";
|
||||
if (isBlank(directiveIndex)) directiveIndex = null;
|
||||
if (isBlank(argumentToPureFunction)) argumentToPureFunction = false;
|
||||
if (isBlank(referencedBySelf)) referencedBySelf = false;
|
||||
|
||||
return new ProtoRecord(mode, name, null, [], null, 0, directiveIndex, 0, null, lastInBinding,
|
||||
false, argumentToPureFunction, referencedBySelf, 0);
|
||||
}
|
||||
|
||||
describe("ProtoRecord", () => {
|
||||
describe('shouldBeChecked', () => {
|
||||
it('should be true for pure functions',
|
||||
() => { expect(r({mode: RecordType.CollectionLiteral}).shouldBeChecked()).toBeTruthy(); });
|
||||
|
||||
it('should be true for args of pure functions', () => {
|
||||
expect(r({mode: RecordType.Const, argumentToPureFunction: true}).shouldBeChecked())
|
||||
.toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be true for last in binding records', () => {
|
||||
expect(r({mode: RecordType.Const, lastInBinding: true}).shouldBeChecked()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be false otherwise',
|
||||
() => { expect(r({mode: RecordType.Const}).shouldBeChecked()).toBeFalsy(); });
|
||||
});
|
||||
|
||||
describe('isUsedByOtherRecord', () => {
|
||||
it('should be false for lastInBinding records',
|
||||
() => { expect(r({lastInBinding: true}).isUsedByOtherRecord()).toBeFalsy(); });
|
||||
|
||||
it('should be true for lastInBinding records that are referenced by self records', () => {
|
||||
expect(r({lastInBinding: true, referencedBySelf: true}).isUsedByOtherRecord()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be true for non lastInBinding records',
|
||||
() => { expect(r({lastInBinding: false}).isUsedByOtherRecord()).toBeTruthy(); });
|
||||
});
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user