Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(600)

Side by Side Diff: pkg/polymer_expressions/test/syntax_test.dart

Issue 141703024: Refactor of PolymerExpressions. Adds "as" expressions. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Address review comments Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 import 'dart:async'; 5 import 'dart:async';
6 import 'dart:html'; 6 import 'dart:html';
7 7
8 import 'package:logging/logging.dart'; 8 import 'package:logging/logging.dart';
9 import 'package:observe/observe.dart'; 9 import 'package:observe/observe.dart';
10 import 'package:polymer_expressions/polymer_expressions.dart'; 10 import 'package:polymer_expressions/polymer_expressions.dart';
11 import 'package:polymer_expressions/eval.dart';
11 import 'package:template_binding/template_binding.dart'; 12 import 'package:template_binding/template_binding.dart';
12 import 'package:unittest/html_config.dart'; 13 import 'package:unittest/html_enhanced_config.dart';
13 import 'package:unittest/unittest.dart'; 14 import 'package:unittest/unittest.dart';
14 15
15 main() { 16 main() {
16 useHtmlConfiguration(); 17 useHtmlEnhancedConfiguration();
17 18
18 group('PolymerExpressions', () { 19 group('PolymerExpressions', () {
19 var testDiv; 20 DivElement testDiv;
21 int _scope_id;
20 22
21 setUp(() { 23 setUp(() {
22 document.body.append(testDiv = new DivElement()); 24 document.body.append(testDiv = new DivElement());
25 _scope_id = 0;
23 }); 26 });
24 27
25 tearDown(() { 28 tearDown(() {
26 testDiv.firstChild.remove(); 29 testDiv.children.clear();
27 testDiv = null; 30 testDiv = null;
28 }); 31 });
29 32
30 test('should make two-way bindings to inputs', () { 33 Scope testScopeFactory({model, Map<String, Object> variables,
31 testDiv.nodes.add(new Element.html(''' 34 Scope parent}) {
32 <template id="test" bind> 35 var testVariables = variables == null ? {} : new Map.from(variables);
33 <input id="input" value="{{ firstName }}"> 36 testVariables['_scope_id'] = _scope_id++;
34 </template>''')); 37 var scope = new Scope(model: model, variables: testVariables,
35 var person = new Person('John', 'Messerly', ['A', 'B', 'C']); 38 parent: parent);
36 templateBind(query('#test')) 39 return scope;
37 ..bindingDelegate = new PolymerExpressions() 40 }
38 ..model = person; 41
39 return new Future.delayed(new Duration()).then((_) { 42 Future<Element> setUpTest(String html, {model, Map globals}) {
40 InputElement input = query('#input'); 43 var tag = new Element.html(html);
41 expect(input.value, 'John'); 44 templateBind(tag)
42 input.focus(); 45 ..bindingDelegate = new PolymerExpressions(globals: globals,
43 input.value = 'Justin'; 46 scopeFactory: testScopeFactory)
44 input.blur(); 47 ..model = model;
45 var event = new Event('change'); 48 testDiv.children.clear();
46 // TODO(justin): figure out how to trigger keyboard events to test 49 testDiv.append(tag);
47 // two-way bindings 50 return waitForChange(testDiv);
48 }); 51 }
49 }); 52
50 53 group('scope creation', () {
51 test('should handle null collections in "in" expressions', () { 54 // These tests are sensitive to some internals of the implementation that
52 testDiv.nodes.add(new Element.html(''' 55 // might not be visible to applications, but are useful for verifying that
53 <template id="test" bind> 56 // that we're not creating too many Scopes.
54 <template repeat="{{ item in items }}"> 57
55 {{ item }} 58 // The reason that we create two Scopes in the cases with one binding is
56 </template> 59 // that <template bind> has one scope for the context to evaluate the bind
57 </template>''')); 60 // binding in, and another scope for the bindings inside the template.
58 templateBind(query('#test')).bindingDelegate = 61
59 new PolymerExpressions(globals: {'items': null}); 62 // We could try to optimize the outer scope away in cases where the
60 // the template should be the only node 63 // expression is empty, but there are a lot of special cases in the
61 expect(testDiv.nodes.length, 1); 64 // syntax code already.
62 expect(testDiv.nodes[0].id, 'test'); 65 test('should create two scopes for a single binding', () =>
63 }); 66 setUpTest('''
64 67 <template id="test" bind>
65 test('should silently handle bad variable names', () { 68 <div>{{ _scope_id }}</div>
Siggi Cherem (dart-lang) 2014/03/19 18:37:27 FYI - just ran this test locally in dart2js, seems
justinfagnani 2014/04/24 00:26:13 This has been fixed in template binding, and I no
66 var logger = new Logger('polymer_expressions'); 69 </template>''',
67 var logFuture = logger.onRecord.toList(); 70 model: new Model('a'))
68 testDiv.nodes.add(new Element.html(''' 71 .then((_) {
69 <template id="test" bind>{{ foo }}</template>''')); 72 expect(testDiv.children.length, 2);
70 templateBind(query('#test')) 73 expect(testDiv.children[1].text, '1');
71 ..bindingDelegate = new PolymerExpressions() 74 }));
72 ..model = []; 75
73 return new Future(() { 76 test('should create a single scope for two bindings', () =>
74 logger.clearListeners(); 77 setUpTest('''
75 return logFuture.then((records) { 78 <template id="test" bind>
76 expect(records.length, 1); 79 <div>{{ _scope_id }}</div>
77 expect(records.first.message, 80 <div>{{ _scope_id }}</div>
78 contains('Error evaluating expression')); 81 </template>''',
79 expect(records.first.message, contains('foo')); 82 model: new Model('a'))
83 .then((_) {
84 expect(testDiv.children.length, 3);
85 expect(testDiv.children[1].text, '1');
86 expect(testDiv.children[2].text, '1');
87 }));
88
89 test('should create a new scope for a bind/as binding', () {
90 return setUpTest('''
91 <template id="test" bind>
92 <div>{{ _scope_id }}</div>
93 <template bind="{{ data as a }}" id="inner">
94 <div>{{ _scope_id }}</div>
95 <div>{{ a }}</div>
96 <div>{{ data }}</div>
97 </template>
98 </template>''',
99 model: new Model('foo'))
100 .then((_) {
101 expect(testDiv.children.length, 6);
102 expect(testDiv.children[1].text, '1');
103 expect(testDiv.children[3].text, '2');
104 expect(testDiv.children[4].text, 'foo');
105 expect(testDiv.children[5].text, 'foo');
80 }); 106 });
81 }); 107 });
82 }); 108
109 test('should create scopes for a repeat/in binding', () {
110 return setUpTest('''
111 <template id="test" bind>
112 <div>{{ _scope_id }}</div>
113 <template repeat="{{ i in items }}" id="inner">
114 <div>{{ _scope_id }}</div>
115 <div>{{ i }}</div>
116 <div>{{ data }}</div>
117 </template>
118 </template>''',
119 model: new Model('foo'), globals: {'items': ['a', 'b', 'c']})
120 .then((_) {
121 expect(testDiv.children.length, 12);
122 expect(testDiv.children[1].text, '1');
123 expect(testDiv.children[3].text, '2');
124 expect(testDiv.children[4].text, 'a');
125 expect(testDiv.children[5].text, 'foo');
126 expect(testDiv.children[6].text, '3');
127 expect(testDiv.children[7].text, 'b');
128 expect(testDiv.children[8].text, 'foo');
129 expect(testDiv.children[9].text, '4');
130 expect(testDiv.children[10].text, 'c');
131 expect(testDiv.children[11].text, 'foo');
132 });
133 });
134
135
136 });
137
138 group('with template bind', () {
139
140 test('should show a simple binding on the model', () =>
141 setUpTest('''
142 <template id="test" bind>
143 <div>{{ data }}</div>
144 </template>''',
145 model: new Model('a'))
146 .then((_) {
147 expect(testDiv.children.length, 2);
148 expect(testDiv.children[1].text, 'a');
149 }));
150
151 test('should handle an empty binding on the model', () =>
152 setUpTest('''
153 <template id="test" bind>
154 <div>{{ }}</div>
155 </template>''',
156 model: 'a')
157 .then((_) {
158 expect(testDiv.children.length, 2);
159 expect(testDiv.children[1].text, 'a');
160 }));
161
162 test('should show a simple binding to a global', () =>
163 setUpTest('''
164 <template id="test" bind>
165 <div>{{ a }}</div>
166 </template>''',
167 globals: {'a': '123'})
168 .then((_) {
169 expect(testDiv.children.length, 2);
170 expect(testDiv.children[1].text, '123');
171 }));
172
173 test('should show an expression binding', () =>
174 setUpTest('''
175 <template id="test" bind>
176 <div>{{ data + 'b' }}</div>
177 </template>''',
178 model: new Model('a'))
179 .then((_) {
180 expect(testDiv.children.length, 2);
181 expect(testDiv.children[1].text, 'ab');
182 }));
183
184 test('should handle an expression in the bind attribute', () =>
185 setUpTest('''
186 <template id="test" bind="{{ data }}">
187 <div>{{ this }}</div>
188 </template>''',
189 model: new Model('a'))
190 .then((_) {
191 expect(testDiv.children.length, 2);
192 expect(testDiv.children[1].text, 'a');
193 }));
194
195 test('should handle a nested template with an expression in the bind '
196 'attribute', () =>
197 setUpTest('''
198 <template id="test" bind>
199 <template id="inner" bind="{{ data }}">
200 <div>{{ this }}</div>
201 </template>
202 </template>''',
203 model: new Model('a'))
204 .then((_) {
205 expect(testDiv.children.length, 3);
206 expect(testDiv.children[2].text, 'a');
207 }));
208
209
210 test('should handle an "as" expression in the bind attribute', () =>
211 setUpTest('''
212 <template id="test" bind="{{ data as a }}">
213 <div>{{ data }}b</div>
214 <div>{{ a }}c</div>
215 </template>''',
216 model: new Model('a'))
217 .then((_) {
218 expect(testDiv.children.length, 3);
219 expect(testDiv.children[1].text, 'ab');
220 expect(testDiv.children[2].text, 'ac');
221 }));
222
223 /**
224 * This test fails in dart2js because we appear to be tickling a bug in
225 * mirrors via _TemplateIterator.
226 */
227 test('should not resolve names in the outer template from within a nested '
228 'template with a bind binding', () =>
229 setUpTest('''
230 <template id="test" bind>
231 <div>{{ data }}</div>
232 <div>{{ b }}</div>
233 <template id="inner" bind="{{ b }}">
234 <div>{{ data }}</div>
235 <div>{{ b }}</div>
236 <div>{{ this }}</div>
237 </template>
238 </template>''',
239 model: new Model('foo'), globals: {'b': 'bbb'})
240 .then((_) {
241 expect(testDiv.children.map((c) => c.text),
242 ['', 'foo', 'bbb', '', '', 'bbb', 'bbb']);
243 }));
244
245 test('should shadow names in the outer template from within a nested '
246 'template', () =>
247 setUpTest('''
248 <template id="test" bind>
249 <div>{{ a }}</div>
250 <div>{{ b }}</div>
251 <template bind="{{ b as a }}">
252 <div>{{ a }}</div>
253 <div>{{ b }}</div>
254 </template>
255 </template>''',
256 globals: {'a': 'aaa', 'b': 'bbb'})
257 .then((_) {
258 expect(testDiv.children.map((c) => c.text),
259 ['', 'aaa', 'bbb', '', 'bbb', 'bbb']);
260 }));
261
262 });
263
264 group('with template repeat', () {
265
266 test('should handle "in" expressions', () =>
267 setUpTest('''
268 <template id="test" bind>
269 <div>{{ data }}</div>
270 <template repeat="{{ item in items }}">
271 <div>{{ item }}{{ data }}</div>
272 </template>
273 </template>''',
274 globals: {'items': [1, 2, 3]},
275 model: new Model('a'))
276 .then((_) {
277 // expect 6 children: two templates, a div and three instances
278 expect(testDiv.children.map((c) => c.text),
279 ['', 'a', '', '1a', '2a', '3a']);
280 }));
281
282 });
283
284 group('with template if', () {
285
286 Future doTest(value, bool shouldRender) =>
287 setUpTest('''
288 <template id="test" bind>
289 <div>{{ data }}</div>
290 <template if="{{ show }}">
291 <div>{{ data }}</div>
292 </template>
293 </template>''',
294 globals: {'show': value},
295 model: new Model('a'))
296 .then((_) {
297 if (shouldRender) {
298 expect(testDiv.children.length, 4);
299 expect(testDiv.children[1].text, 'a');
300 expect(testDiv.children[3].text, 'a');
301 } else {
302 expect(testDiv.children.length, 3);
303 expect(testDiv.children[1].text, 'a');
304 }
305 });
306
307 test('should render for a true expression',
308 () => doTest(true, true));
309
310 test('should treat a non-null expression as truthy',
311 () => doTest('a', true));
312
313 test('should treat an empty list as truthy',
314 () => doTest([], true));
315
316 test('should handle a false expression',
317 () => doTest(false, false));
318
319 test('should treat null as falsey',
320 () => doTest(null, false));
321 });
322
323 group('error handling', () {
324
325 test('should handle and log bad variable names', () {
326 var logger = new Logger('polymer_expressions');
327 var logFuture = logger.onRecord.toList();
328 return setUpTest('''
329 <template id="test" bind>
330 <span>A</span>
331 <span>{{ foo }}</span>
332 <span>B</span>
333 </template>''')
334 .then((_) {
335 expect(testDiv.children.length, 4);
336 expect(testDiv.children.skip(1).map((c) => c.text), ['A', '', 'B']);
337 logger.clearListeners();
338 return logFuture.then((records) {
339 expect(records.length, 1);
340 expect(records.first.message,
341 contains('Error evaluating expression'));
342 expect(records.first.message, contains('foo'));
343 });
344 });
345 });
346
347 test('should handle null collections in "in" expressions', () =>
348 setUpTest('''
349 <template id="test" bind>
350 <template repeat="{{ item in items }}">
351 {{ item }}
352 </template>
353 </template>''',
354 globals: {'items': null})
355 .then((_) {
356 expect(testDiv.children.length, 2);
357 expect(testDiv.children[0].id, 'test');
358 }));
359
360 });
361
362 group('regression tests', () {
363
364 test('should bind to literals', () =>
365 setUpTest('''
366 <template id="test" bind>
367 <div>{{ 123 }}</div>
368 <div>{{ 123.456 }}</div>
369 <div>{{ "abc" }}</div>
370 <div>{{ true }}</div>
371 <div>{{ null }}</div>
372 </template>''',
373 globals: {'items': null})
374 .then((_) {
375 expect(testDiv.children.length, 6);
376 expect(testDiv.children[1].text, '123');
377 expect(testDiv.children[2].text, '123.456');
378 expect(testDiv.children[3].text, 'abc');
379 expect(testDiv.children[4].text, 'true');
380 expect(testDiv.children[5].text, '');
381 }));
382
383 });
384
83 }); 385 });
84 } 386 }
85 387
388 Future<Element> waitForChange(Element e) {
389 var completer = new Completer<Element>();
390 new MutationObserver((mutations, observer) {
391 observer.disconnect();
392 completer.complete(e);
393 }).observe(e, childList: true);
394 return completer.future.timeout(new Duration(seconds: 1));
395 }
396
86 @reflectable 397 @reflectable
87 class Person extends ChangeNotifier { 398 class Model extends ChangeNotifier {
88 String _firstName; 399 String _data;
89 String _lastName; 400
90 List<String> _items; 401 Model(this._data);
91 402
92 Person(this._firstName, this._lastName, this._items); 403 String get data => _data;
93 404
94 String get firstName => _firstName; 405 void set data(String value) {
95 406 _data = notifyPropertyChange(#data, _data, value);
96 void set firstName(String value) {
97 _firstName = notifyPropertyChange(#firstName, _firstName, value);
98 } 407 }
99 408
100 String get lastName => _lastName; 409 String toString() => "Model(data: $_data)";
101
102 void set lastName(String value) {
103 _lastName = notifyPropertyChange(#lastName, _lastName, value);
104 }
105
106 String getFullName() => '$_firstName $_lastName';
107
108 List<String> get items => _items;
109
110 void set items(List<String> value) {
111 _items = notifyPropertyChange(#items, _items, value);
112 }
113
114 String toString() => "Person(firstName: $_firstName, lastName: $_lastName)";
115 } 410 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698