| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library eval_test; |
| 6 |
| 7 import 'dart:async'; |
| 8 |
| 9 import 'package:polymer_expression/eval.dart'; |
| 10 import 'package:polymer_expression/filter.dart'; |
| 11 import 'package:polymer_expression/parser.dart'; |
| 12 import 'package:unittest/unittest.dart'; |
| 13 import 'package:observe/observe.dart'; |
| 14 |
| 15 main() { |
| 16 |
| 17 group('eval', () { |
| 18 test('should return the model for an empty expression', () { |
| 19 expectEval('', 'model', 'model'); |
| 20 }); |
| 21 |
| 22 test('should handle the "this" keyword', () { |
| 23 expectEval('this', 'model', 'model'); |
| 24 expectEval('this.name', 'foo', new Foo(name: 'foo')); |
| 25 expectEval('this["a"]', 'x', {'a': 'x'}); |
| 26 }); |
| 27 |
| 28 test('should return a literal int', () { |
| 29 expectEval('1', 1); |
| 30 expectEval('+1', 1); |
| 31 expectEval('-1', -1); |
| 32 }); |
| 33 |
| 34 test('should return a literal double', () { |
| 35 expectEval('1.2', 1.2); |
| 36 expectEval('+1.2', 1.2); |
| 37 expectEval('-1.2', -1.2); |
| 38 }); |
| 39 |
| 40 test('should return a literal string', () { |
| 41 expectEval('"hello"', "hello"); |
| 42 expectEval("'hello'", "hello"); |
| 43 }); |
| 44 |
| 45 test('should return a literal boolean', () { |
| 46 expectEval('true', true); |
| 47 expectEval('false', false); |
| 48 }); |
| 49 |
| 50 test('should return a literal map', () { |
| 51 expectEval('{"a": 1}', equals(new Map.from({'a': 1}))); |
| 52 expectEval('{"a": 1}', containsPair('a', 1)); |
| 53 }); |
| 54 |
| 55 test('should call methods on a literal map', () { |
| 56 expectEval('{"a": 1}.length', 1); |
| 57 }); |
| 58 |
| 59 test('should evaluate unary operators', () { |
| 60 expectEval('+a', 2, null, {'a': 2}); |
| 61 expectEval('-a', -2, null, {'a': 2}); |
| 62 expectEval('!a', false, null, {'a': true}); |
| 63 }); |
| 64 |
| 65 test('should evaluate binary operators', () { |
| 66 expectEval('1 + 2', 3); |
| 67 expectEval('2 - 1', 1); |
| 68 expectEval('4 / 2', 2); |
| 69 expectEval('2 * 3', 6); |
| 70 |
| 71 expectEval('1 == 1', true); |
| 72 expectEval('1 == 2', false); |
| 73 expectEval('1 != 1', false); |
| 74 expectEval('1 != 2', true); |
| 75 |
| 76 expectEval('1 > 1', false); |
| 77 expectEval('1 > 2', false); |
| 78 expectEval('2 > 1', true); |
| 79 expectEval('1 >= 1', true); |
| 80 expectEval('1 >= 2', false); |
| 81 expectEval('2 >= 1', true); |
| 82 expectEval('1 < 1', false); |
| 83 expectEval('1 < 2', true); |
| 84 expectEval('2 < 1', false); |
| 85 expectEval('1 <= 1', true); |
| 86 expectEval('1 <= 2', true); |
| 87 expectEval('2 <= 1', false); |
| 88 |
| 89 expectEval('true || true', true); |
| 90 expectEval('true || false', true); |
| 91 expectEval('false || true', true); |
| 92 expectEval('false || false', false); |
| 93 |
| 94 expectEval('true && true', true); |
| 95 expectEval('true && false', false); |
| 96 expectEval('false && true', false); |
| 97 expectEval('false && false', false); |
| 98 }); |
| 99 |
| 100 test('should invoke a method on the model', () { |
| 101 var foo = new Foo(name: 'foo', age: 2); |
| 102 expectEval('x()', foo.x(), foo); |
| 103 expectEval('name', foo.name, foo); |
| 104 }); |
| 105 |
| 106 test('should invoke chained methods', () { |
| 107 var foo = new Foo(name: 'foo', age: 2); |
| 108 expectEval('name.length', foo.name.length, foo); |
| 109 expectEval('x().toString()', foo.x().toString(), foo); |
| 110 expectEval('name.substring(2)', foo.name.substring(2), foo); |
| 111 expectEval('a()()', 1, null, {'a': () => () => 1}); |
| 112 }); |
| 113 |
| 114 test('should invoke a top-level function', () { |
| 115 expectEval('x()', 42, null, {'x': () => 42}); |
| 116 expectEval('x(5)', 5, null, {'x': (i) => i}); |
| 117 expectEval('y(5, 10)', 50, null, {'y': (i, j) => i * j}); |
| 118 }); |
| 119 |
| 120 test('should give precedence to top-level functions over methods', () { |
| 121 var foo = new Foo(name: 'foo', age: 2); |
| 122 expectEval('x()', 42, foo, {'x': () => 42}); |
| 123 }); |
| 124 |
| 125 test('should invoke the [] operator', () { |
| 126 var map = {'a': 1, 'b': 2}; |
| 127 expectEval('map["a"]', 1, null, {'map': map}); |
| 128 expectEval('map["a"] + map["b"]', 3, null, {'map': map}); |
| 129 }); |
| 130 |
| 131 test('should call a filter', () { |
| 132 var topLevel = { |
| 133 'a': 'foo', |
| 134 'uppercase': (s) => s.toUpperCase(), |
| 135 }; |
| 136 expectEval('a | uppercase', 'FOO', null, topLevel); |
| 137 }); |
| 138 |
| 139 test('should call a transformer', () { |
| 140 var topLevel = { |
| 141 'a': '42', |
| 142 'parseInt': parseInt, |
| 143 'add': add, |
| 144 }; |
| 145 expectEval('a | parseInt()', 42, null, topLevel); |
| 146 expectEval('a | parseInt(8)', 34, null, topLevel); |
| 147 expectEval('a | parseInt() | add(10)', 52, null, topLevel); |
| 148 }); |
| 149 |
| 150 test('should return null if the receiver of a method is null', () { |
| 151 expectEval('a.b', null, null, {'a': null}); |
| 152 expectEval('a.b()', null, null, {'a': null}); |
| 153 }); |
| 154 |
| 155 test('should return null if null is invoked', () { |
| 156 expectEval('a()', null, null, {'a': null}); |
| 157 }); |
| 158 |
| 159 test('should return null if an operand is null', () { |
| 160 expectEval('a + b', null, null, {'a': null, 'b': null}); |
| 161 expectEval('+a', null, null, {'a': null}); |
| 162 }); |
| 163 |
| 164 test('should treat null as false', () { |
| 165 expectEval('!a', true, null, {'a': null}); |
| 166 |
| 167 expectEval('a && b', false, null, {'a': null, 'b': true}); |
| 168 expectEval('a && b', false, null, {'a': true, 'b': null}); |
| 169 expectEval('a && b', false, null, {'a': null, 'b': false}); |
| 170 expectEval('a && b', false, null, {'a': false, 'b': null}); |
| 171 expectEval('a && b', false, null, {'a': null, 'b': null}); |
| 172 |
| 173 expectEval('a || b', true, null, {'a': null, 'b': true}); |
| 174 expectEval('a || b', true, null, {'a': true, 'b': null}); |
| 175 expectEval('a || b', false, null, {'a': null, 'b': false}); |
| 176 expectEval('a || b', false, null, {'a': false, 'b': null}); |
| 177 expectEval('a || b', false, null, {'a': null, 'b': null}); |
| 178 }); |
| 179 |
| 180 test('should evaluate an "in" expression', () { |
| 181 var scope = new Scope(variables: {'items': [1, 2, 3]}); |
| 182 var comprehension = eval(parse('item in items'), scope); |
| 183 expect(comprehension.iterable, orderedEquals([1, 2, 3])); |
| 184 }); |
| 185 |
| 186 test('should handle null iterators in "in" expressions', () { |
| 187 var scope = new Scope(variables: {'items': null}); |
| 188 var comprehension = eval(parse('item in items'), scope); |
| 189 expect(comprehension, isNotNull); |
| 190 expect(comprehension.iterable, null); |
| 191 }); |
| 192 |
| 193 }); |
| 194 |
| 195 group('assign', () { |
| 196 |
| 197 test('should assign a single identifier', () { |
| 198 var foo = new Foo(name: 'a'); |
| 199 assign(parse('name'), 'b', new Scope(model: foo)); |
| 200 expect(foo.name, 'b'); |
| 201 }); |
| 202 |
| 203 test('should assign a sub-property', () { |
| 204 var child = new Foo(name: 'child'); |
| 205 var parent = new Foo(child: child); |
| 206 assign(parse('child.name'), 'Joe', new Scope(model: parent)); |
| 207 expect(parent.child.name, 'Joe'); |
| 208 }); |
| 209 |
| 210 test('should assign an index', () { |
| 211 var foo = new Foo(items: [1, 2, 3]); |
| 212 assign(parse('items[0]'), 4, new Scope(model: foo)); |
| 213 expect(foo.items[0], 4); |
| 214 }); |
| 215 |
| 216 test('should assign through transformers', () { |
| 217 var foo = new Foo(name: '42', age: 32); |
| 218 var globals = { |
| 219 'a': '42', |
| 220 'parseInt': parseInt, |
| 221 'add': add, |
| 222 }; |
| 223 var scope = new Scope(model: foo, variables: globals); |
| 224 assign(parse('age | add(7)'), 29, scope); |
| 225 expect(foo.age, 22); |
| 226 assign(parse('name | parseInt() | add(10)'), 29, scope); |
| 227 expect(foo.name, '19'); |
| 228 }); |
| 229 |
| 230 }); |
| 231 |
| 232 group('scope', () { |
| 233 test('should return fields on the model', () { |
| 234 var foo = new Foo(name: 'a', age: 1); |
| 235 var scope = new Scope(model: foo); |
| 236 expect(scope['name'], 'a'); |
| 237 expect(scope['age'], 1); |
| 238 }); |
| 239 |
| 240 test('should throw for undefined names', () { |
| 241 var scope = new Scope(); |
| 242 expect(() => scope['a'], throwsException); |
| 243 }); |
| 244 |
| 245 test('should return variables', () { |
| 246 var scope = new Scope(variables: {'a': 'A'}); |
| 247 expect(scope['a'], 'A'); |
| 248 }); |
| 249 |
| 250 test("should a field from the parent's model", () { |
| 251 var parent = new Scope(variables: {'a': 'A', 'b': 'B'}); |
| 252 var child = new Scope(variables: {'a': 'a'}, parent: parent); |
| 253 expect(child['a'], 'a'); |
| 254 expect(parent['a'], 'A'); |
| 255 expect(child['b'], 'B'); |
| 256 }); |
| 257 |
| 258 }); |
| 259 |
| 260 group('observe', () { |
| 261 test('should observe an identifier', () { |
| 262 var foo = new Foo(name: 'foo'); |
| 263 return expectObserve('name', |
| 264 model: foo, |
| 265 beforeMatcher: 'foo', |
| 266 mutate: () { |
| 267 foo.name = 'fooz'; |
| 268 }, |
| 269 afterMatcher: 'fooz' |
| 270 ); |
| 271 }); |
| 272 |
| 273 test('should observe an invocation', () { |
| 274 var foo = new Foo(name: 'foo'); |
| 275 return expectObserve('foo.name', |
| 276 variables: {'foo': foo}, |
| 277 beforeMatcher: 'foo', |
| 278 mutate: () { |
| 279 foo.name = 'fooz'; |
| 280 }, |
| 281 afterMatcher: 'fooz' |
| 282 ); |
| 283 }); |
| 284 |
| 285 test('should observe map access', () { |
| 286 var foo = toObservable({'one': 'one', 'two': 'two'}); |
| 287 return expectObserve('foo["one"]', |
| 288 variables: {'foo': foo}, |
| 289 beforeMatcher: 'one', |
| 290 mutate: () { |
| 291 foo['one'] = '1'; |
| 292 }, |
| 293 afterMatcher: '1' |
| 294 ); |
| 295 }); |
| 296 |
| 297 test('should observe an comprehension', () { |
| 298 var items = new ObservableList(); |
| 299 var foo = new Foo(name: 'foo'); |
| 300 return expectObserve('item in items', |
| 301 variables: {'items': items}, |
| 302 beforeMatcher: (c) => c.iterable.isEmpty, |
| 303 mutate: () { |
| 304 items.add(foo); |
| 305 }, |
| 306 afterMatcher: (c) => c.iterable.contains(foo) |
| 307 ); |
| 308 }); |
| 309 |
| 310 }); |
| 311 |
| 312 } |
| 313 |
| 314 class Foo extends Object with ChangeNotifierMixin { |
| 315 String _name; |
| 316 String get name => _name; |
| 317 void set name(String n) { |
| 318 _name = notifyPropertyChange(const Symbol('name'), _name, n); |
| 319 } |
| 320 |
| 321 int age; |
| 322 Foo child; |
| 323 List<int> items; |
| 324 |
| 325 Foo({name, this.age, this.child, this.items}) : _name = name; |
| 326 |
| 327 int x() => age * age; |
| 328 } |
| 329 |
| 330 parseInt([int radix = 10]) => new IntToString(radix: radix); |
| 331 |
| 332 class IntToString extends Transformer<int, String> { |
| 333 final int radix; |
| 334 IntToString({this.radix: 10}); |
| 335 int forward(String s) => int.parse(s, radix: radix); |
| 336 String reverse(int i) => '$i'; |
| 337 } |
| 338 |
| 339 add(int i) => new Add(i); |
| 340 |
| 341 class Add extends Transformer<int, int> { |
| 342 final int i; |
| 343 Add(this.i); |
| 344 int forward(int x) => x + i; |
| 345 int reverse(int x) => x - i; |
| 346 } |
| 347 |
| 348 Object evalString(String s, [Object model, Map vars]) => |
| 349 eval(new Parser(s).parse(), new Scope(model: model, variables: vars)); |
| 350 |
| 351 expectEval(String s, dynamic matcher, [Object model, Map vars = const {}]) => |
| 352 expect( |
| 353 eval(new Parser(s).parse(), new Scope(model: model, variables: vars)), |
| 354 matcher, |
| 355 reason: s); |
| 356 |
| 357 expectObserve(String s, { |
| 358 Object model, |
| 359 Map variables: const {}, |
| 360 dynamic beforeMatcher, |
| 361 mutate(), |
| 362 dynamic afterMatcher}) { |
| 363 |
| 364 var observer = observe(new Parser(s).parse(), |
| 365 new Scope(model: model, variables: variables)); |
| 366 expect(observer.currentValue, beforeMatcher); |
| 367 var passed = false; |
| 368 var future = observer.onUpdate.first.then((value) { |
| 369 expect(value, afterMatcher); |
| 370 expect(observer.currentValue, afterMatcher); |
| 371 passed = true; |
| 372 }); |
| 373 mutate(); |
| 374 // fail if we don't receive an update by the next event loop |
| 375 return Future.wait([future, new Future(() { |
| 376 expect(passed, true, reason: "Didn't receive a change notification on $s"); |
| 377 })]); |
| 378 } |
| OLD | NEW |