OLD | NEW |
---|---|
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:template_binding/template_binding.dart'; | 11 import 'package:template_binding/template_binding.dart'; |
12 import 'package:unittest/html_enhanced_config.dart'; | 12 import 'package:unittest/html_enhanced_config.dart'; |
13 import 'package:unittest/unittest.dart'; | 13 import 'package:unittest/unittest.dart'; |
14 | 14 |
15 main() { | 15 main() { |
16 useHtmlEnhancedConfiguration(); | 16 useHtmlEnhancedConfiguration(); |
17 | 17 |
18 group('PolymerExpressions', () { | 18 group('PolymerExpressions', () { |
19 var testDiv; | 19 DivElement testDiv; |
20 | 20 |
21 setUp(() { | 21 setUp(() { |
22 document.body.append(testDiv = new DivElement()); | 22 document.body.append(testDiv = new DivElement()); |
23 }); | 23 }); |
24 | 24 |
25 tearDown(() { | 25 tearDown(() { |
26 testDiv.firstChild.remove(); | 26 testDiv.children.clear(); |
27 testDiv = null; | 27 testDiv = null; |
28 }); | 28 }); |
29 | 29 |
30 test('should make two-way bindings to inputs', () { | 30 Future<Element> setUpTest(String html, {model: null, Map globals: null}) { |
Jennifer Messerly
2014/01/31 02:48:50
: null is not needed
justinfagnani
2014/03/12 23:21:30
Done.
| |
31 testDiv.nodes.add(new Element.html(''' | 31 testDiv.innerHtml = html; |
32 <template id="test" bind> | 32 templateBind(querySelector('#test')) |
33 <input id="input" value="{{ firstName }}"> | 33 ..bindingDelegate = new PolymerExpressions(globals: globals) |
34 </template>''')); | 34 ..model = model; |
35 var person = new Person('John', 'Messerly', ['A', 'B', 'C']); | 35 return waitForChange(testDiv); |
36 templateBind(query('#test')) | 36 } |
37 ..bindingDelegate = new PolymerExpressions() | 37 |
38 ..model = person; | 38 group('with template bind', () { |
39 return new Future.delayed(new Duration()).then((_) { | 39 |
40 InputElement input = query('#input'); | 40 test('should show a simple binding', () => |
41 expect(input.value, 'John'); | 41 setUpTest(''' |
42 input.focus(); | 42 <template id="test" bind> |
43 input.value = 'Justin'; | 43 <div>{{ data + 'b' }}</div> |
44 input.blur(); | 44 </template>''', |
45 var event = new Event('change'); | 45 model: new Model('a')) |
46 // TODO(justin): figure out how to trigger keyboard events to test | 46 .then((_) { |
47 // two-way bindings | 47 expect(testDiv.children.length, 2); |
48 }); | 48 expect(testDiv.children[1].text, 'ab'); |
49 }); | 49 })); |
50 | 50 |
51 test('should handle null collections in "in" expressions', () { | 51 test('should handle an expression in the bind attribute', () => |
52 testDiv.nodes.add(new Element.html(''' | 52 setUpTest(''' |
53 <template id="test" bind> | 53 <template id="test" bind="{{ data }}"> |
54 <template repeat="{{ item in items }}"> | 54 <div>{{ this }}</div> |
55 {{ item }} | 55 </template>''', |
56 </template> | 56 model: new Model('a')) |
57 </template>''')); | 57 .then((_) { |
58 templateBind(query('#test')).bindingDelegate = | 58 expect(testDiv.children.length, 2); |
59 new PolymerExpressions(globals: {'items': null}); | 59 expect(testDiv.children[1].text, 'a'); |
60 // the template should be the only node | 60 })); |
61 expect(testDiv.nodes.length, 1); | 61 |
62 expect(testDiv.nodes[0].id, 'test'); | 62 test('should handle an "as" expression in the bind attribute', () => |
63 }); | 63 setUpTest(''' |
64 | 64 <template id="test" bind="{{ data as a }}"> |
65 test('should silently handle bad variable names', () { | 65 <div>{{ data }}</div> |
66 var logger = new Logger('polymer_expressions'); | 66 <div>{{ a }}</div> |
67 var logFuture = logger.onRecord.toList(); | 67 </template>''', |
68 testDiv.nodes.add(new Element.html(''' | 68 model: new Model('a')) |
69 <template id="test" bind>{{ foo }}</template>''')); | 69 .then((_) { |
70 templateBind(query('#test')).bindingDelegate = new PolymerExpressions(); | 70 expect(testDiv.children.length, 3); |
71 return new Future(() { | 71 expect(testDiv.children[1].text, 'a'); |
72 logger.clearListeners(); | 72 expect(testDiv.children[2].text, 'a'); |
73 return logFuture.then((records) { | 73 })); |
74 expect(records.length, 1); | 74 |
75 expect(records.first.message, | 75 /** |
76 contains('Error evaluating expression')); | 76 * This test fails in dart2js because we appear to be tickling a bug in |
77 expect(records.first.message, contains('foo')); | 77 * mirrors via _TemplateIterator. |
78 */ | |
79 skip_test('should resolve names in the outer template from within a nested ' | |
80 'template', () => | |
81 setUpTest(''' | |
82 <template id="test" bind> | |
83 <div>{{ a }}</div> | |
84 <div>{{ b }}</div> | |
85 <template bind="{{ b }}"> | |
86 <div>{{ a }}</div> | |
87 <div>{{ b }}</div> | |
88 <div>{{ }}</div> | |
89 </template> | |
90 </template>''', | |
91 globals: {'a': 'aaa', 'b': 'bbb'}) | |
92 .then((_) { | |
93 print("A"); | |
94 expect(testDiv.children.map((c) => c.text), | |
95 ['', 'aaa', 'bbb', '', 'aaa', 'bbb', 'bbb']); | |
96 })); | |
97 | |
98 test('should shadow names in the outer template from within a nested ' | |
99 'template', () => | |
100 setUpTest(''' | |
101 <template id="test" bind> | |
102 <div>{{ a }}</div> | |
103 <div>{{ b }}</div> | |
104 <template bind="{{ b as a}}"> | |
105 <div>{{ a }}</div> | |
106 <div>{{ b }}</div> | |
107 </template> | |
108 </template>''', | |
109 globals: {'a': 'aaa', 'b': 'bbb'}) | |
110 .then((_) { | |
111 expect(testDiv.children.map((c) => c.text), | |
112 ['', 'aaa', 'bbb', '', 'bbb', 'bbb']); | |
113 })); | |
114 | |
115 }); | |
116 | |
117 group('with template repeat', () { | |
118 | |
119 test('should handle "in" expressions', () => | |
120 setUpTest(''' | |
121 <template id="test" bind> | |
122 <div>{{ data }}</div> | |
123 <template repeat="{{ item in items }}"> | |
124 <div>{{ item }}{{ data }}</div> | |
125 </template> | |
126 </template>''', | |
127 globals: {'items': [1, 2, 3]}, | |
128 model: new Model('a')) | |
129 .then((_) { | |
130 // expect 6 children: two templates, a div and three instances | |
131 expect(testDiv.children.map((c) => c.text), | |
132 ['', 'a', '', '1a', '2a', '3a']); | |
133 })); | |
134 | |
135 }); | |
136 | |
137 group('with template if', () { | |
138 | |
139 Future doTest(value, bool shouldRender) => | |
140 setUpTest(''' | |
141 <template id="test" bind> | |
142 <div>{{ data }}</div> | |
143 <template if="{{ show }}"> | |
144 <div>{{ data }}</div> | |
145 </template> | |
146 </template>''', | |
147 globals: {'show': value}, | |
148 model: new Model('a')) | |
149 .then((_) { | |
150 if (shouldRender) { | |
151 expect(testDiv.children.length, 4); | |
152 expect(testDiv.children[1].text, 'a'); | |
153 expect(testDiv.children[3].text, 'a'); | |
154 } else { | |
155 expect(testDiv.children.length, 3); | |
156 expect(testDiv.children[1].text, 'a'); | |
157 } | |
158 }); | |
159 | |
160 test('should render for a true expression', | |
161 () => doTest(true, true)); | |
162 | |
163 test('should treat a non-null expression as truthy', | |
164 () => doTest('a', true)); | |
165 | |
166 test('should treat an empty list as truthy', | |
167 () => doTest([], true)); | |
168 | |
169 test('should handle a false expression', | |
170 () => doTest(false, false)); | |
171 | |
172 test('should treat null as falsey', | |
173 () => doTest(null, false)); | |
174 }); | |
175 | |
176 group('error handling', () { | |
177 | |
178 test('should handle and log bad variable names', () { | |
179 var logger = new Logger('polymer_expressions'); | |
180 var logFuture = logger.onRecord.toList(); | |
181 return setUpTest(''' | |
182 <template id="test" bind> | |
183 <span>A</span> | |
184 <span>{{ foo }}</span> | |
185 <span>B</span> | |
186 </template>''') | |
187 .then((_) { | |
188 expect(testDiv.children.length, 4); | |
189 expect(testDiv.children.skip(1).map((c) => c.text), ['A', '', 'B']); | |
190 logger.clearListeners(); | |
191 return logFuture.then((records) { | |
192 expect(records.length, 1); | |
193 expect(records.first.message, | |
194 contains('Error evaluating expression')); | |
195 expect(records.first.message, contains('foo')); | |
196 }); | |
78 }); | 197 }); |
79 }); | 198 }); |
199 | |
200 test('should handle null collections in "in" expressions', () => | |
201 setUpTest(''' | |
202 <template id="test" bind> | |
203 <template repeat="{{ item in items }}"> | |
204 {{ item }} | |
205 </template> | |
206 </template>''', | |
207 globals: {'items': null}) | |
208 .then((_) { | |
209 expect(testDiv.children.length, 2); | |
210 expect(testDiv.children[0].id, 'test'); | |
211 })); | |
212 | |
80 }); | 213 }); |
81 }); | 214 }); |
82 } | 215 } |
83 | 216 |
217 Future<Element> waitForChange(Element e) { | |
218 var completer = new Completer<Element>(); | |
219 new MutationObserver((mutations, observer) { | |
220 observer.disconnect(); | |
221 completer.complete(e); | |
222 }).observe(e, childList: true); | |
223 return completer.future; | |
224 } | |
225 | |
84 @reflectable | 226 @reflectable |
85 class Person extends ChangeNotifier { | 227 class Model extends ChangeNotifier { |
86 String _firstName; | 228 String _data; |
87 String _lastName; | 229 |
88 List<String> _items; | 230 Model(this._data); |
89 | 231 |
90 Person(this._firstName, this._lastName, this._items); | 232 String get data => _data; |
91 | 233 |
92 String get firstName => _firstName; | 234 void set data(String value) { |
93 | 235 _data = notifyPropertyChange(#data, _data, value); |
94 void set firstName(String value) { | |
95 _firstName = notifyPropertyChange(#firstName, _firstName, value); | |
96 } | 236 } |
97 | 237 |
98 String get lastName => _lastName; | 238 String toString() => "Model(data: $_data)"; |
99 | |
100 void set lastName(String value) { | |
101 _lastName = notifyPropertyChange(#lastName, _lastName, value); | |
102 } | |
103 | |
104 String getFullName() => '$_firstName $_lastName'; | |
105 | |
106 List<String> get items => _items; | |
107 | |
108 void set items(List<String> value) { | |
109 _items = notifyPropertyChange(#items, _items, value); | |
110 } | |
111 | |
112 String toString() => "Person(firstName: $_firstName, lastName: $_lastName)"; | |
113 } | 239 } |
OLD | NEW |