chore(packaging): move files to match target file structure

This commit is contained in:
Yegor Jbanov
2015-02-04 23:05:13 -08:00
parent 7ce4f66cdc
commit 3820609f24
149 changed files with 0 additions and 77 deletions

View 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]']
}));
});
});
});
}

View 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());
}
}

View 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)
);
});
});
}

View File

@ -0,0 +1,6 @@
import 'dart:collection';
class TestIterable extends IterableBase<int> {
List<int> list = [];
Iterator<int> get iterator => list.iterator;
}

View File

@ -0,0 +1,9 @@
export class TestIterable {
constructor() {
this.list = [];
}
[Symbol.iterator]() {
return this.list[Symbol.iterator]();
}
}

View 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]']
}));
});
});
}
});
});
}

View 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);
});
})
}

View 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, '#');
});
});
});
}

View 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');
});
});
});
}

View 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";
}