Index: third_party/pkg/angular/test/core/parser/parser_spec.dart |
diff --git a/third_party/pkg/angular/test/core/parser/parser_spec.dart b/third_party/pkg/angular/test/core/parser/parser_spec.dart |
index 6db4a5b3fc5977aac042de390b8843bea5b6dbfe..0957470a1c33b00849ccae23a74773c5fde68df4 100644 |
--- a/third_party/pkg/angular/test/core/parser/parser_spec.dart |
+++ b/third_party/pkg/angular/test/core/parser/parser_spec.dart |
@@ -10,6 +10,8 @@ class TestData { |
set str(x) => _str = x; |
method() => "testMethod"; |
+ sub1(a, {b: 0}) => a - b; |
+ sub2({a: 0, b: 0}) => a - b; |
} |
class Ident { |
@@ -47,18 +49,20 @@ main() { |
describe('parse', () { |
Map<String, dynamic> context; |
Parser<Expression> parser; |
- FilterMap filters; |
- beforeEach(module((Module module) { |
+ FormatterMap formatters; |
+ |
+ beforeEachModule((Module module) { |
module.type(IncrementFilter); |
module.type(SubstringFilter); |
- })); |
- beforeEach(inject((Parser injectedParser, FilterMap injectedFilters) { |
+ }); |
+ |
+ beforeEach((Parser injectedParser, FormatterMap injectedFilters) { |
parser = injectedParser; |
- filters = injectedFilters; |
- })); |
+ formatters = injectedFilters; |
+ }); |
- eval(String text, [FilterMap f]) |
- => parser(text).eval(context, f == null ? filters : f); |
+ eval(String text, [FormatterMap f]) => |
+ parser(text).eval(context, f == null ? formatters : f); |
expectEval(String expr) => expect(() => eval(expr)); |
beforeEach((){ context = {}; }); |
@@ -154,9 +158,9 @@ main() { |
describe('error handling', () { |
Parser<Expression> parser; |
- beforeEach(inject((Parser p) { |
+ beforeEach((Parser p) { |
parser = p; |
- })); |
+ }); |
// We only care about the error strings in the DynamicParser. |
var errStr = (x) { |
@@ -190,6 +194,10 @@ main() { |
expectEval("4()").toThrow('4 is not a function'); |
}); |
+ it("should throw on an unexpected token", (){ |
+ expectEval("[1,2] trac") |
+ .toThrow('Parser Error: \'trac\' is an unexpected token at column 7 in [[1,2] trac]'); |
+ }); |
it('should fail gracefully when invoking non-function', () { |
expect(() { |
@@ -274,7 +282,8 @@ main() { |
context['map'] = {}; |
expect(eval('null')).toBe(null); |
- expect(eval('map.null')).toBe(null); |
+ expect(() => eval('map.null')) |
+ .toThrow("Identifier 'null' is a reserved word."); |
}); |
@@ -283,6 +292,20 @@ main() { |
}); |
+ it('should eval binary operators with null as null', () { |
+ expect(eval("null < 0")).toEqual(null); |
+ expect(eval("null * 3")).toEqual(null); |
+ |
+ // But + and - are special cases. |
+ expect(eval("null + 6")).toEqual(6); |
+ expect(eval("5 + null")).toEqual(5); |
+ expect(eval("null - 4")).toEqual(-4); |
+ expect(eval("3 - null")).toEqual(3); |
+ expect(eval("null + null")).toEqual(0); |
+ expect(eval("null - null")).toEqual(0); |
+ }); |
+ |
+ |
it('should pass exceptions through getters', () { |
expect(() { |
parser('boo').eval(new ScopeWithErrors()); |
@@ -290,10 +313,13 @@ main() { |
}); |
- it('should pass noSuchMethExceptions through getters', () { |
+ it('should pass noSuchMethodExceptions through getters', () { |
expect(() { |
parser('getNoSuchMethod').eval(new ScopeWithErrors()); |
- }).toThrow("iDontExist"); |
+ }).toThrow("null"); |
+ // Dartium throws: The null object does not have a method 'iDontExist' |
+ // Chrome throws: NullError: Cannot call "iDontExist$0" on null |
+ // Firefox throws: NullError: null has no properties |
}); |
@@ -310,6 +336,7 @@ main() { |
}).toThrow("notAProperty"); |
}); |
+ |
it('should fail on private field access', () { |
expect(parser('publicField').eval(new WithPrivateField())).toEqual(4); |
// On Dartium, this fails with "NoSuchMethod: no instance getter" |
@@ -319,6 +346,26 @@ main() { |
parser('_privateField').eval(new WithPrivateField()); |
}).toThrow(); |
}); |
+ |
+ |
+ it('should only allow identifier or keyword as formatter names', () { |
+ expect(() => parser('"Foo"|(')).toThrow('identifier or keyword'); |
+ expect(() => parser('"Foo"|1234')).toThrow('identifier or keyword'); |
+ expect(() => parser('"Foo"|"uppercase"')).toThrow('identifier or keyword'); |
+ }); |
+ |
+ |
+ it('should only allow identifier or keyword as member names', () { |
+ expect(() => parser('x.(')).toThrow('identifier or keyword'); |
+ expect(() => parser('x. 1234')).toThrow('identifier or keyword'); |
+ expect(() => parser('x."foo"')).toThrow('identifier or keyword'); |
+ }); |
+ |
+ |
+ it('should only allow identifier, string, or keyword as object literal key', () { |
+ expect(() => parser('{(:0}')).toThrow('expected identifier, keyword, or string'); |
+ expect(() => parser('{1234:0}')).toThrow('expected identifier, keyword, or string'); |
+ }); |
}); |
describe('setters', () { |
@@ -397,8 +444,8 @@ main() { |
}); |
}); |
- describe('reserved words', () { |
- it('should support reserved words in member get access', () { |
+ xdescribe('reserved words', () { |
+ iit('should support reserved words in member get access', () { |
for (String reserved in RESERVED_WORDS) { |
expect(parser("o.$reserved").eval({ 'o': new Object() })).toEqual(null); |
expect(parser("o.$reserved").eval({ 'o': { reserved: reserved }})).toEqual(reserved); |
@@ -632,16 +679,15 @@ main() { |
expect(eval("constN()")).toEqual(123); |
}); |
+ |
it('should access a protected keyword on scope', () { |
context['const'] = 3; |
- expect(eval('const')).toEqual(3); |
+ expect(eval('this["const"]')).toEqual(3); |
}); |
- it('should evaluate function call with arguments', () { |
- context["add"] = (a,b) { |
- return a+b; |
- }; |
+ it('should evaluate scope call with arguments', () { |
+ context["add"] = (a,b) => a + b; |
expect(eval("add(1,2)")).toEqual(3); |
}); |
@@ -900,6 +946,7 @@ main() { |
}); |
}); |
+ |
describe('assignable', () { |
it('should expose assignment function', () { |
var fn = parser('a'); |
@@ -910,6 +957,7 @@ main() { |
}); |
}); |
+ |
describe('locals', () { |
it('should expose local variables', () { |
expect(parser('a').bind({'a': 6}, ScopeLocals.wrapper)({'a': 1})).toEqual(1); |
@@ -925,10 +973,10 @@ main() { |
}); |
- it('should work with scopes', inject((Scope scope) { |
+ it('should work with scopes', (Scope scope) { |
scope.context['a'] = {'b': 6}; |
expect(parser('a.b').bind(scope.context, ScopeLocals.wrapper)({'a': {'b':1}})).toEqual(1); |
- })); |
+ }); |
it('should expose assignment function', () { |
var fn = parser('a.b'); |
@@ -942,51 +990,176 @@ main() { |
}); |
- describe('filters', () { |
- it('should call a filter', () { |
- expect(eval("'Foo'|uppercase", filters)).toEqual("FOO"); |
- expect(eval("'fOo'|uppercase|lowercase", filters)).toEqual("foo"); |
+ describe('named arguments', () { |
+ it('should be supported for scope calls', () { |
+ var data = new TestData(); |
+ expect(parser("sub1(1)").eval(data)).toEqual(1); |
+ expect(parser("sub1(3, b: 2)").eval(data)).toEqual(1); |
+ |
+ expect(parser("sub2()").eval(data)).toEqual(0); |
+ expect(parser("sub2(a: 3)").eval(data)).toEqual(3); |
+ expect(parser("sub2(a: 3, b: 2)").eval(data)).toEqual(1); |
+ expect(parser("sub2(b: 4)").eval(data)).toEqual(-4); |
+ }); |
+ |
+ |
+ it('should be supported for scope calls (map)', () { |
+ context["sub1"] = (a, {b: 0}) => a - b; |
+ expect(eval("sub1(1)")).toEqual(1); |
+ expect(eval("sub1(3, b: 2)")).toEqual(1); |
+ |
+ context["sub2"] = ({a: 0, b: 0}) => a - b; |
+ expect(eval("sub2()")).toEqual(0); |
+ expect(eval("sub2(a: 3)")).toEqual(3); |
+ expect(eval("sub2(a: 3, b: 2)")).toEqual(1); |
+ expect(eval("sub2(b: 4)")).toEqual(-4); |
+ }); |
+ |
+ |
+ it('should be supported for member calls', () { |
+ context['o'] = new TestData(); |
+ expect(eval("o.sub1(1)")).toEqual(1); |
+ expect(eval("o.sub1(3, b: 2)")).toEqual(1); |
+ |
+ expect(eval("o.sub2()")).toEqual(0); |
+ expect(eval("o.sub2(a: 3)")).toEqual(3); |
+ expect(eval("o.sub2(a: 3, b: 2)")).toEqual(1); |
+ expect(eval("o.sub2(b: 4)")).toEqual(-4); |
+ }); |
+ |
+ |
+ it('should be supported for member calls (map)', () { |
+ context['o'] = { |
+ 'sub1': (a, {b: 0}) => a - b, |
+ 'sub2': ({a: 0, b: 0}) => a - b |
+ }; |
+ expect(eval("o.sub1(1)")).toEqual(1); |
+ expect(eval("o.sub1(3, b: 2)")).toEqual(1); |
+ |
+ expect(eval("o.sub2()")).toEqual(0); |
+ expect(eval("o.sub2(a: 3)")).toEqual(3); |
+ expect(eval("o.sub2(a: 3, b: 2)")).toEqual(1); |
+ expect(eval("o.sub2(b: 4)")).toEqual(-4); |
+ }); |
+ |
+ |
+ it('should be supported for function calls', () { |
+ context["sub1"] = (a, {b: 0}) => a - b; |
+ expect(eval("(sub1)(1)")).toEqual(1); |
+ expect(eval("(sub1)(3, b: 2)")).toEqual(1); |
+ |
+ context["sub2"] = ({a: 0, b: 0}) => a - b; |
+ expect(eval("(sub2)()")).toEqual(0); |
+ expect(eval("(sub2)(a: 3)")).toEqual(3); |
+ expect(eval("(sub2)(a: 3, b: 2)")).toEqual(1); |
+ expect(eval("(sub2)(b: 4)")).toEqual(-4); |
+ }); |
+ |
+ |
+ it('should be an error to use the same name twice', () { |
+ expect(() => parser('foo(a: 0, a: 1)')).toThrow("Duplicate argument named 'a' at column 11"); |
+ expect(() => parser('foo(a: 0, b: 1, a: 2)')).toThrow("Duplicate argument named 'a' at column 17"); |
+ expect(() => parser('foo(0, a: 1, a: 2)')).toThrow("Duplicate argument named 'a' at column 14"); |
+ expect(() => parser('foo(0, a: 1, b: 2, a: 3)')).toThrow("Duplicate argument named 'a' at column 20"); |
+ }); |
+ |
+ |
+ it('should be an error to use Dart reserved words as names', () { |
+ expect(() => parser('foo(if: 0)')).toThrow("Cannot use Dart reserved word 'if' as named argument at column 5"); |
+ expect(() => parser('foo(a: 0, class: 0)')).toThrow("Cannot use Dart reserved word 'class' as named argument at column 11"); |
+ }); |
+ |
+ |
+ it('should pretty print scope calls correctly', () { |
+ expect(parser('foo(a: 0)').toString()).toEqual('foo(a: 0)'); |
+ expect(parser('foo(a: 0, b: 1)').toString()).toEqual('foo(a: 0, b: 1)'); |
+ expect(parser('foo(b: 1, a: 0)').toString()).toEqual('foo(b: 1, a: 0)'); |
+ |
+ expect(parser('foo(0)').toString()).toEqual('foo(0)'); |
+ expect(parser('foo(0, a: 0)').toString()).toEqual('foo(0, a: 0)'); |
+ expect(parser('foo(0, a: 0, b: 1)').toString()).toEqual('foo(0, a: 0, b: 1)'); |
+ expect(parser('foo(0, b: 1, a: 0)').toString()).toEqual('foo(0, b: 1, a: 0)'); |
+ }); |
+ |
+ |
+ it('should pretty print member calls correctly', () { |
+ expect(parser('o.foo(a: 0)').toString()).toEqual('o.foo(a: 0)'); |
+ expect(parser('o.foo(a: 0, b: 1)').toString()).toEqual('o.foo(a: 0, b: 1)'); |
+ expect(parser('o.foo(b: 1, a: 0)').toString()).toEqual('o.foo(b: 1, a: 0)'); |
+ |
+ expect(parser('o.foo(0)').toString()).toEqual('o.foo(0)'); |
+ expect(parser('o.foo(0, a: 0)').toString()).toEqual('o.foo(0, a: 0)'); |
+ expect(parser('o.foo(0, a: 0, b: 1)').toString()).toEqual('o.foo(0, a: 0, b: 1)'); |
+ expect(parser('o.foo(0, b: 1, a: 0)').toString()).toEqual('o.foo(0, b: 1, a: 0)'); |
+ }); |
+ |
+ |
+ it('should pretty print function calls correctly', () { |
+ expect(parser('(foo)(a: 0)').toString()).toEqual('(foo)(a: 0)'); |
+ expect(parser('(foo)(a: 0, b: 1)').toString()).toEqual('(foo)(a: 0, b: 1)'); |
+ expect(parser('(foo)(b: 1, a: 0)').toString()).toEqual('(foo)(b: 1, a: 0)'); |
+ |
+ expect(parser('(foo)(0)').toString()).toEqual('(foo)(0)'); |
+ expect(parser('(foo)(0, a: 0)').toString()).toEqual('(foo)(0, a: 0)'); |
+ expect(parser('(foo)(0, a: 0, b: 1)').toString()).toEqual('(foo)(0, a: 0, b: 1)'); |
+ expect(parser('(foo)(0, b: 1, a: 0)').toString()).toEqual('(foo)(0, b: 1, a: 0)'); |
+ }); |
+ }); |
+ |
+ |
+ describe('formatters', () { |
+ it('should call a formatter', () { |
+ expect(eval("'Foo'|uppercase", formatters)).toEqual("FOO"); |
+ // Re-enable after static parser is removed |
+ //expect(eval("'f' + ('o'|uppercase) + 'o'", formatters)).toEqual("fOo"); |
+ expect(eval("'fOo'|uppercase|lowercase", formatters)).toEqual("foo"); |
+ }); |
+ |
+ it('should call a formatter with arguments', () { |
+ expect(eval("1|increment:2", formatters)).toEqual(3); |
}); |
- it('should call a filter with arguments', () { |
- expect(eval("1|increment:2", filters)).toEqual(3); |
+ it('should evaluate grouped formatters', () { |
+ context = {'name': 'MISKO'}; |
+ expect(eval('n = (name|lowercase)', formatters)).toEqual('misko'); |
+ expect(eval('n')).toEqual('misko'); |
}); |
- it('should parse filters', () { |
+ it('should parse formatters', () { |
expect(() { |
eval("1|nonexistent"); |
- }).toThrow('No NgFilter: nonexistent found!'); |
+ }).toThrow('No Formatter: nonexistent found!'); |
expect(() { |
- eval("1|nonexistent", filters); |
- }).toThrow('No NgFilter: nonexistent found!'); |
+ eval("1|nonexistent", formatters); |
+ }).toThrow('No Formatter: nonexistent found!'); |
context['offset'] = 3; |
expect(eval("'abcd'|substring:1:offset")).toEqual("bc"); |
expect(eval("'abcd'|substring:1:3|uppercase")).toEqual("BC"); |
}); |
- it('should only use filters that are passed as an argument', inject((Injector injector) { |
+ it('should only use formatters that are passed as an argument', (Injector injector) { |
var expression = parser("'World'|hello"); |
expect(() { |
- expression.eval({}, filters); |
- }).toThrow('No NgFilter: hello found!'); |
+ expression.eval({}, formatters); |
+ }).toThrow('No Formatter: hello found!'); |
var module = new Module() |
..type(HelloFilter); |
var childInjector = injector.createChild([module], |
- forceNewInstances: [FilterMap]); |
- var newFilters = childInjector.get(FilterMap); |
+ forceNewInstances: [FormatterMap]); |
+ var newFilters = childInjector.get(FormatterMap); |
expect(expression.eval({}, newFilters)).toEqual('Hello, World!'); |
- })); |
+ }); |
- it('should not allow filters in a chain', () { |
+ it('should not allow formatters in a chain', () { |
expect(() { |
parser("1;'World'|hello"); |
- }).toThrow('cannot have a filter in a chain the end of the expression [1;\'World\'|hello]'); |
+ }).toThrow('Cannot have a formatter in a chain the end of the expression [1;\'World\'|hello]'); |
expect(() { |
parser("'World'|hello;1"); |
- }).toThrow('cannot have a filter in a chain at column 15 in [\'World\'|hello;1]'); |
+ }).toThrow('Cannot have a formatter in a chain at column 15 in [\'World\'|hello;1]'); |
}); |
}); |
}); |
@@ -1024,19 +1197,19 @@ class ScopeWithErrors { |
get getNoSuchMethod => null.iDontExist(); |
} |
-@NgFilter(name:'increment') |
+@Formatter(name:'increment') |
class IncrementFilter { |
call(a, b) => a + b; |
} |
-@NgFilter(name:'substring') |
+@Formatter(name:'substring') |
class SubstringFilter { |
call(String str, startIndex, [endIndex]) { |
return str.substring(startIndex, endIndex); |
} |
} |
-@NgFilter(name:'hello') |
+@Formatter(name:'hello') |
class HelloFilter { |
call(String str) { |
return 'Hello, $str!'; |