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 template_binding.test.custom_element_bindings_test; | 5 library template_binding.test.custom_element_bindings_test; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:html'; | 8 import 'dart:html'; |
9 import 'dart:collection' show MapView; | 9 import 'dart:collection' show MapView; |
10 import 'package:template_binding/template_binding.dart'; | 10 import 'package:template_binding/template_binding.dart'; |
11 import 'package:observe/observe.dart'; | 11 import 'package:observe/observe.dart'; |
12 import 'package:unittest/html_config.dart'; | 12 import 'package:unittest/html_config.dart'; |
13 import 'package:unittest/unittest.dart'; | 13 import 'package:unittest/unittest.dart'; |
14 import 'package:web_components/polyfill.dart'; | 14 import 'package:web_components/polyfill.dart'; |
15 import 'utils.dart'; | 15 import 'utils.dart'; |
16 | 16 |
17 Future _registered; | 17 Future _registered; |
18 | 18 |
19 main() => dirtyCheckZone().run(() { | 19 main() => dirtyCheckZone().run(() { |
20 useHtmlConfiguration(); | 20 useHtmlConfiguration(); |
21 | 21 |
22 _registered = customElementsReady.then((_) { | 22 _registered = customElementsReady.then((_) { |
23 document.register('my-custom-element', MyCustomElement); | 23 document.registerElement('my-custom-element', MyCustomElement); |
24 document.register('with-attrs-custom-element', WithAttrsCustomElement); | 24 document.registerElement('with-attrs-custom-element', |
| 25 WithAttrsCustomElement); |
25 }); | 26 }); |
26 | 27 |
27 group('Custom Element Bindings', customElementBindingsTest); | 28 group('Custom Element Bindings', customElementBindingsTest); |
28 }); | 29 }); |
29 | 30 |
30 customElementBindingsTest() { | 31 customElementBindingsTest() { |
31 setUp(() { | 32 setUp(() { |
32 document.body.append(testDiv = new DivElement()); | 33 document.body.append(testDiv = new DivElement()); |
33 return _registered; | 34 return _registered; |
34 }); | 35 }); |
35 | 36 |
36 tearDown(() { | 37 tearDown(() { |
37 testDiv.remove(); | 38 testDiv.remove(); |
38 testDiv = null; | 39 testDiv = null; |
39 }); | 40 }); |
40 | 41 |
41 test('override bind/unbind/unbindAll', () { | 42 test('override bind/bindFinished', () { |
42 var element = new MyCustomElement(); | 43 var element = new MyCustomElement(); |
43 var model = toObservable({'a': new Point(123, 444), 'b': new Monster(100)}); | 44 var model = toObservable({'a': new Point(123, 444), 'b': new Monster(100)}); |
44 | 45 |
45 nodeBind(element) | 46 var pointBinding = nodeBind(element) |
46 ..bind('my-point', new PathObserver(model, 'a')) | 47 .bind('my-point', new PathObserver(model, 'a')); |
47 ..bind('scary-monster', new PathObserver(model, 'b')); | 48 |
| 49 var scaryBinding = nodeBind(element) |
| 50 .bind('scary-monster', new PathObserver(model, 'b')); |
48 | 51 |
49 expect(element.attributes, isNot(contains('my-point'))); | 52 expect(element.attributes, isNot(contains('my-point'))); |
50 expect(element.attributes, isNot(contains('scary-monster'))); | 53 expect(element.attributes, isNot(contains('scary-monster'))); |
51 | 54 |
52 expect(element.myPoint, model['a']); | 55 expect(element.myPoint, model['a']); |
53 expect(element.scaryMonster, model['b']); | 56 expect(element.scaryMonster, model['b']); |
54 | 57 |
55 model['a'] = null; | 58 model['a'] = null; |
56 return new Future(() { | 59 return new Future(() { |
57 expect(element.myPoint, null); | 60 expect(element.myPoint, null); |
58 nodeBind(element).unbind('my-point'); | 61 expect(element.bindFinishedCalled, 0); |
| 62 pointBinding.close(); |
59 | 63 |
60 model['a'] = new Point(1, 2); | 64 model['a'] = new Point(1, 2); |
61 model['b'] = new Monster(200); | 65 model['b'] = new Monster(200); |
62 }).then(endOfMicrotask).then((_) { | 66 }).then(endOfMicrotask).then((_) { |
63 expect(element.scaryMonster, model['b']); | 67 expect(element.scaryMonster, model['b']); |
64 expect(element.myPoint, null, reason: 'a was unbound'); | 68 expect(element.myPoint, null, reason: 'a was unbound'); |
65 | 69 |
66 nodeBind(element).unbindAll(); | 70 scaryBinding.close(); |
67 model['b'] = null; | 71 model['b'] = null; |
68 }).then(endOfMicrotask).then((_) { | 72 }).then(endOfMicrotask).then((_) { |
69 expect(element.scaryMonster.health, 200); | 73 expect(element.scaryMonster.health, 200); |
| 74 expect(element.bindFinishedCalled, 0); |
70 }); | 75 }); |
71 }); | 76 }); |
72 | 77 |
73 test('override attribute setter', () { | 78 test('override attribute setter', () { |
74 var element = new WithAttrsCustomElement(); | 79 var element = new WithAttrsCustomElement(); |
75 var model = toObservable({'a': 1, 'b': 2}); | 80 var model = toObservable({'a': 1, 'b': 2}); |
76 nodeBind(element).bind('hidden?', new PathObserver(model, 'a')); | 81 nodeBind(element).bind('hidden?', new PathObserver(model, 'a')); |
77 nodeBind(element).bind('id', new PathObserver(model, 'b')); | 82 nodeBind(element).bind('id', new PathObserver(model, 'b')); |
78 | 83 |
79 expect(element.attributes, contains('hidden')); | 84 expect(element.attributes, contains('hidden')); |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
117 | 122 |
118 var model = toObservable({'a': new Point(123, 444), 'b': new Monster(100)}); | 123 var model = toObservable({'a': new Point(123, 444), 'b': new Monster(100)}); |
119 var div = createTestHtml('<template bind>' | 124 var div = createTestHtml('<template bind>' |
120 '<my-custom-element my-point="{{a}}" scary-monster="{{b}}">' | 125 '<my-custom-element my-point="{{a}}" scary-monster="{{b}}">' |
121 '</my-custom-element>' | 126 '</my-custom-element>' |
122 '</template>'); | 127 '</template>'); |
123 | 128 |
124 templateBind(div.query('template')).model = model; | 129 templateBind(div.query('template')).model = model; |
125 var element; | 130 var element; |
126 return new Future(() { | 131 return new Future(() { |
127 print('!!! running future'); | |
128 element = div.nodes[1]; | 132 element = div.nodes[1]; |
129 | 133 |
130 expect(element is MyCustomElement, true, | 134 expect(element is MyCustomElement, true, |
131 reason: '$element should be a MyCustomElement'); | 135 reason: '$element should be a MyCustomElement'); |
132 | 136 |
133 expect(element.myPoint, model['a']); | 137 expect(element.myPoint, model['a']); |
134 expect(element.scaryMonster, model['b']); | 138 expect(element.scaryMonster, model['b']); |
135 | 139 |
136 expect(element.attributes, isNot(contains('my-point'))); | 140 expect(element.attributes, isNot(contains('my-point'))); |
137 expect(element.attributes, isNot(contains('scary-monster'))); | 141 expect(element.attributes, isNot(contains('scary-monster'))); |
138 | 142 |
| 143 expect(element.bindFinishedCalled, 1); |
| 144 |
139 model['a'] = null; | 145 model['a'] = null; |
140 }).then(endOfMicrotask).then((_) { | 146 }).then(endOfMicrotask).then((_) { |
141 expect(element.myPoint, null); | 147 expect(element.myPoint, null); |
| 148 expect(element.bindFinishedCalled, 1); |
| 149 |
142 | 150 |
143 templateBind(div.query('template')).model = null; | 151 templateBind(div.query('template')).model = null; |
144 }).then(endOfMicrotask).then((_) { | 152 }).then(endOfMicrotask).then((_) { |
145 | 153 // Note: the detached element |
146 expect(element.parentNode, null, reason: 'element was detached'); | 154 expect(element.parentNode is DocumentFragment, true, |
| 155 reason: 'removed element is added back to its document fragment'); |
| 156 expect(element.parentNode.parentNode, null, |
| 157 reason: 'document fragment is detached'); |
| 158 expect(element.bindFinishedCalled, 1); |
147 | 159 |
148 model['a'] = new Point(1, 2); | 160 model['a'] = new Point(1, 2); |
149 model['b'] = new Monster(200); | 161 model['b'] = new Monster(200); |
150 }).then(endOfMicrotask).then((_) { | 162 }).then(endOfMicrotask).then((_) { |
151 | |
152 expect(element.myPoint, null, reason: 'model was unbound'); | 163 expect(element.myPoint, null, reason: 'model was unbound'); |
153 expect(element.scaryMonster.health, 100, reason: 'model was unbound'); | 164 expect(element.scaryMonster.health, 100, reason: 'model was unbound'); |
| 165 expect(element.bindFinishedCalled, 1); |
154 }); | 166 }); |
155 }); | 167 }); |
156 | 168 |
157 } | 169 } |
158 | 170 |
159 class Monster { | 171 class Monster { |
160 int health; | 172 int health; |
161 Monster(this.health); | 173 Monster(this.health); |
162 } | 174 } |
163 | 175 |
164 /** Demonstrates a custom element overriding bind/unbind/unbindAll. */ | 176 /** Demonstrates a custom element overriding bind/bindFinished. */ |
165 class MyCustomElement extends HtmlElement implements NodeBindExtension { | 177 class MyCustomElement extends HtmlElement implements NodeBindExtension { |
166 Point myPoint; | 178 Point myPoint; |
167 Monster scaryMonster; | 179 Monster scaryMonster; |
| 180 int bindFinishedCalled = 0; |
168 | 181 |
169 factory MyCustomElement() => new Element.tag('my-custom-element'); | 182 factory MyCustomElement() => new Element.tag('my-custom-element'); |
170 | 183 |
171 MyCustomElement.created() : super.created(); | 184 MyCustomElement.created() : super.created(); |
172 | 185 |
173 Bindable bind(String name, value, {oneTime: false}) { | 186 Bindable bind(String name, value, {oneTime: false}) { |
174 switch (name) { | 187 switch (name) { |
175 case 'my-point': | 188 case 'my-point': |
176 case 'scary-monster': | 189 case 'scary-monster': |
177 attributes.remove(name); | 190 attributes.remove(name); |
178 if (oneTime) { | 191 if (oneTime) { |
179 _setProperty(name, value); | 192 _setProperty(name, value); |
180 return null; | 193 return null; |
181 } | 194 } |
182 unbind(name); | |
183 _setProperty(name, value.open((x) => _setProperty(name, x))); | 195 _setProperty(name, value.open((x) => _setProperty(name, x))); |
| 196 |
| 197 if (!enableBindingsReflection) return value; |
| 198 if (bindings == null) bindings = {}; |
| 199 var old = bindings[name]; |
| 200 if (old != null) old.close(); |
184 return bindings[name] = value; | 201 return bindings[name] = value; |
185 } | 202 } |
186 return nodeBindFallback(this).bind(name, value, oneTime: oneTime); | 203 return nodeBindFallback(this).bind(name, value, oneTime: oneTime); |
187 } | 204 } |
188 | 205 |
189 unbind(name) => nodeBindFallback(this).unbind(name); | 206 void bindFinished() { |
190 unbindAll() => nodeBindFallback(this).unbindAll(); | 207 bindFinishedCalled++; |
| 208 } |
| 209 |
191 get bindings => nodeBindFallback(this).bindings; | 210 get bindings => nodeBindFallback(this).bindings; |
| 211 set bindings(x) => nodeBindFallback(this).bindings = x; |
192 get templateInstance => nodeBindFallback(this).templateInstance; | 212 get templateInstance => nodeBindFallback(this).templateInstance; |
193 | 213 |
194 void _setProperty(String property, newValue) { | 214 void _setProperty(String property, newValue) { |
195 if (property == 'my-point') myPoint = newValue; | 215 if (property == 'my-point') myPoint = newValue; |
196 if (property == 'scary-monster') scaryMonster = newValue; | 216 if (property == 'scary-monster') scaryMonster = newValue; |
197 } | 217 } |
198 } | 218 } |
199 | 219 |
200 | 220 |
201 /** | 221 /** |
(...skipping 19 matching lines...) Expand all Loading... |
221 | 241 |
222 AttributeMapWrapper(Map map) : super(map); | 242 AttributeMapWrapper(Map map) : super(map); |
223 | 243 |
224 void operator []=(K key, V value) { | 244 void operator []=(K key, V value) { |
225 log.add(['[]=', key, value]); | 245 log.add(['[]=', key, value]); |
226 super[key] = value; | 246 super[key] = value; |
227 } | 247 } |
228 | 248 |
229 V remove(Object key) { | 249 V remove(Object key) { |
230 log.add(['remove', key]); | 250 log.add(['remove', key]); |
231 super.remove(key); | 251 return super.remove(key); |
232 } | 252 } |
233 } | 253 } |
OLD | NEW |