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 library bindings_test; | 5 library bindings_test; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:html'; | 8 import 'dart:html'; |
| 9 | 9 |
| 10 import 'package:observe/observe.dart'; | 10 import 'package:observe/observe.dart'; |
| 11 import 'package:observe/mirrors_used.dart'; // make test smaller. | 11 import 'package:observe/mirrors_used.dart'; // make test smaller. |
| 12 import 'package:observe/src/dirty_check.dart' show dirtyCheckZone; | 12 import 'package:observe/src/dirty_check.dart' show dirtyCheckZone; |
| 13 import 'package:polymer_expressions/polymer_expressions.dart'; | 13 import 'package:polymer_expressions/polymer_expressions.dart'; |
| 14 import 'package:template_binding/template_binding.dart' show templateBind; | 14 import 'package:template_binding/template_binding.dart' show templateBind; |
| 15 import 'package:unittest/html_config.dart'; | 15 import 'package:unittest/html_config.dart'; |
| 16 import 'package:unittest/unittest.dart'; | 16 import 'package:unittest/unittest.dart'; |
| 17 | 17 |
| 18 var testDiv; | |
| 19 | |
| 18 main() => dirtyCheckZone().run(() { | 20 main() => dirtyCheckZone().run(() { |
| 19 useHtmlConfiguration(); | 21 useHtmlConfiguration(); |
| 20 | 22 |
| 21 group('bindings', () { | 23 group('bindings', () { |
| 22 var stop = null; | 24 var stop = null; |
| 23 var testDiv; | |
| 24 setUp(() { | 25 setUp(() { |
| 25 document.body.append(testDiv = new DivElement()); | 26 document.body.append(testDiv = new DivElement()); |
| 26 }); | 27 }); |
| 27 | 28 |
| 28 tearDown(() { | 29 tearDown(() { |
| 29 testDiv.remove(); | 30 testDiv.remove(); |
| 30 testDiv = null; | 31 testDiv = null; |
| 31 }); | 32 }); |
| 32 | 33 |
| 33 test('should update binding when data changes', () { | 34 test('should update binding when data changes', () { |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 69 new PolymerExpressions())); | 70 new PolymerExpressions())); |
| 70 | 71 |
| 71 return _nextMicrotask(null); | 72 return _nextMicrotask(null); |
| 72 }, onError: (e) { | 73 }, onError: (e) { |
| 73 expect('$e', startsWith("Error evaluating expression 'foo':")); | 74 expect('$e', startsWith("Error evaluating expression 'foo':")); |
| 74 completer.complete(true); | 75 completer.complete(true); |
| 75 }); | 76 }); |
| 76 return completer.future; | 77 return completer.future; |
| 77 }); | 78 }); |
| 78 | 79 |
| 79 test('should preserve the cursor position', () { | |
| 80 var model = new NotifyModel('abcde'); | |
| 81 var template = templateBind(new Element.html( | |
| 82 '<template><input id="i1" value={{x}}></template>')); | |
| 83 testDiv.append(template.createInstance(model, new PolymerExpressions())); | |
| 84 | |
| 85 var el; | |
| 86 return new Future(() { | |
| 87 el = testDiv.query("#i1"); | |
| 88 var subscription = el.onInput.listen(expectAsync((_) {}, count: 1)); | |
| 89 el.focus(); | |
| 90 | |
| 91 expect(el.value, 'abcde'); | |
| 92 expect(model.x, 'abcde'); | |
| 93 | |
| 94 el.selectionStart = 3; | |
| 95 el.selectionEnd = 3; | |
| 96 expect(el.selectionStart, 3); | |
| 97 expect(el.selectionEnd, 3); | |
| 98 | |
| 99 el.value = 'abc de'; | |
| 100 // Updating the input value programatically (even to the same value in | |
| 101 // Chrome) loses the selection position. | |
| 102 expect(el.selectionStart, 6); | |
| 103 expect(el.selectionEnd, 6); | |
| 104 | |
| 105 el.selectionStart = 4; | |
| 106 el.selectionEnd = 4; | |
| 107 | |
| 108 expect(model.x, 'abcde'); | |
| 109 el.dispatchEvent(new Event('input')); | |
| 110 expect(model.x, 'abc de'); | |
| 111 expect(el.value, 'abc de'); | |
| 112 | |
| 113 // But propagating observable values through reassign the value and | |
| 114 // selection will be preserved. | |
| 115 expect(el.selectionStart, 4); | |
| 116 expect(el.selectionEnd, 4); | |
| 117 subscription.cancel(); | |
| 118 }).then(_nextMicrotask).then((_) { | |
| 119 // Nothing changes on the next micro task. | |
| 120 expect(el.selectionStart, 4); | |
| 121 expect(el.selectionEnd, 4); | |
| 122 }).then((_) => window.animationFrame).then((_) { | |
| 123 // ... or on the next animation frame. | |
| 124 expect(el.selectionStart, 4); | |
| 125 expect(el.selectionEnd, 4); | |
| 126 }).then(_afterTimeout).then((_) { | |
| 127 // ... or later. | |
| 128 expect(el.selectionStart, 4); | |
| 129 expect(el.selectionEnd, 4); | |
| 130 }); | |
| 131 }); | |
| 132 | |
| 133 test('detects changes to ObservableList', () { | 80 test('detects changes to ObservableList', () { |
| 134 var list = new ObservableList.from([1, 2, 3]); | 81 var list = new ObservableList.from([1, 2, 3]); |
| 135 var template = templateBind(new Element.html( | 82 var template = templateBind(new Element.html( |
| 136 '<template>{{x[1]}}</template>')); | 83 '<template>{{x[1]}}</template>')); |
| 137 var model = new NotifyModel(list); | 84 var model = new NotifyModel(list); |
| 138 testDiv.append(template.createInstance(model, new PolymerExpressions())); | 85 testDiv.append(template.createInstance(model, new PolymerExpressions())); |
| 139 | 86 |
| 140 return new Future(() { | 87 return new Future(() { |
| 141 expect(testDiv.text, '2'); | 88 expect(testDiv.text, '2'); |
| 142 list[1] = 10; | 89 list[1] = 10; |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 170 expect(testDiv.text, 'a:1,b:2,'); | 117 expect(testDiv.text, 'a:1,b:2,'); |
| 171 map.remove('b'); | 118 map.remove('b'); |
| 172 map['c'] = 3; | 119 map['c'] = 3; |
| 173 }).then(_nextMicrotask).then((_) { | 120 }).then(_nextMicrotask).then((_) { |
| 174 expect(testDiv.text, 'a:1,c:3,'); | 121 expect(testDiv.text, 'a:1,c:3,'); |
| 175 map['a'] = 4; | 122 map['a'] = 4; |
| 176 }).then(_nextMicrotask).then((_) { | 123 }).then(_nextMicrotask).then((_) { |
| 177 expect(testDiv.text, 'a:4,c:3,'); | 124 expect(testDiv.text, 'a:4,c:3,'); |
| 178 }); | 125 }); |
| 179 }); | 126 }); |
| 127 | |
| 128 // TODO(sigmund): enable this test (issue 19105) | |
| 129 // _cursorPositionTest(false); | |
| 130 _cursorPositionTest(true); | |
| 131 | |
| 132 // Regression tests for issue 18792. | |
| 133 for (var usePolymer in [true, false]) { | |
| 134 // We run these tests both with PolymerExpressions and with the default | |
| 135 // delegate to ensure the results are consistent. The expressions on these | |
| 136 // tests use syntax common to both delegates. | |
| 137 var name = usePolymer ? 'polymer-expressions' : 'default'; | |
| 138 group('$name delegate', () { | |
| 139 // Use <option template repeat="{{y}}" value="{{}}">item {{}} | |
| 140 _initialSelectTest('{{y}}', '{{}}', usePolymer); | |
| 141 _updateSelectTest('{{y}}', '{{}}', usePolymer); | |
| 142 }); | |
| 143 } | |
| 144 | |
| 145 group('polymer-expressions delegate, polymer syntax', () { | |
| 146 // Use <option template repeat="{{i in y}}" value="{{i}}">item {{i}} | |
| 147 _initialSelectTest('{{i in y}}', '{{i}}', true); | |
| 148 _updateSelectTest('{{i in y}}', '{{i}}', true); | |
| 149 }); | |
| 180 }); | 150 }); |
| 181 }); | 151 }); |
| 182 | 152 |
| 153 | |
| 154 _cursorPositionTest(bool usePolymer) { | |
| 155 test('should preserve the cursor position', () { | |
|
Siggi Cherem (dart-lang)
2014/05/30 21:36:03
this test is unchanged, just moved down and change
| |
| 156 var model = new NotifyModel('abcde'); | |
| 157 var template = templateBind(new Element.html( | |
| 158 '<template><input id="i1" value={{x}}></template>')); | |
| 159 var delegate = usePolymer ? new PolymerExpressions() : null; | |
| 160 testDiv.append(template.createInstance(model, delegate)); | |
| 161 | |
| 162 var el; | |
| 163 return new Future(() { | |
| 164 el = testDiv.query("#i1"); | |
| 165 var subscription = el.onInput.listen(expectAsync((_) {}, count: 1)); | |
| 166 el.focus(); | |
| 167 | |
| 168 expect(el.value, 'abcde'); | |
| 169 expect(model.x, 'abcde'); | |
| 170 | |
| 171 el.selectionStart = 3; | |
| 172 el.selectionEnd = 3; | |
| 173 expect(el.selectionStart, 3); | |
| 174 expect(el.selectionEnd, 3); | |
| 175 | |
| 176 el.value = 'abc de'; | |
| 177 // Updating the input value programatically (even to the same value in | |
| 178 // Chrome) loses the selection position. | |
| 179 expect(el.selectionStart, 6); | |
| 180 expect(el.selectionEnd, 6); | |
| 181 | |
| 182 el.selectionStart = 4; | |
| 183 el.selectionEnd = 4; | |
| 184 | |
| 185 expect(model.x, 'abcde'); | |
| 186 el.dispatchEvent(new Event('input')); | |
| 187 expect(model.x, 'abc de'); | |
| 188 expect(el.value, 'abc de'); | |
| 189 | |
| 190 // But propagating observable values through reassign the value and | |
| 191 // selection will be preserved. | |
| 192 expect(el.selectionStart, 4); | |
| 193 expect(el.selectionEnd, 4); | |
| 194 subscription.cancel(); | |
| 195 }).then(_nextMicrotask).then((_) { | |
| 196 // Nothing changes on the next micro task. | |
| 197 expect(el.selectionStart, 4); | |
| 198 expect(el.selectionEnd, 4); | |
| 199 }).then((_) => window.animationFrame).then((_) { | |
| 200 // ... or on the next animation frame. | |
| 201 expect(el.selectionStart, 4); | |
| 202 expect(el.selectionEnd, 4); | |
| 203 }).then(_afterTimeout).then((_) { | |
| 204 // ... or later. | |
| 205 expect(el.selectionStart, 4); | |
| 206 expect(el.selectionEnd, 4); | |
| 207 }); | |
| 208 }); | |
| 209 } | |
| 210 | |
| 211 _initialSelectTest(String repeatExp, String valueExp, bool usePolymer) { | |
| 212 test('initial select value is set correctly', () { | |
| 213 var list = const ['a', 'b']; | |
| 214 var template = templateBind(new Element.html('<template>' | |
| 215 '<select value="{{x}}">' | |
| 216 '<option template repeat="$repeatExp" value="$valueExp">item $valueExp' | |
| 217 '</option></select></template>', | |
| 218 treeSanitizer: _nullTreeSanitizer)); | |
| 219 var model = new NotifyModel('b', list); | |
| 220 var delegate = usePolymer ? new PolymerExpressions() : null; | |
| 221 testDiv.append(template.createInstance(model, delegate)); | |
| 222 | |
| 223 expect(testDiv.querySelector('select').value, 'b'); | |
| 224 return new Future(() { | |
| 225 expect(model.x, 'b'); | |
| 226 expect(testDiv.querySelector('select').value, 'b'); | |
| 227 }); | |
| 228 }); | |
| 229 } | |
| 230 | |
| 231 _updateSelectTest(String repeatExp, String valueExp, bool usePolymer) { | |
| 232 test('updates to select value propagate correctly', () { | |
| 233 var list = const ['a', 'b']; | |
| 234 var template = templateBind(new Element.html('<template>' | |
| 235 '<select value="{{x}}">' | |
| 236 '<option template repeat="$repeatExp" value="$valueExp">item $valueExp' | |
| 237 '</option></select></template>', | |
| 238 treeSanitizer: _nullTreeSanitizer)); | |
| 239 var model = new NotifyModel('a', list); | |
| 240 var delegate = usePolymer ? new PolymerExpressions() : null; | |
| 241 testDiv.append(template.createInstance(model, delegate)); | |
| 242 | |
| 243 expect(testDiv.querySelector('select').value, 'a'); | |
| 244 return new Future(() { | |
| 245 expect(testDiv.querySelector('select').value, 'a'); | |
| 246 model.x = 'b'; | |
| 247 }).then(_nextMicrotask).then((_) { | |
| 248 expect(testDiv.querySelector('select').value, 'b'); | |
| 249 }); | |
| 250 }); | |
| 251 } | |
| 252 | |
| 183 _nextMicrotask(_) => new Future(() {}); | 253 _nextMicrotask(_) => new Future(() {}); |
| 184 _afterTimeout(_) => new Future.delayed(new Duration(milliseconds: 30), () {}); | 254 _afterTimeout(_) => new Future.delayed(new Duration(milliseconds: 30), () {}); |
| 185 | 255 |
| 186 @reflectable | 256 @reflectable |
| 187 class NotifyModel extends ChangeNotifier { | 257 class NotifyModel extends ChangeNotifier { |
| 188 var _x; | 258 var _x; |
| 189 NotifyModel([this._x]); | 259 var _y; |
| 260 NotifyModel([this._x, this._y]); | |
| 190 | 261 |
| 191 get x => _x; | 262 get x => _x; |
| 192 set x(value) { | 263 set x(value) { |
| 193 _x = notifyPropertyChange(#x, _x, value); | 264 _x = notifyPropertyChange(#x, _x, value); |
| 194 } | 265 } |
| 266 | |
| 267 get y => _y; | |
| 268 set y(value) { | |
| 269 _y = notifyPropertyChange(#y, _y, value); | |
| 270 } | |
| 195 } | 271 } |
| 272 | |
| 273 class _NullTreeSanitizer implements NodeTreeSanitizer { | |
| 274 void sanitizeTree(Node node) {} | |
| 275 } | |
| 276 final _nullTreeSanitizer = new _NullTreeSanitizer(); | |
| OLD | NEW |