chore(packaging): move files to match target file structure
This commit is contained in:
299
modules/angular2/test/change_detection/array_changes_spec.js
vendored
Normal file
299
modules/angular2/test/change_detection/array_changes_spec.js
vendored
Normal file
@ -0,0 +1,299 @@
|
||||
import {describe, it, iit, xit, expect, beforeEach, afterEach} from 'test_lib/test_lib';
|
||||
import {ArrayChanges} from 'change_detection/src/array_changes';
|
||||
|
||||
import {NumberWrapper} from 'facade/src/lang';
|
||||
import {ListWrapper, MapWrapper} from 'facade/src/collection';
|
||||
|
||||
import {TestIterable} from './iterable';
|
||||
import {arrayChangesAsString} from './util';
|
||||
|
||||
// todo(vicb): UnmodifiableListView / frozen object when implemented
|
||||
export function main() {
|
||||
describe('collection_changes', function() {
|
||||
describe('CollectionChanges', function() {
|
||||
var changes;
|
||||
var l;
|
||||
|
||||
beforeEach(() => {
|
||||
changes = new ArrayChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
changes = null;
|
||||
});
|
||||
|
||||
it('should support list and iterables', () => {
|
||||
expect(ArrayChanges.supports([])).toBeTruthy();
|
||||
expect(ArrayChanges.supports(new TestIterable())).toBeTruthy();
|
||||
expect(ArrayChanges.supports(MapWrapper.create())).toBeFalsy();
|
||||
expect(ArrayChanges.supports(null)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should support iterables', () => {
|
||||
l = new TestIterable();
|
||||
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: []
|
||||
}));
|
||||
|
||||
l.list = [1];
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['1[null->0]'],
|
||||
additions: ['1[null->0]']
|
||||
}));
|
||||
|
||||
l.list = [2, 1];
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['2[null->0]', '1[0->1]'],
|
||||
previous: ['1[0->1]'],
|
||||
additions: ['2[null->0]'],
|
||||
moves: ['1[0->1]']
|
||||
}));
|
||||
});
|
||||
|
||||
it('should detect additions', () => {
|
||||
l = [];
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: []
|
||||
}));
|
||||
|
||||
ListWrapper.push(l, 'a');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['a[null->0]'],
|
||||
additions: ['a[null->0]']
|
||||
}));
|
||||
|
||||
ListWrapper.push(l, 'b');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['a', 'b[null->1]'],
|
||||
previous: ['a'],
|
||||
additions: ['b[null->1]']
|
||||
}));
|
||||
});
|
||||
|
||||
it('should support changing the reference', () => {
|
||||
l = [0];
|
||||
changes.check(l);
|
||||
|
||||
l = [1, 0];
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['1[null->0]', '0[0->1]'],
|
||||
previous: ['0[0->1]'],
|
||||
additions: ['1[null->0]'],
|
||||
moves: ['0[0->1]']
|
||||
}));
|
||||
|
||||
l = [2, 1, 0];
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['2[null->0]', '1[0->1]', '0[1->2]'],
|
||||
previous: ['1[0->1]', '0[1->2]'],
|
||||
additions: ['2[null->0]'],
|
||||
moves: ['1[0->1]', '0[1->2]']
|
||||
}));
|
||||
});
|
||||
|
||||
it('should handle swapping element', () => {
|
||||
l = [1, 2];
|
||||
changes.check(l);
|
||||
|
||||
ListWrapper.clear(l);
|
||||
ListWrapper.push(l, 2);
|
||||
ListWrapper.push(l, 1);
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['2[1->0]', '1[0->1]'],
|
||||
previous: ['1[0->1]', '2[1->0]'],
|
||||
moves: ['2[1->0]', '1[0->1]']
|
||||
}));
|
||||
});
|
||||
|
||||
it('should handle swapping element', () => {
|
||||
l = ['a', 'b', 'c'];
|
||||
changes.check(l);
|
||||
|
||||
ListWrapper.removeAt(l, 1);
|
||||
ListWrapper.insert(l, 0, 'b');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['b[1->0]', 'a[0->1]', 'c'],
|
||||
previous: ['a[0->1]', 'b[1->0]', 'c'],
|
||||
moves: ['b[1->0]', 'a[0->1]']
|
||||
}));
|
||||
|
||||
ListWrapper.removeAt(l, 1);
|
||||
ListWrapper.push(l, 'a');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['b', 'c[2->1]', 'a[1->2]'],
|
||||
previous: ['b', 'a[1->2]', 'c[2->1]'],
|
||||
moves: ['c[2->1]', 'a[1->2]']
|
||||
}));
|
||||
});
|
||||
|
||||
it('should detect changes in list', () => {
|
||||
l = [];
|
||||
changes.check(l);
|
||||
|
||||
ListWrapper.push(l, 'a');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['a[null->0]'],
|
||||
additions: ['a[null->0]']
|
||||
}));
|
||||
|
||||
ListWrapper.push(l, 'b');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['a', 'b[null->1]'],
|
||||
previous: ['a'],
|
||||
additions: ['b[null->1]']
|
||||
}));
|
||||
|
||||
ListWrapper.push(l, 'c');
|
||||
ListWrapper.push(l, 'd');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['a', 'b', 'c[null->2]', 'd[null->3]'],
|
||||
previous: ['a', 'b'],
|
||||
additions: ['c[null->2]', 'd[null->3]']
|
||||
}));
|
||||
|
||||
ListWrapper.removeAt(l, 2);
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['a', 'b', 'd[3->2]'],
|
||||
previous: ['a', 'b', 'c[2->null]', 'd[3->2]'],
|
||||
moves: ['d[3->2]'],
|
||||
removals: ['c[2->null]']
|
||||
}));
|
||||
|
||||
ListWrapper.clear(l);
|
||||
ListWrapper.push(l, 'd');
|
||||
ListWrapper.push(l, 'c');
|
||||
ListWrapper.push(l, 'b');
|
||||
ListWrapper.push(l, 'a');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['d[2->0]', 'c[null->1]', 'b[1->2]', 'a[0->3]'],
|
||||
previous: ['a[0->3]', 'b[1->2]', 'd[2->0]'],
|
||||
additions: ['c[null->1]'],
|
||||
moves: ['d[2->0]', 'b[1->2]', 'a[0->3]']
|
||||
}));
|
||||
});
|
||||
|
||||
it('should test string by value rather than by reference (Dart)', () => {
|
||||
l = ['a', 'boo'];
|
||||
changes.check(l);
|
||||
|
||||
var b = 'b';
|
||||
var oo = 'oo';
|
||||
ListWrapper.set(l, 1, b + oo);
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['a', 'boo'],
|
||||
previous: ['a', 'boo']
|
||||
}));
|
||||
});
|
||||
|
||||
it('should ignore [NaN] != [NaN] (JS)', () => {
|
||||
l = [NumberWrapper.NaN];
|
||||
changes.check(l);
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: [NumberWrapper.NaN],
|
||||
previous: [NumberWrapper.NaN]
|
||||
}));
|
||||
});
|
||||
|
||||
it('should detect [NaN] moves', () => {
|
||||
l = [NumberWrapper.NaN, NumberWrapper.NaN];
|
||||
changes.check(l);
|
||||
|
||||
ListWrapper.insert(l, 0, 'foo');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['foo[null->0]', 'NaN[0->1]', 'NaN[1->2]'],
|
||||
previous: ['NaN[0->1]', 'NaN[1->2]'],
|
||||
additions: ['foo[null->0]'],
|
||||
moves: ['NaN[0->1]', 'NaN[1->2]']}
|
||||
));
|
||||
});
|
||||
|
||||
it('should remove and add same item', () => {
|
||||
l = ['a', 'b', 'c'];
|
||||
changes.check(l);
|
||||
|
||||
ListWrapper.removeAt(l, 1);
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['a', 'c[2->1]'],
|
||||
previous: ['a', 'b[1->null]', 'c[2->1]'],
|
||||
moves: ['c[2->1]'],
|
||||
removals: ['b[1->null]']
|
||||
}));
|
||||
|
||||
ListWrapper.insert(l, 1, 'b');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['a', 'b[null->1]', 'c[1->2]'],
|
||||
previous: ['a', 'c[1->2]'],
|
||||
additions: ['b[null->1]'],
|
||||
moves: ['c[1->2]']
|
||||
}));
|
||||
});
|
||||
|
||||
it('should support duplicates', () => {
|
||||
l = ['a', 'a', 'a', 'b', 'b'];
|
||||
changes.check(l);
|
||||
|
||||
ListWrapper.removeAt(l, 0);
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['a', 'a', 'b[3->2]', 'b[4->3]'],
|
||||
previous: ['a', 'a', 'a[2->null]', 'b[3->2]', 'b[4->3]'],
|
||||
moves: ['b[3->2]', 'b[4->3]'],
|
||||
removals: ['a[2->null]']
|
||||
}));
|
||||
});
|
||||
|
||||
it('should support insertions/moves', () => {
|
||||
l = ['a', 'a', 'b', 'b'];
|
||||
changes.check(l);
|
||||
|
||||
ListWrapper.insert(l, 0, 'b');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['b[2->0]', 'a[0->1]', 'a[1->2]', 'b', 'b[null->4]'],
|
||||
previous: ['a[0->1]', 'a[1->2]', 'b[2->0]', 'b'],
|
||||
additions: ['b[null->4]'],
|
||||
moves: ['b[2->0]', 'a[0->1]', 'a[1->2]']
|
||||
}));
|
||||
});
|
||||
|
||||
it('should not report unnecessary moves', () => {
|
||||
l = ['a', 'b', 'c'];
|
||||
changes.check(l);
|
||||
|
||||
ListWrapper.clear(l);
|
||||
ListWrapper.push(l, 'b');
|
||||
ListWrapper.push(l, 'a');
|
||||
ListWrapper.push(l, 'c');
|
||||
changes.check(l);
|
||||
expect(changes.toString()).toEqual(arrayChangesAsString({
|
||||
collection: ['b[1->0]', 'a[0->1]', 'c'],
|
||||
previous: ['a[0->1]', 'b[1->0]', 'c'],
|
||||
moves: ['b[1->0]', 'a[0->1]']
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
637
modules/angular2/test/change_detection/change_detection_spec.js
vendored
Normal file
637
modules/angular2/test/change_detection/change_detection_spec.js
vendored
Normal file
@ -0,0 +1,637 @@
|
||||
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, IS_DARTIUM} from 'test_lib/test_lib';
|
||||
|
||||
import {isPresent, isBlank, isJsObject, BaseException, FunctionWrapper} from 'facade/src/lang';
|
||||
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/src/collection';
|
||||
|
||||
import {Parser} from 'change_detection/src/parser/parser';
|
||||
import {Lexer} from 'change_detection/src/parser/lexer';
|
||||
import {reflector} from 'reflection/src/reflection';
|
||||
import {arrayChangesAsString, kvChangesAsString} from './util';
|
||||
|
||||
import {ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError, ContextWithVariableBindings,
|
||||
CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from 'change_detection/change_detection';
|
||||
|
||||
|
||||
import {JitProtoChangeDetector, DynamicProtoChangeDetector} from 'change_detection/src/proto_change_detector';
|
||||
import {ChangeDetectionUtil} from 'change_detection/src/change_detection_util';
|
||||
|
||||
|
||||
export function main() {
|
||||
describe("change detection", () => {
|
||||
StringMapWrapper.forEach(
|
||||
{ "dynamic": () => new DynamicProtoChangeDetector(),
|
||||
"JIT": () => new JitProtoChangeDetector()
|
||||
}, (createProtoChangeDetector, name) => {
|
||||
|
||||
if (name == "JIT" && IS_DARTIUM) return;
|
||||
|
||||
function ast(exp:string, location:string = 'location') {
|
||||
var parser = new Parser(new Lexer());
|
||||
return parser.parseBinding(exp, location);
|
||||
}
|
||||
|
||||
function createChangeDetector(memo:string, exp:string, context = null, formatters = null,
|
||||
structural = false) {
|
||||
var pcd = createProtoChangeDetector();
|
||||
pcd.addAst(ast(exp), memo, memo, structural);
|
||||
|
||||
var dispatcher = new TestDispatcher();
|
||||
var cd = pcd.instantiate(dispatcher, formatters);
|
||||
cd.setContext(context);
|
||||
|
||||
return {"changeDetector" : cd, "dispatcher" : dispatcher};
|
||||
}
|
||||
|
||||
function executeWatch(memo:string, exp:string, context = null, formatters = null,
|
||||
content = false) {
|
||||
var res = createChangeDetector(memo, exp, context, formatters, content);
|
||||
res["changeDetector"].detectChanges();
|
||||
return res["dispatcher"].log;
|
||||
}
|
||||
|
||||
describe(`${name} change detection`, () => {
|
||||
it('should do simple watching', () => {
|
||||
var person = new Person("misko");
|
||||
var c = createChangeDetector('name', 'name', person);
|
||||
var cd = c["changeDetector"];
|
||||
var dispatcher = c["dispatcher"];
|
||||
|
||||
cd.detectChanges();
|
||||
expect(dispatcher.log).toEqual(['name=misko']);
|
||||
dispatcher.clear();
|
||||
|
||||
cd.detectChanges();
|
||||
expect(dispatcher.log).toEqual([]);
|
||||
dispatcher.clear();
|
||||
|
||||
person.name = "Misko";
|
||||
cd.detectChanges();
|
||||
expect(dispatcher.log).toEqual(['name=Misko']);
|
||||
});
|
||||
|
||||
it('should report all changes on the first run including uninitialized values', () => {
|
||||
expect(executeWatch('value', 'value', new Uninitialized())).toEqual(['value=null']);
|
||||
});
|
||||
|
||||
it('should report all changes on the first run including null values', () => {
|
||||
var td = new TestData(null);
|
||||
expect(executeWatch('a', 'a', td)).toEqual(['a=null']);
|
||||
});
|
||||
|
||||
it("should support literals", () => {
|
||||
expect(executeWatch('const', '10')).toEqual(['const=10']);
|
||||
expect(executeWatch('const', '"str"')).toEqual(['const=str']);
|
||||
expect(executeWatch('const', '"a\n\nb"')).toEqual(['const=a\n\nb']);
|
||||
});
|
||||
|
||||
it('simple chained property access', () => {
|
||||
var address = new Address('Grenoble');
|
||||
var person = new Person('Victor', address);
|
||||
|
||||
expect(executeWatch('address.city', 'address.city', person))
|
||||
.toEqual(['address.city=Grenoble']);
|
||||
});
|
||||
|
||||
it("should support method calls", () => {
|
||||
var person = new Person('Victor');
|
||||
expect(executeWatch('m', 'sayHi("Jim")', person)).toEqual(['m=Hi, Jim']);
|
||||
});
|
||||
|
||||
it("should support function calls", () => {
|
||||
var td = new TestData(() => (a) => a);
|
||||
expect(executeWatch('value', 'a()(99)', td)).toEqual(['value=99']);
|
||||
});
|
||||
|
||||
it("should support chained method calls", () => {
|
||||
var person = new Person('Victor');
|
||||
var td = new TestData(person);
|
||||
expect(executeWatch('m', 'a.sayHi("Jim")', td)).toEqual(['m=Hi, Jim']);
|
||||
});
|
||||
|
||||
it("should support literal array", () => {
|
||||
var c = createChangeDetector('array', '[1,2]');
|
||||
c["changeDetector"].detectChanges();
|
||||
expect(c["dispatcher"].loggedValues).toEqual([[[1, 2]]]);
|
||||
|
||||
c = createChangeDetector('array', '[1,a]', new TestData(2));
|
||||
c["changeDetector"].detectChanges();
|
||||
expect(c["dispatcher"].loggedValues).toEqual([[[1, 2]]]);
|
||||
});
|
||||
|
||||
it("should support literal maps", () => {
|
||||
var c = createChangeDetector('map', '{z:1}');
|
||||
c["changeDetector"].detectChanges();
|
||||
expect(c["dispatcher"].loggedValues[0][0]['z']).toEqual(1);
|
||||
|
||||
c = createChangeDetector('map', '{z:a}', new TestData(1));
|
||||
c["changeDetector"].detectChanges();
|
||||
expect(c["dispatcher"].loggedValues[0][0]['z']).toEqual(1);
|
||||
});
|
||||
|
||||
it("should support binary operations", () => {
|
||||
expect(executeWatch('exp', '10 + 2')).toEqual(['exp=12']);
|
||||
expect(executeWatch('exp', '10 - 2')).toEqual(['exp=8']);
|
||||
|
||||
expect(executeWatch('exp', '10 * 2')).toEqual(['exp=20']);
|
||||
expect(executeWatch('exp', '10 / 2')).toEqual([`exp=${5.0}`]); //dart exp=5.0, js exp=5
|
||||
expect(executeWatch('exp', '11 % 2')).toEqual(['exp=1']);
|
||||
|
||||
expect(executeWatch('exp', '1 == 1')).toEqual(['exp=true']);
|
||||
expect(executeWatch('exp', '1 != 1')).toEqual(['exp=false']);
|
||||
|
||||
expect(executeWatch('exp', '1 < 2')).toEqual(['exp=true']);
|
||||
expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']);
|
||||
|
||||
expect(executeWatch('exp', '2 > 1')).toEqual(['exp=true']);
|
||||
expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']);
|
||||
|
||||
expect(executeWatch('exp', '1 <= 2')).toEqual(['exp=true']);
|
||||
expect(executeWatch('exp', '2 <= 2')).toEqual(['exp=true']);
|
||||
expect(executeWatch('exp', '2 <= 1')).toEqual(['exp=false']);
|
||||
|
||||
expect(executeWatch('exp', '2 >= 1')).toEqual(['exp=true']);
|
||||
expect(executeWatch('exp', '2 >= 2')).toEqual(['exp=true']);
|
||||
expect(executeWatch('exp', '1 >= 2')).toEqual(['exp=false']);
|
||||
|
||||
expect(executeWatch('exp', 'true && true')).toEqual(['exp=true']);
|
||||
expect(executeWatch('exp', 'true && false')).toEqual(['exp=false']);
|
||||
|
||||
expect(executeWatch('exp', 'true || false')).toEqual(['exp=true']);
|
||||
expect(executeWatch('exp', 'false || false')).toEqual(['exp=false']);
|
||||
});
|
||||
|
||||
it("should support negate", () => {
|
||||
expect(executeWatch('exp', '!true')).toEqual(['exp=false']);
|
||||
expect(executeWatch('exp', '!!true')).toEqual(['exp=true']);
|
||||
});
|
||||
|
||||
it("should support conditionals", () => {
|
||||
expect(executeWatch('m', '1 < 2 ? 1 : 2')).toEqual(['m=1']);
|
||||
expect(executeWatch('m', '1 > 2 ? 1 : 2')).toEqual(['m=2']);
|
||||
});
|
||||
|
||||
describe("keyed access", () => {
|
||||
it("should support accessing a list item", () => {
|
||||
expect(executeWatch('array[0]', '["foo", "bar"][0]')).toEqual(['array[0]=foo']);
|
||||
});
|
||||
|
||||
it("should support accessing a map item", () => {
|
||||
expect(executeWatch('map[foo]', '{"foo": "bar"}["foo"]')).toEqual(['map[foo]=bar']);
|
||||
});
|
||||
});
|
||||
|
||||
it("should support formatters", () => {
|
||||
var formatters = MapWrapper.createFromPairs([
|
||||
['uppercase', (v) => v.toUpperCase()],
|
||||
['wrap', (v, before, after) => `${before}${v}${after}`]]);
|
||||
expect(executeWatch('str', '"aBc" | uppercase', null, formatters)).toEqual(['str=ABC']);
|
||||
expect(executeWatch('str', '"b" | wrap:"a":"c"', null, formatters)).toEqual(['str=abc']);
|
||||
});
|
||||
|
||||
it("should support interpolation", () => {
|
||||
var parser = new Parser(new Lexer());
|
||||
var pcd = createProtoChangeDetector();
|
||||
var ast = parser.parseInterpolation("B{{a}}A", "location");
|
||||
pcd.addAst(ast, "memo", "memo", false);
|
||||
|
||||
var dispatcher = new TestDispatcher();
|
||||
var cd = pcd.instantiate(dispatcher, MapWrapper.create());
|
||||
cd.setContext(new TestData("value"));
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(dispatcher.log).toEqual(["memo=BvalueA"]);
|
||||
});
|
||||
|
||||
describe("group changes", () => {
|
||||
it("should notify the dispatcher when a group of records changes", () => {
|
||||
var pcd = createProtoChangeDetector();
|
||||
pcd.addAst(ast("1 + 2"), "memo", "1");
|
||||
pcd.addAst(ast("10 + 20"), "memo", "1");
|
||||
pcd.addAst(ast("100 + 200"), "memo2", "2");
|
||||
|
||||
var dispatcher = new TestDispatcher();
|
||||
var cd = pcd.instantiate(dispatcher, null);
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(dispatcher.loggedValues).toEqual([[3, 30], [300]]);
|
||||
});
|
||||
|
||||
it("should notify the dispatcher before switching to the next group", () => {
|
||||
var pcd = createProtoChangeDetector();
|
||||
pcd.addAst(ast("a()"), "a", "1");
|
||||
pcd.addAst(ast("b()"), "b", "2");
|
||||
pcd.addAst(ast("c()"), "c", "2");
|
||||
|
||||
var dispatcher = new TestDispatcher();
|
||||
var cd = pcd.instantiate(dispatcher, null);
|
||||
|
||||
var tr = new TestRecord();
|
||||
tr.a = () => {
|
||||
dispatcher.logValue('InvokeA');
|
||||
return 'a'
|
||||
};
|
||||
tr.b = () => {
|
||||
dispatcher.logValue('InvokeB');
|
||||
return 'b'
|
||||
};
|
||||
tr.c = () => {
|
||||
dispatcher.logValue('InvokeC');
|
||||
return 'c'
|
||||
};
|
||||
cd.setContext(tr);
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(dispatcher.loggedValues).toEqual(['InvokeA', ['a'], 'InvokeB', 'InvokeC', ['b', 'c']]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("enforce no new changes", () => {
|
||||
it("should throw when a record gets changed after it has been checked", () => {
|
||||
var pcd = createProtoChangeDetector();
|
||||
pcd.addAst(ast("a"), "a", 1);
|
||||
|
||||
var dispatcher = new TestDispatcher();
|
||||
var cd = pcd.instantiate(dispatcher, null);
|
||||
cd.setContext(new TestData('value'));
|
||||
|
||||
expect(() => {
|
||||
cd.checkNoChanges();
|
||||
}).toThrowError(new RegExp("Expression 'a in location' has changed after it was checked"));
|
||||
});
|
||||
});
|
||||
|
||||
//TODO vsavkin: implement it
|
||||
describe("error handling", () => {
|
||||
xit("should wrap exceptions into ChangeDetectionError", () => {
|
||||
var pcd = createProtoChangeDetector();
|
||||
pcd.addAst(ast('invalidProp', 'someComponent'), "a", 1);
|
||||
|
||||
var cd = pcd.instantiate(new TestDispatcher(), null);
|
||||
cd.setContext(null);
|
||||
|
||||
try {
|
||||
cd.detectChanges();
|
||||
|
||||
throw new BaseException("fail");
|
||||
} catch (e) {
|
||||
expect(e).toBeAnInstanceOf(ChangeDetectionError);
|
||||
expect(e.location).toEqual("invalidProp in someComponent");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("collections", () => {
|
||||
it("should not register a change when going from null to null", () => {
|
||||
var context = new TestData(null);
|
||||
|
||||
var c = createChangeDetector('a', 'a', context, null, true);
|
||||
var cd = c["changeDetector"];
|
||||
var dispatcher = c["dispatcher"];
|
||||
|
||||
cd.detectChanges();
|
||||
expect(dispatcher.log).toEqual([]);
|
||||
});
|
||||
|
||||
it("should register changes when switching from null to collection and back", () => {
|
||||
var context = new TestData(null);
|
||||
|
||||
var c = createChangeDetector('a', 'a', context, null, true);
|
||||
var cd = c["changeDetector"];
|
||||
var dispatcher = c["dispatcher"];
|
||||
|
||||
context.a = [0];
|
||||
cd.detectChanges();
|
||||
|
||||
expect(dispatcher.log).toEqual(["a=" +
|
||||
arrayChangesAsString({
|
||||
collection: ['0[null->0]'],
|
||||
additions: ['0[null->0]']
|
||||
})
|
||||
]);
|
||||
dispatcher.clear();
|
||||
|
||||
context.a = null;
|
||||
cd.detectChanges();
|
||||
expect(dispatcher.log).toEqual(['a=null']);
|
||||
});
|
||||
|
||||
describe("list", () => {
|
||||
it("should support list changes", () => {
|
||||
var context = new TestData([1, 2]);
|
||||
|
||||
expect(executeWatch("a", "a", context, null, true))
|
||||
.toEqual(["a=" +
|
||||
arrayChangesAsString({
|
||||
collection: ['1[null->0]', '2[null->1]'],
|
||||
additions: ['1[null->0]', '2[null->1]']
|
||||
})]);
|
||||
});
|
||||
|
||||
it("should handle reference changes", () => {
|
||||
var context = new TestData([1, 2]);
|
||||
var objs = createChangeDetector("a", "a", context, null, true);
|
||||
var cd = objs["changeDetector"];
|
||||
var dispatcher = objs["dispatcher"];
|
||||
cd.detectChanges();
|
||||
dispatcher.clear();
|
||||
|
||||
context.a = [2, 1];
|
||||
cd.detectChanges();
|
||||
expect(dispatcher.log).toEqual(["a=" +
|
||||
arrayChangesAsString({
|
||||
collection: ['2[1->0]', '1[0->1]'],
|
||||
previous: ['1[0->1]', '2[1->0]'],
|
||||
moves: ['2[1->0]', '1[0->1]']
|
||||
})]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("map", () => {
|
||||
it("should support map changes", () => {
|
||||
var map = MapWrapper.create();
|
||||
MapWrapper.set(map, "foo", "bar");
|
||||
var context = new TestData(map);
|
||||
expect(executeWatch("a", "a", context, null, true))
|
||||
.toEqual(["a=" +
|
||||
kvChangesAsString({
|
||||
map: ['foo[null->bar]'],
|
||||
additions: ['foo[null->bar]']
|
||||
})]);
|
||||
});
|
||||
|
||||
it("should handle reference changes", () => {
|
||||
var map = MapWrapper.create();
|
||||
MapWrapper.set(map, "foo", "bar");
|
||||
var context = new TestData(map);
|
||||
var objs = createChangeDetector("a", "a", context, null, true);
|
||||
var cd = objs["changeDetector"];
|
||||
var dispatcher = objs["dispatcher"];
|
||||
cd.detectChanges();
|
||||
dispatcher.clear();
|
||||
|
||||
context.a = MapWrapper.create();
|
||||
MapWrapper.set(context.a, "bar", "foo");
|
||||
cd.detectChanges();
|
||||
expect(dispatcher.log).toEqual(["a=" +
|
||||
kvChangesAsString({
|
||||
map: ['bar[null->foo]'],
|
||||
previous: ['foo[bar->null]'],
|
||||
additions: ['bar[null->foo]'],
|
||||
removals: ['foo[bar->null]']
|
||||
})]);
|
||||
});
|
||||
});
|
||||
|
||||
if (!IS_DARTIUM) {
|
||||
describe("js objects", () => {
|
||||
it("should support object changes", () => {
|
||||
var map = {"foo": "bar"};
|
||||
var context = new TestData(map);
|
||||
expect(executeWatch("a", "a", context, null, true))
|
||||
.toEqual(["a=" +
|
||||
kvChangesAsString({
|
||||
map: ['foo[null->bar]'],
|
||||
additions: ['foo[null->bar]']
|
||||
})]);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("ContextWithVariableBindings", () => {
|
||||
it('should read a field from ContextWithVariableBindings', () => {
|
||||
var locals = new ContextWithVariableBindings(null,
|
||||
MapWrapper.createFromPairs([["key", "value"]]));
|
||||
|
||||
expect(executeWatch('key', 'key', locals))
|
||||
.toEqual(['key=value']);
|
||||
});
|
||||
|
||||
it('should handle nested ContextWithVariableBindings', () => {
|
||||
var nested = new ContextWithVariableBindings(null,
|
||||
MapWrapper.createFromPairs([["key", "value"]]));
|
||||
var locals = new ContextWithVariableBindings(nested, MapWrapper.create());
|
||||
|
||||
expect(executeWatch('key', 'key', locals))
|
||||
.toEqual(['key=value']);
|
||||
});
|
||||
|
||||
it("should fall back to a regular field read when ContextWithVariableBindings " +
|
||||
"does not have the requested field", () => {
|
||||
var locals = new ContextWithVariableBindings(new Person("Jim"),
|
||||
MapWrapper.createFromPairs([["key", "value"]]));
|
||||
|
||||
expect(executeWatch('name', 'name', locals))
|
||||
.toEqual(['name=Jim']);
|
||||
});
|
||||
});
|
||||
|
||||
describe("handle children", () => {
|
||||
var parent, child;
|
||||
|
||||
beforeEach(() => {
|
||||
var protoParent = createProtoChangeDetector();
|
||||
parent = protoParent.instantiate(null, null);
|
||||
|
||||
var protoChild = createProtoChangeDetector();
|
||||
child = protoChild.instantiate(null, null);
|
||||
});
|
||||
|
||||
it("should add children", () => {
|
||||
parent.addChild(child);
|
||||
|
||||
expect(parent.children.length).toEqual(1);
|
||||
expect(parent.children[0]).toBe(child);
|
||||
});
|
||||
|
||||
it("should remove children", () => {
|
||||
parent.addChild(child);
|
||||
parent.removeChild(child);
|
||||
|
||||
expect(parent.children).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("optimizations", () => {
|
||||
it("should not rerun formatters when args did not change", () => {
|
||||
var count = 0;
|
||||
var formatters = MapWrapper.createFromPairs([
|
||||
['count', (v) => {count ++; "value"}]]);
|
||||
|
||||
var c = createChangeDetector('a', 'a | count', new TestData(null), formatters);
|
||||
var cd = c["changeDetector"];
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(count).toEqual(1);
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(count).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mode", () => {
|
||||
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.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.mode = CHECKED;
|
||||
cd.detectChanges();
|
||||
|
||||
expect(dispatcher.log).toEqual([]);
|
||||
});
|
||||
|
||||
it("should change CHECK_ONCE to CHECKED", () => {
|
||||
var cd = createProtoChangeDetector().instantiate(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, null);
|
||||
cd.mode = CHECK_ALWAYS;
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(cd.mode).toEqual(CHECK_ALWAYS);
|
||||
});
|
||||
});
|
||||
|
||||
describe("markPathToRootAsCheckOnce", () => {
|
||||
function changeDetector(mode, parent) {
|
||||
var cd = createProtoChangeDetector().instantiate(null, null);
|
||||
cd.mode = mode;
|
||||
if (isPresent(parent)) parent.addChild(cd);
|
||||
return cd;
|
||||
}
|
||||
|
||||
it("should mark all checked detectors as CHECK_ONCE " +
|
||||
"until reaching a detached one", () => {
|
||||
|
||||
var root = changeDetector(CHECK_ALWAYS, null);
|
||||
var disabled = changeDetector(DETACHED, root);
|
||||
var parent = changeDetector(CHECKED, disabled);
|
||||
var checkAlwaysChild = changeDetector(CHECK_ALWAYS, parent);
|
||||
var checkOnceChild = changeDetector(CHECK_ONCE, checkAlwaysChild);
|
||||
var checkedChild = changeDetector(CHECKED, checkOnceChild);
|
||||
|
||||
ChangeDetectionUtil.markPathToRootAsCheckOnce(checkedChild);
|
||||
|
||||
expect(root.mode).toEqual(CHECK_ALWAYS);
|
||||
expect(disabled.mode).toEqual(DETACHED);
|
||||
expect(parent.mode).toEqual(CHECK_ONCE);
|
||||
expect(checkAlwaysChild.mode).toEqual(CHECK_ALWAYS);
|
||||
expect(checkOnceChild.mode).toEqual(CHECK_ONCE);
|
||||
expect(checkedChild.mode).toEqual(CHECK_ONCE);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class TestRecord {
|
||||
a;
|
||||
b;
|
||||
c;
|
||||
}
|
||||
|
||||
class Person {
|
||||
name:string;
|
||||
age:number;
|
||||
address:Address;
|
||||
constructor(name:string, address:Address = null) {
|
||||
this.name = name;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
sayHi(m) {
|
||||
return `Hi, ${m}`;
|
||||
}
|
||||
|
||||
toString():string {
|
||||
var address = this.address == null ? '' : ' address=' + this.address.toString();
|
||||
|
||||
return 'name=' + this.name + address;
|
||||
}
|
||||
}
|
||||
|
||||
class Address {
|
||||
city:string;
|
||||
constructor(city:string) {
|
||||
this.city = city;
|
||||
}
|
||||
|
||||
toString():string {
|
||||
return this.city;
|
||||
}
|
||||
}
|
||||
|
||||
class Uninitialized {
|
||||
value:any;
|
||||
}
|
||||
|
||||
class TestData {
|
||||
a;
|
||||
|
||||
constructor(a) {
|
||||
this.a = a;
|
||||
}
|
||||
}
|
||||
|
||||
class TestDispatcher extends ChangeDispatcher {
|
||||
log:List;
|
||||
loggedValues:List;
|
||||
onChange:Function;
|
||||
|
||||
constructor() {
|
||||
this.log = null;
|
||||
this.loggedValues = null;
|
||||
this.onChange = (_, __) => {};
|
||||
this.clear();
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.log = ListWrapper.create();
|
||||
this.loggedValues = ListWrapper.create();
|
||||
}
|
||||
|
||||
logValue(value) {
|
||||
ListWrapper.push(this.loggedValues, value);
|
||||
}
|
||||
|
||||
onRecordChange(group, updates:List) {
|
||||
var value = updates[0].change.currentValue;
|
||||
var memento = updates[0].bindingMemento;
|
||||
ListWrapper.push(this.log, memento + '=' + this._asString(value));
|
||||
|
||||
var values = ListWrapper.map(updates, (r) => r.change.currentValue);
|
||||
ListWrapper.push(this.loggedValues, values);
|
||||
|
||||
this.onChange(group, updates);
|
||||
}
|
||||
|
||||
|
||||
_asString(value) {
|
||||
return (isBlank(value) ? 'null' : value.toString());
|
||||
}
|
||||
}
|
82
modules/angular2/test/change_detection/coalesce_spec.js
vendored
Normal file
82
modules/angular2/test/change_detection/coalesce_spec.js
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'test_lib/test_lib';
|
||||
|
||||
import {coalesce} from 'change_detection/src/coalesce';
|
||||
import {RECORD_TYPE_SELF, ProtoRecord} from 'change_detection/src/proto_change_detector';
|
||||
|
||||
export function main() {
|
||||
function r(funcOrValue, args, contextIndex, selfIndex, lastInBinding = false) {
|
||||
return new ProtoRecord(99, "name", funcOrValue, args, null, contextIndex, selfIndex,
|
||||
null, null, null, lastInBinding, false);
|
||||
}
|
||||
|
||||
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, true),
|
||||
r("user", [], 0, 2, true)
|
||||
]);
|
||||
|
||||
expect(rs[1]).toEqual(new ProtoRecord(
|
||||
RECORD_TYPE_SELF, "self", null,
|
||||
[], null, 1, 2,
|
||||
null, null, null,
|
||||
true, false)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
6
modules/angular2/test/change_detection/iterable.dart
Normal file
6
modules/angular2/test/change_detection/iterable.dart
Normal file
@ -0,0 +1,6 @@
|
||||
import 'dart:collection';
|
||||
|
||||
class TestIterable extends IterableBase<int> {
|
||||
List<int> list = [];
|
||||
Iterator<int> get iterator => list.iterator;
|
||||
}
|
9
modules/angular2/test/change_detection/iterable.es6
Normal file
9
modules/angular2/test/change_detection/iterable.es6
Normal file
@ -0,0 +1,9 @@
|
||||
export class TestIterable {
|
||||
constructor() {
|
||||
this.list = [];
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this.list[Symbol.iterator]();
|
||||
}
|
||||
}
|
190
modules/angular2/test/change_detection/keyvalue_changes_spec.js
vendored
Normal file
190
modules/angular2/test/change_detection/keyvalue_changes_spec.js
vendored
Normal file
@ -0,0 +1,190 @@
|
||||
import {describe, it, iit, xit, expect, beforeEach, afterEach} from 'test_lib/test_lib';
|
||||
import {KeyValueChanges} from 'change_detection/src/keyvalue_changes';
|
||||
import {NumberWrapper, isJsObject} from 'facade/src/lang';
|
||||
import {MapWrapper} from 'facade/src/collection';
|
||||
import {kvChangesAsString} from './util';
|
||||
|
||||
// todo(vicb): Update the code & tests for object equality
|
||||
export function main() {
|
||||
describe('keyvalue_changes', function() {
|
||||
describe('KeyValueChanges', function() {
|
||||
var changes;
|
||||
var m;
|
||||
|
||||
beforeEach(() => {
|
||||
changes = new KeyValueChanges();
|
||||
m = MapWrapper.create();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
changes = null;
|
||||
});
|
||||
|
||||
it('should detect additions', () => {
|
||||
changes.check(m);
|
||||
|
||||
MapWrapper.set(m, 'a', 1);
|
||||
changes.check(m);
|
||||
expect(changes.toString()).toEqual(kvChangesAsString({
|
||||
map: ['a[null->1]'],
|
||||
additions: ['a[null->1]']
|
||||
}));
|
||||
|
||||
MapWrapper.set(m, 'b', 2);
|
||||
changes.check(m);
|
||||
expect(changes.toString()).toEqual(kvChangesAsString({
|
||||
map: ['a', 'b[null->2]'],
|
||||
previous: ['a'],
|
||||
additions: ['b[null->2]']
|
||||
}));
|
||||
});
|
||||
|
||||
it('should handle changing key/values correctly', () => {
|
||||
MapWrapper.set(m, 1, 10);
|
||||
MapWrapper.set(m, 2, 20);
|
||||
changes.check(m);
|
||||
|
||||
MapWrapper.set(m, 2, 10);
|
||||
MapWrapper.set(m, 1, 20);
|
||||
changes.check(m);
|
||||
expect(changes.toString()).toEqual(kvChangesAsString({
|
||||
map: ['1[10->20]', '2[20->10]'],
|
||||
previous: ['1[10->20]', '2[20->10]'],
|
||||
changes: ['1[10->20]', '2[20->10]']
|
||||
}));
|
||||
});
|
||||
|
||||
it('should do basic map watching', () => {
|
||||
changes.check(m);
|
||||
|
||||
MapWrapper.set(m, 'a', 'A');
|
||||
changes.check(m);
|
||||
expect(changes.toString()).toEqual(kvChangesAsString({
|
||||
map: ['a[null->A]'],
|
||||
additions: ['a[null->A]']
|
||||
}));
|
||||
|
||||
MapWrapper.set(m, 'b', 'B');
|
||||
changes.check(m);
|
||||
expect(changes.toString()).toEqual(kvChangesAsString({
|
||||
map: ['a', 'b[null->B]'],
|
||||
previous: ['a'],
|
||||
additions: ['b[null->B]']
|
||||
}));
|
||||
|
||||
MapWrapper.set(m, 'b', 'BB');
|
||||
MapWrapper.set(m, 'd', 'D');
|
||||
changes.check(m);
|
||||
expect(changes.toString()).toEqual(kvChangesAsString({
|
||||
map: ['a', 'b[B->BB]', 'd[null->D]'],
|
||||
previous: ['a', 'b[B->BB]'],
|
||||
additions: ['d[null->D]'],
|
||||
changes: ['b[B->BB]']
|
||||
}));
|
||||
|
||||
MapWrapper.delete(m, 'b');
|
||||
changes.check(m);
|
||||
expect(changes.toString()).toEqual(kvChangesAsString({
|
||||
map: ['a', 'd'],
|
||||
previous: ['a', 'b[BB->null]', 'd'],
|
||||
removals: ['b[BB->null]']
|
||||
}));
|
||||
|
||||
MapWrapper.clear(m);
|
||||
changes.check(m);
|
||||
expect(changes.toString()).toEqual(kvChangesAsString({
|
||||
previous: ['a[A->null]', 'd[D->null]'],
|
||||
removals: ['a[A->null]', 'd[D->null]']
|
||||
}));
|
||||
});
|
||||
|
||||
it('should test string by value rather than by reference (DART)', () => {
|
||||
MapWrapper.set(m, 'foo', 'bar');
|
||||
changes.check(m);
|
||||
|
||||
var f = 'f';
|
||||
var oo = 'oo';
|
||||
var b = 'b';
|
||||
var ar = 'ar';
|
||||
|
||||
MapWrapper.set(m, f + oo, b + ar);
|
||||
changes.check(m);
|
||||
|
||||
expect(changes.toString()).toEqual(kvChangesAsString({
|
||||
map: ['foo'],
|
||||
previous: ['foo']
|
||||
}));
|
||||
});
|
||||
|
||||
it('should not see a NaN value as a change (JS)', () => {
|
||||
MapWrapper.set(m, 'foo', NumberWrapper.NaN);
|
||||
changes.check(m);
|
||||
|
||||
changes.check(m);
|
||||
expect(changes.toString()).toEqual(kvChangesAsString({
|
||||
map: ['foo'],
|
||||
previous: ['foo']
|
||||
}));
|
||||
});
|
||||
|
||||
// JS specific tests (JS Objects)
|
||||
if (isJsObject({})) {
|
||||
describe('JsObject changes', () => {
|
||||
it('should support JS Object', () => {
|
||||
expect(KeyValueChanges.supports({})).toBeTruthy();
|
||||
expect(KeyValueChanges.supports("not supported")).toBeFalsy();
|
||||
expect(KeyValueChanges.supports(0)).toBeFalsy();
|
||||
expect(KeyValueChanges.supports(null)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should do basic object watching', () => {
|
||||
m = {};
|
||||
changes.check(m);
|
||||
|
||||
m['a'] = 'A';
|
||||
changes.check(m);
|
||||
expect(changes.toString()).toEqual(kvChangesAsString({
|
||||
map: ['a[null->A]'],
|
||||
additions: ['a[null->A]']
|
||||
}));
|
||||
|
||||
m['b'] = 'B';
|
||||
changes.check(m);
|
||||
expect(changes.toString()).toEqual(kvChangesAsString({
|
||||
map: ['a', 'b[null->B]'],
|
||||
previous: ['a'],
|
||||
additions: ['b[null->B]']
|
||||
}));
|
||||
|
||||
m['b'] = 'BB';
|
||||
m['d'] = 'D';
|
||||
changes.check(m);
|
||||
expect(changes.toString()).toEqual(kvChangesAsString({
|
||||
map: ['a', 'b[B->BB]', 'd[null->D]'],
|
||||
previous: ['a', 'b[B->BB]'],
|
||||
additions: ['d[null->D]'],
|
||||
changes: ['b[B->BB]']
|
||||
}));
|
||||
|
||||
m = {};
|
||||
m['a'] = 'A';
|
||||
m['d'] = 'D';
|
||||
changes.check(m);
|
||||
expect(changes.toString()).toEqual(kvChangesAsString({
|
||||
map: ['a', 'd'],
|
||||
previous: ['a', 'b[BB->null]', 'd'],
|
||||
removals: ['b[BB->null]']
|
||||
}));
|
||||
|
||||
m = {};
|
||||
changes.check(m);
|
||||
expect(changes.toString()).toEqual(kvChangesAsString({
|
||||
previous: ['a[A->null]', 'd[D->null]'],
|
||||
removals: ['a[A->null]', 'd[D->null]']
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
44
modules/angular2/test/change_detection/parser/context_with_variable_bindings_spec.js
vendored
Normal file
44
modules/angular2/test/change_detection/parser/context_with_variable_bindings_spec.js
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
import {ddescribe, describe, it, xit, iit, expect, beforeEach} from 'test_lib/test_lib';
|
||||
|
||||
import {ContextWithVariableBindings} from 'change_detection/src/parser/context_with_variable_bindings';
|
||||
|
||||
import {BaseException, isBlank, isPresent} from 'facade/src/lang';
|
||||
import {MapWrapper, ListWrapper} from 'facade/src/collection';
|
||||
|
||||
export function main() {
|
||||
describe('ContextWithVariableBindings', () => {
|
||||
var locals;
|
||||
beforeEach(() => {
|
||||
locals = new ContextWithVariableBindings(null,
|
||||
MapWrapper.createFromPairs([['key', 'value'], ['nullKey', null]]));
|
||||
});
|
||||
|
||||
it('should support getting values', () => {
|
||||
expect(locals.get('key')).toBe('value');
|
||||
|
||||
var notPresentValue = locals.get('notPresent');
|
||||
expect(isPresent(notPresentValue)).toBe(false);
|
||||
});
|
||||
|
||||
it('should support checking if key is persent', () => {
|
||||
expect(locals.hasBinding('key')).toBe(true);
|
||||
expect(locals.hasBinding('nullKey')).toBe(true);
|
||||
expect(locals.hasBinding('notPresent')).toBe(false);
|
||||
});
|
||||
|
||||
it('should support setting persent 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.clearValues();
|
||||
expect(locals.get('key')).toBe(null);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
249
modules/angular2/test/change_detection/parser/lexer_spec.js
vendored
Normal file
249
modules/angular2/test/change_detection/parser/lexer_spec.js
vendored
Normal file
@ -0,0 +1,249 @@
|
||||
import {describe, it, expect} from 'test_lib/test_lib';
|
||||
|
||||
import {Lexer, Token} from 'change_detection/src/parser/lexer';
|
||||
|
||||
import {List, ListWrapper} from "facade/src/collection";
|
||||
import {StringWrapper, int} from "facade/src/lang";
|
||||
|
||||
function lex(text:string):List {
|
||||
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:List<int> = lex("j");
|
||||
expect(tokens.length).toEqual(1);
|
||||
expectIdentifierToken(tokens[0], 0, 'j');
|
||||
});
|
||||
|
||||
it('should tokenize a dotted identifier', function() {
|
||||
var tokens:List<int> = 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:List<int> = lex("j-k");
|
||||
expect(tokens.length).toEqual(3);
|
||||
expectOperatorToken(tokens[1], 1, '-');
|
||||
});
|
||||
|
||||
it('should tokenize an indexed operator', function() {
|
||||
var tokens:List<int> = lex("j[k]");
|
||||
expect(tokens.length).toEqual(4);
|
||||
expectCharacterToken(tokens[1], 1, "[");
|
||||
expectCharacterToken(tokens[3], 3, "]");
|
||||
});
|
||||
|
||||
it('should tokenize numbers', function() {
|
||||
var tokens:List<int> = 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:List<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:List<Token> = lex("undefined");
|
||||
expectKeywordToken(tokens[0], 0, "undefined");
|
||||
expect(tokens[0].isKeywordUndefined()).toBe(true);
|
||||
});
|
||||
|
||||
it('should ignore whitespace', function() {
|
||||
var tokens:List<Token> = lex("a \t \n \r b");
|
||||
expectIdentifierToken(tokens[0], 0, 'a');
|
||||
expectIdentifierToken(tokens[1], 8, 'b');
|
||||
});
|
||||
|
||||
it('should tokenize quoted string', function() {
|
||||
var str = "['\\'', \"\\\"\"]";
|
||||
var tokens:List<Token> = lex(str);
|
||||
expectStringToken(tokens[1], 1, "'");
|
||||
expectStringToken(tokens[3], 7, '"');
|
||||
});
|
||||
|
||||
it('should tokenize escaped quoted string', function() {
|
||||
var str = '"\\"\\n\\f\\r\\t\\v\\u00A0"';
|
||||
var tokens:List<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:List<Token> = lex('"\\u00A0"');
|
||||
expect(tokens.length).toEqual(1);
|
||||
expect(tokens[0].toString()).toEqual('\u00a0');
|
||||
});
|
||||
|
||||
it('should tokenize relation', function() {
|
||||
var tokens:List<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, '>=');
|
||||
});
|
||||
|
||||
it('should tokenize statements', function() {
|
||||
var tokens:List<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:List<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:List<Token> = lex("a.method()");
|
||||
expectIdentifierToken(tokens[2], 2, 'method');
|
||||
});
|
||||
|
||||
it('should tokenize method invocation', function() {
|
||||
var tokens:List<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:List<Token> = lex("0.5");
|
||||
expectNumberToken(tokens[0], 0, 0.5);
|
||||
});
|
||||
|
||||
// NOTE(deboer): NOT A LEXER TEST
|
||||
// it('should tokenize negative number', function() {
|
||||
// var tokens:List<Token> = lex("-0.5");
|
||||
// expectNumberToken(tokens[0], 0, -0.5);
|
||||
// });
|
||||
|
||||
it('should tokenize number with exponent', function() {
|
||||
var tokens:List<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(function() {
|
||||
lex("0.5E-");
|
||||
}).toThrowError('Lexer Error: Invalid exponent at column 4 in expression [0.5E-]');
|
||||
|
||||
expect(function() {
|
||||
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:List<Token> = lex(".5");
|
||||
expectNumberToken(tokens[0], 0, 0.5);
|
||||
});
|
||||
|
||||
it('should throw error on invalid unicode', function() {
|
||||
expect(function() {
|
||||
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:List<Token> = lex("#");
|
||||
expectOperatorToken(tokens[0], 0, '#');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
548
modules/angular2/test/change_detection/parser/parser_spec.js
vendored
Normal file
548
modules/angular2/test/change_detection/parser/parser_spec.js
vendored
Normal file
@ -0,0 +1,548 @@
|
||||
import {ddescribe, describe, it, xit, iit, expect, beforeEach} from 'test_lib/test_lib';
|
||||
import {BaseException, isBlank, isPresent} from 'facade/src/lang';
|
||||
import {reflector} from 'reflection/src/reflection';
|
||||
import {MapWrapper, ListWrapper} from 'facade/src/collection';
|
||||
import {Parser} from 'change_detection/src/parser/parser';
|
||||
import {Lexer} from 'change_detection/src/parser/lexer';
|
||||
import {ContextWithVariableBindings} from 'change_detection/src/parser/context_with_variable_bindings';
|
||||
import {Formatter, LiteralPrimitive} from 'change_detection/src/parser/ast';
|
||||
|
||||
class TestData {
|
||||
a;
|
||||
b;
|
||||
fnReturnValue;
|
||||
constructor(a, b, fnReturnValue) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.fnReturnValue = fnReturnValue;
|
||||
}
|
||||
|
||||
fn() {
|
||||
return this.fnReturnValue;
|
||||
}
|
||||
|
||||
add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
|
||||
export function main() {
|
||||
function td(a = 0, b = 0, fnReturnValue = "constant") {
|
||||
return new TestData(a, b, fnReturnValue);
|
||||
}
|
||||
|
||||
function createParser() {
|
||||
return new Parser(new Lexer(), reflector);
|
||||
}
|
||||
|
||||
function parseAction(text, location = null) {
|
||||
return createParser().parseAction(text, location);
|
||||
}
|
||||
|
||||
function parseBinding(text, location = null) {
|
||||
return createParser().parseBinding(text, location);
|
||||
}
|
||||
|
||||
function parseTemplateBindings(text, location = null) {
|
||||
return createParser().parseTemplateBindings(text, location);
|
||||
}
|
||||
|
||||
function parseInterpolation(text, location = null) {
|
||||
return createParser().parseInterpolation(text, location);
|
||||
}
|
||||
|
||||
function expectEval(text, passedInContext = null) {
|
||||
var c = isBlank(passedInContext) ? td() : passedInContext;
|
||||
return expect(parseAction(text).eval(c));
|
||||
}
|
||||
|
||||
function expectEvalError(text, passedInContext = null) {
|
||||
var c = isBlank(passedInContext) ? td() : passedInContext;
|
||||
return expect(() => parseAction(text).eval(c));
|
||||
}
|
||||
|
||||
function evalAsts(asts, passedInContext = null) {
|
||||
var c = isBlank(passedInContext) ? td() : passedInContext;
|
||||
var res = [];
|
||||
for (var i=0; i<asts.length; i++) {
|
||||
ListWrapper.push(res, asts[i].eval(c));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
describe("parser", () => {
|
||||
describe("parseAction", () => {
|
||||
describe("basic expressions", () => {
|
||||
it('should parse numerical expressions', () => {
|
||||
expectEval("1").toEqual(1);
|
||||
});
|
||||
|
||||
it('should parse strings', () => {
|
||||
expectEval("'1'").toEqual('1');
|
||||
expectEval('"1"').toEqual('1');
|
||||
});
|
||||
|
||||
it('should parse null', () => {
|
||||
expectEval("null").toBe(null);
|
||||
});
|
||||
|
||||
it('should parse unary - expressions', () => {
|
||||
expectEval("-1").toEqual(-1);
|
||||
expectEval("+1").toEqual(1);
|
||||
});
|
||||
|
||||
it('should parse unary ! expressions', () => {
|
||||
expectEval("!true").toEqual(!true);
|
||||
expectEval("!!true").toEqual(!!true);
|
||||
});
|
||||
|
||||
it('should parse multiplicative expressions', () => {
|
||||
expectEval("3*4/2%5").toEqual(3 * 4 / 2 % 5);
|
||||
});
|
||||
|
||||
it('should parse additive expressions', () => {
|
||||
expectEval("3+6-2").toEqual(3 + 6 - 2);
|
||||
});
|
||||
|
||||
it('should parse relational expressions', () => {
|
||||
expectEval("2<3").toEqual(2 < 3);
|
||||
expectEval("2>3").toEqual(2 > 3);
|
||||
expectEval("2<=2").toEqual(2 <= 2);
|
||||
expectEval("2>=2").toEqual(2 >= 2);
|
||||
});
|
||||
|
||||
it('should parse equality expressions', () => {
|
||||
expectEval("2==3").toEqual(2 == 3);
|
||||
expectEval("2!=3").toEqual(2 != 3);
|
||||
});
|
||||
|
||||
it('should parse logicalAND expressions', () => {
|
||||
expectEval("true&&true").toEqual(true && true);
|
||||
expectEval("true&&false").toEqual(true && false);
|
||||
});
|
||||
|
||||
it('should parse logicalOR expressions', () => {
|
||||
expectEval("false||true").toEqual(false || true);
|
||||
expectEval("false||false").toEqual(false || false);
|
||||
});
|
||||
|
||||
it('should short-circuit AND operator', () => {
|
||||
expectEval('false && a()', td(() => {throw "BOOM"})).toBe(false);
|
||||
});
|
||||
|
||||
it('should short-circuit OR operator', () => {
|
||||
expectEval('true || a()', td(() => {throw "BOOM"})).toBe(true);
|
||||
});
|
||||
|
||||
it('should evaluate grouped expressions', () => {
|
||||
expectEval("(1+2)*3").toEqual((1+2)*3);
|
||||
});
|
||||
|
||||
it('should parse an empty string', () => {
|
||||
expectEval('').toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("literals", () => {
|
||||
it('should evaluate array', () => {
|
||||
expectEval("[1][0]").toEqual(1);
|
||||
expectEval("[[1]][0][0]").toEqual(1);
|
||||
expectEval("[]").toEqual([]);
|
||||
expectEval("[].length").toEqual(0);
|
||||
expectEval("[1, 2].length").toEqual(2);
|
||||
});
|
||||
|
||||
it('should evaluate map', () => {
|
||||
expectEval("{}").toEqual({});
|
||||
expectEval("{a:'b'}['a']").toEqual('b');
|
||||
expectEval("{'a':'b'}['a']").toEqual('b');
|
||||
expectEval("{\"a\":'b'}['a']").toEqual('b');
|
||||
expectEval("{\"a\":'b'}['a']").toEqual("b");
|
||||
expectEval("{}['a']").not.toBeDefined();
|
||||
expectEval("{\"a\":'b'}['invalid']").not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should only allow identifier, string, or keyword as map key', () => {
|
||||
expectEvalError('{(:0}').toThrowError(new RegExp('expected identifier, keyword, or string'));
|
||||
expectEvalError('{1234:0}').toThrowError(new RegExp('expected identifier, keyword, or string'));
|
||||
});
|
||||
});
|
||||
|
||||
describe("member access", () => {
|
||||
it("should parse field access", () => {
|
||||
expectEval("a", td(999)).toEqual(999);
|
||||
expectEval("a.a", td(td(999))).toEqual(999);
|
||||
});
|
||||
|
||||
it('should throw when accessing a field on null', () => {
|
||||
expectEvalError("a.a.a").toThrowError();
|
||||
});
|
||||
|
||||
it('should only allow identifier or keyword as member names', () => {
|
||||
expectEvalError('x.(').toThrowError(new RegExp('identifier or keyword'));
|
||||
expectEvalError('x. 1234').toThrowError(new RegExp('identifier or keyword'));
|
||||
expectEvalError('x."foo"').toThrowError(new RegExp('identifier or keyword'));
|
||||
});
|
||||
|
||||
it("should read a field from ContextWithVariableBindings", () => {
|
||||
var locals = new ContextWithVariableBindings(null,
|
||||
MapWrapper.createFromPairs([["key", "value"]]));
|
||||
expectEval("key", locals).toEqual("value");
|
||||
});
|
||||
|
||||
it("should handle nested ContextWithVariableBindings", () => {
|
||||
var nested = new ContextWithVariableBindings(null,
|
||||
MapWrapper.createFromPairs([["key", "value"]]));
|
||||
var locals = new ContextWithVariableBindings(nested, MapWrapper.create());
|
||||
expectEval("key", locals).toEqual("value");
|
||||
});
|
||||
|
||||
it("should fall back to a regular field read when ContextWithVariableBindings "+
|
||||
"does not have the requested field", () => {
|
||||
var locals = new ContextWithVariableBindings(td(999), MapWrapper.create());
|
||||
expectEval("a", locals).toEqual(999);
|
||||
});
|
||||
});
|
||||
|
||||
describe("method calls", () => {
|
||||
it("should evaluate method calls", () => {
|
||||
expectEval("fn()", td(0, 0, "constant")).toEqual("constant");
|
||||
expectEval("add(1,2)").toEqual(3);
|
||||
expectEval("a.add(1,2)", td(td())).toEqual(3);
|
||||
expectEval("fn().add(1,2)", td(0, 0, td())).toEqual(3);
|
||||
});
|
||||
|
||||
it('should throw when no method', () => {
|
||||
expectEvalError("blah()").toThrowError();
|
||||
});
|
||||
|
||||
it('should evaluate a method from ContextWithVariableBindings', () => {
|
||||
var context = new ContextWithVariableBindings(
|
||||
td(0, 0, 'parent'),
|
||||
MapWrapper.createFromPairs([['fn', () => 'child']])
|
||||
);
|
||||
expectEval("fn()", context).toEqual('child');
|
||||
});
|
||||
|
||||
it('should fall back to the parent context when ContextWithVariableBindings does not ' +
|
||||
'have the requested method', () => {
|
||||
var context = new ContextWithVariableBindings(
|
||||
td(0, 0, 'parent'),
|
||||
MapWrapper.create()
|
||||
);
|
||||
expectEval("fn()", context).toEqual('parent');
|
||||
});
|
||||
});
|
||||
|
||||
describe("functional calls", () => {
|
||||
it("should evaluate function calls", () => {
|
||||
expectEval("fn()(1,2)", td(0, 0, (a, b) => a + b)).toEqual(3);
|
||||
});
|
||||
|
||||
it('should throw on non-function function calls', () => {
|
||||
expectEvalError("4()").toThrowError(new RegExp('4 is not a function'));
|
||||
});
|
||||
|
||||
it('should parse functions for object indices', () => {
|
||||
expectEval('a[b()]()', td([()=>6], () => 0)).toEqual(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe("conditional", () => {
|
||||
it('should parse ternary/conditional expressions', () => {
|
||||
expectEval("7==3+4?10:20").toEqual(10);
|
||||
expectEval("false?10:20").toEqual(20);
|
||||
});
|
||||
|
||||
it('should throw on incorrect ternary operator syntax', () => {
|
||||
expectEvalError("true?1").
|
||||
toThrowError(new RegExp('Parser Error: Conditional expression true\\?1 requires all 3 expressions'));
|
||||
});
|
||||
});
|
||||
|
||||
describe("assignment", () => {
|
||||
it("should support field assignments", () => {
|
||||
var context = td();
|
||||
expectEval("a=12", context).toEqual(12);
|
||||
expect(context.a).toEqual(12);
|
||||
});
|
||||
|
||||
it("should support nested field assignments", () => {
|
||||
var context = td(td(td()));
|
||||
expectEval("a.a.a=123;", context).toEqual(123);
|
||||
expect(context.a.a.a).toEqual(123);
|
||||
});
|
||||
|
||||
it("should support multiple assignments", () => {
|
||||
var context = td();
|
||||
expectEval("a=123; b=234", context).toEqual(234);
|
||||
expect(context.a).toEqual(123);
|
||||
expect(context.b).toEqual(234);
|
||||
});
|
||||
|
||||
it("should support array updates", () => {
|
||||
var context = td([100]);
|
||||
expectEval('a[0] = 200', context).toEqual(200);
|
||||
expect(context.a[0]).toEqual(200);
|
||||
});
|
||||
|
||||
it("should support map updates", () => {
|
||||
var context = td({"key" : 100});
|
||||
expectEval('a["key"] = 200', context).toEqual(200);
|
||||
expect(context.a["key"]).toEqual(200);
|
||||
});
|
||||
|
||||
it("should support array/map updates", () => {
|
||||
var context = td([{"key" : 100}]);
|
||||
expectEval('a[0]["key"] = 200', context).toEqual(200);
|
||||
expect(context.a[0]["key"]).toEqual(200);
|
||||
});
|
||||
|
||||
it('should allow assignment after array dereference', () => {
|
||||
var context = td([td()]);
|
||||
expectEval('a[0].a = 200', context).toEqual(200);
|
||||
expect(context.a[0].a).toEqual(200);
|
||||
});
|
||||
|
||||
it('should throw on bad assignment', () => {
|
||||
expectEvalError("5=4").toThrowError(new RegExp("Expression 5 is not assignable"));
|
||||
});
|
||||
|
||||
it('should reassign when no variable binding with the given name', () => {
|
||||
var context = td();
|
||||
var locals = new ContextWithVariableBindings(context, MapWrapper.create());
|
||||
expectEval('a = 200', locals).toEqual(200);
|
||||
expect(context.a).toEqual(200);
|
||||
});
|
||||
|
||||
it('should throw when reassigning a variable binding', () => {
|
||||
var locals = new ContextWithVariableBindings(null, MapWrapper.createFromPairs([["key", "value"]]));
|
||||
expectEvalError('key = 200', locals).toThrowError(new RegExp("Cannot reassign a variable binding"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("general error handling", () => {
|
||||
it("should throw on an unexpected token", () => {
|
||||
expectEvalError("[1,2] trac")
|
||||
.toThrowError(new RegExp('Unexpected token \'trac\''));
|
||||
});
|
||||
|
||||
it('should throw a reasonable error for unconsumed tokens', () => {
|
||||
expectEvalError(")").toThrowError(new RegExp("Unexpected token \\) at column 1 in \\[\\)\\]"));
|
||||
});
|
||||
|
||||
it('should throw on missing expected token', () => {
|
||||
expectEvalError("a(b").toThrowError(new RegExp("Missing expected \\) at the end of the expression \\[a\\(b\\]"));
|
||||
});
|
||||
});
|
||||
|
||||
it("should error when using formatters", () => {
|
||||
expectEvalError('x|blah').toThrowError(new RegExp('Cannot have a formatter'));
|
||||
});
|
||||
|
||||
it('should pass exceptions', () => {
|
||||
expect(() => {
|
||||
parseAction('a()').eval(td(() => {throw new BaseException("boo to you")}));
|
||||
}).toThrowError('boo to you');
|
||||
});
|
||||
|
||||
describe("multiple statements", () => {
|
||||
it("should return the last non-blank value", () => {
|
||||
expectEval("a=1;b=3;a+b").toEqual(4);
|
||||
expectEval("1;;").toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseBinding", () => {
|
||||
describe("formatters", () => {
|
||||
it("should parse formatters", () => {
|
||||
var exp = parseBinding("'Foo'|uppercase").ast;
|
||||
expect(exp).toBeAnInstanceOf(Formatter);
|
||||
expect(exp.name).toEqual("uppercase");
|
||||
});
|
||||
|
||||
it("should parse formatters with args", () => {
|
||||
var exp = parseBinding("1|increment:2").ast;
|
||||
expect(exp).toBeAnInstanceOf(Formatter);
|
||||
expect(exp.name).toEqual("increment");
|
||||
expect(exp.args[0]).toBeAnInstanceOf(LiteralPrimitive);
|
||||
});
|
||||
|
||||
it('should only allow identifier or keyword as formatter names', () => {
|
||||
expect(() => parseBinding('"Foo"|(')).toThrowError(new RegExp('identifier or keyword'));
|
||||
expect(() => parseBinding('"Foo"|1234')).toThrowError(new RegExp('identifier or keyword'));
|
||||
expect(() => parseBinding('"Foo"|"uppercase"')).toThrowError(new RegExp('identifier or keyword'));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
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 assignmnt', () => {
|
||||
expect(() => parseBinding("1;2")).toThrowError(new RegExp("contain chained expression"));
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseTemplateBindings', () => {
|
||||
|
||||
function keys(templateBindings) {
|
||||
return ListWrapper.map(templateBindings, (binding) => binding.key );
|
||||
}
|
||||
|
||||
function keyValues(templateBindings) {
|
||||
return ListWrapper.map(templateBindings, (binding) => {
|
||||
if (binding.keyIsVar) {
|
||||
return '#' + binding.key + (isBlank(binding.name) ? '' : '=' + binding.name);
|
||||
} else {
|
||||
return binding.key + (isBlank(binding.expression) ? '' : `=${binding.expression}`)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function exprSources(templateBindings) {
|
||||
return ListWrapper.map(templateBindings,
|
||||
(binding) => isPresent(binding.expression) ? binding.expression.source : null );
|
||||
}
|
||||
|
||||
function exprAsts(templateBindings) {
|
||||
return ListWrapper.map(templateBindings,
|
||||
(binding) => isPresent(binding.expression) ? binding.expression : null );
|
||||
}
|
||||
|
||||
it('should parse an empty string', () => {
|
||||
var bindings = parseTemplateBindings('');
|
||||
expect(bindings).toEqual([]);
|
||||
});
|
||||
|
||||
it('should parse a string without a value', () => {
|
||||
var bindings = parseTemplateBindings('a');
|
||||
expect(keys(bindings)).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']);
|
||||
expect(evalAsts(exprAsts(bindings), td(0, 23))).toEqual([23]);
|
||||
|
||||
bindings = parseTemplateBindings("a:1+1");
|
||||
expect(exprSources(bindings)).toEqual(['1+1']);
|
||||
expect(evalAsts(exprAsts(bindings))).toEqual([2]);
|
||||
});
|
||||
|
||||
it('should detect names as value', () => {
|
||||
var bindings = parseTemplateBindings("a:#b");
|
||||
expect(keyValues(bindings)).toEqual(['a', '#b']);
|
||||
});
|
||||
|
||||
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', 'b']);
|
||||
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']);
|
||||
|
||||
bindings = parseTemplateBindings("#i");
|
||||
expect(keyValues(bindings)).toEqual(['#i']);
|
||||
|
||||
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', 'in=expr in location', '#a=b']);
|
||||
});
|
||||
});
|
||||
|
||||
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 ast = parseInterpolation('before{{a}}middle{{b}}after').ast;
|
||||
expect(ast.strings).toEqual(['before', 'middle', 'after']);
|
||||
expect(ast.expressions.length).toEqual(2);
|
||||
expect(ast.expressions[0].name).toEqual('a');
|
||||
expect(ast.expressions[1].name).toEqual('b');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
29
modules/angular2/test/change_detection/util.js
vendored
Normal file
29
modules/angular2/test/change_detection/util.js
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
import {isBlank} from 'facade/src/lang';
|
||||
|
||||
export function arrayChangesAsString({collection, previous, additions, moves, removals}) {
|
||||
if (isBlank(collection)) collection = [];
|
||||
if (isBlank(previous)) previous = [];
|
||||
if (isBlank(additions)) additions = [];
|
||||
if (isBlank(moves)) moves = [];
|
||||
if (isBlank(removals)) removals = [];
|
||||
|
||||
return "collection: " + collection.join(', ') + "\n" +
|
||||
"previous: " + previous.join(', ') + "\n" +
|
||||
"additions: " + additions.join(', ') + "\n" +
|
||||
"moves: " + moves.join(', ') + "\n" +
|
||||
"removals: " + removals.join(', ') + "\n";
|
||||
}
|
||||
|
||||
export function kvChangesAsString({map, previous, additions, changes, removals}) {
|
||||
if (isBlank(map)) map = [];
|
||||
if (isBlank(previous)) previous = [];
|
||||
if (isBlank(additions)) additions = [];
|
||||
if (isBlank(changes)) changes = [];
|
||||
if (isBlank(removals)) removals = [];
|
||||
|
||||
return "map: " + map.join(', ') + "\n" +
|
||||
"previous: " + previous.join(', ') + "\n" +
|
||||
"additions: " + additions.join(', ') + "\n" +
|
||||
"changes: " + changes.join(', ') + "\n" +
|
||||
"removals: " + removals.join(', ') + "\n";
|
||||
}
|
Reference in New Issue
Block a user