Chromium Code Reviews| 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 |