OLD | NEW |
| (Empty) |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library template_binding.test.binding_syntax; | |
6 | |
7 import 'dart:async'; | |
8 import 'dart:collection'; | |
9 import 'dart:html'; | |
10 import 'package:template_binding/template_binding.dart'; | |
11 import 'package:observe/observe.dart'; | |
12 import 'package:unittest/unittest.dart'; | |
13 import 'utils.dart'; | |
14 | |
15 // Note: this test is executed by template_element_test.dart | |
16 | |
17 syntaxTests(FooBarModel fooModel([foo, bar])) { | |
18 test('prepareBinding', () { | |
19 var model = fooModel('bar'); | |
20 var testSyntax = new TestBindingSyntax(); | |
21 var div = createTestHtml( | |
22 '<template bind>{{ foo }}' | |
23 '<template bind>{{ foo }}</template>' | |
24 '</template>'); | |
25 var template = templateBind(div.firstChild); | |
26 template | |
27 ..model = model | |
28 ..bindingDelegate = testSyntax; | |
29 return new Future(() { | |
30 expect(div.nodes.length, 4); | |
31 expect(div.nodes.last.text, 'bar'); | |
32 expect(div.nodes[2].tagName, 'TEMPLATE'); | |
33 expect(testSyntax.log, [ | |
34 ['prepare', '', 'bind', 'TEMPLATE'], | |
35 ['bindFn', model, 'TEMPLATE', 0], | |
36 ['prepare', 'foo', 'text', 'TEXT'], | |
37 ['prepare', '', 'bind', 'TEMPLATE'], | |
38 ['bindFn', model, 'TEXT', 2], | |
39 ['bindFn', model, 'TEMPLATE', 3], | |
40 ['prepare', 'foo', 'text', 'TEXT'], | |
41 ['bindFn', model, 'TEXT', 6], | |
42 ]); | |
43 }); | |
44 }); | |
45 | |
46 test('prepareInstanceModel', () { | |
47 var model = toObservable([fooModel(1), fooModel(2), fooModel(3)]); | |
48 | |
49 var testSyntax = new TestModelSyntax(); | |
50 testSyntax.altModels.addAll([fooModel('a'), fooModel('b'), fooModel('c')]); | |
51 | |
52 var div = createTestHtml('<template repeat>{{ foo }}</template>'); | |
53 | |
54 var template = div.nodes[0]; | |
55 templateBind(template) | |
56 ..model = model | |
57 ..bindingDelegate = testSyntax; | |
58 return new Future(() { | |
59 | |
60 expect(div.nodes.length, 4); | |
61 expect(div.nodes[0].tagName, 'TEMPLATE'); | |
62 expect(div.nodes[1].text, 'a'); | |
63 expect(div.nodes[2].text, 'b'); | |
64 expect(div.nodes[3].text, 'c'); | |
65 | |
66 expect(testSyntax.log, [ | |
67 ['prepare', template], | |
68 ['bindFn', model[0]], | |
69 ['bindFn', model[1]], | |
70 ['bindFn', model[2]], | |
71 ]); | |
72 }); | |
73 }); | |
74 | |
75 test('prepareInstanceModel - reorder instances', () { | |
76 var model = toObservable([0, 1, 2]); | |
77 | |
78 var div = createTestHtml('<template repeat>{{}}</template>'); | |
79 var template = div.firstChild; | |
80 var delegate = new TestInstanceModelSyntax(); | |
81 | |
82 templateBind(template) | |
83 ..model = model | |
84 ..bindingDelegate = delegate; | |
85 return new Future(() { | |
86 expect(delegate.prepareCount, 1); | |
87 expect(delegate.callCount, 3); | |
88 | |
89 // Note: intentionally mutate in place. | |
90 model.replaceRange(0, model.length, model.reversed.toList()); | |
91 }).then(endOfMicrotask).then((_) { | |
92 expect(delegate.prepareCount, 1); | |
93 expect(delegate.callCount, 3); | |
94 }); | |
95 }); | |
96 | |
97 test('prepareInstancePositionChanged', () { | |
98 var model = toObservable(['a', 'b', 'c']); | |
99 | |
100 var div = createTestHtml('<template repeat>{{}}</template>'); | |
101 var delegate = new TestPositionChangedSyntax(); | |
102 | |
103 var template = div.nodes[0]; | |
104 templateBind(template) | |
105 ..model = model | |
106 ..bindingDelegate = delegate; | |
107 return new Future(() { | |
108 | |
109 expect(div.nodes.length, 4); | |
110 expect(div.nodes[0].tagName, 'TEMPLATE'); | |
111 expect(div.nodes[1].text, 'a'); | |
112 expect(div.nodes[2].text, 'b'); | |
113 expect(div.nodes[3].text, 'c'); | |
114 | |
115 expect(delegate.log, [ | |
116 ['prepare', template], | |
117 ['bindFn', model[0], 0], | |
118 ['bindFn', model[1], 1], | |
119 ['bindFn', model[2], 2], | |
120 ]); | |
121 | |
122 delegate.log.clear(); | |
123 | |
124 model.removeAt(1); | |
125 }).then(endOfMicrotask).then((_) { | |
126 | |
127 expect(delegate.log, [['bindFn', 'c', 1]], reason: 'removed item'); | |
128 | |
129 expect(div.nodes.skip(1).map((n) => n.text), ['a', 'c']); | |
130 }); | |
131 }); | |
132 | |
133 | |
134 test('Update bindingDelegate with active template', () { | |
135 var model = toObservable([1, 2]); | |
136 | |
137 var div = createTestHtml( | |
138 '<template repeat>{{ \$index }} - {{ \$ident }}</template>'); | |
139 var template = templateBind(div.firstChild) | |
140 ..bindingDelegate = new UpdateBindingDelegateA() | |
141 ..model = model; | |
142 | |
143 return new Future(() { | |
144 expect(div.nodes.length, 3); | |
145 expect(div.nodes[1].text, 'i:0 - a:1'); | |
146 expect(div.nodes[2].text, 'i:1 - a:2'); | |
147 | |
148 expect(() { | |
149 template.bindingDelegate = new UpdateBindingDelegateB(); | |
150 }, throws); | |
151 | |
152 template.clear(); | |
153 expect(div.nodes.length, 1); | |
154 | |
155 template | |
156 ..bindingDelegate = new UpdateBindingDelegateB() | |
157 ..model = model; | |
158 | |
159 model.add(3); | |
160 }).then(nextMicrotask).then((_) { | |
161 // All instances should reflect delegateB | |
162 expect(4, div.nodes.length); | |
163 expect(div.nodes[1].text, 'I:0 - A:1-narg'); | |
164 expect(div.nodes[2].text, 'I:2 - A:2-narg'); | |
165 expect(div.nodes[3].text, 'I:4 - A:3-narg'); | |
166 }); | |
167 }); | |
168 | |
169 test('Basic', () { | |
170 var model = fooModel(2, 4); | |
171 var div = createTestHtml( | |
172 '<template bind>' | |
173 '{{ foo }} + {{ 2x: bar }} + {{ 4x: bar }}</template>'); | |
174 var template = templateBind(div.firstChild); | |
175 template | |
176 ..model = model | |
177 ..bindingDelegate = new TimesTwoSyntax(); | |
178 return new Future(() { | |
179 expect(div.nodes.length, 2); | |
180 expect(div.nodes.last.text, '2 + 8 + '); | |
181 | |
182 model.foo = 4; | |
183 model.bar = 8; | |
184 }).then(endOfMicrotask).then((_) { | |
185 expect(div.nodes.last.text, '4 + 16 + '); | |
186 }); | |
187 }); | |
188 | |
189 test('CreateInstance', () { | |
190 var delegateFoo = new SimpleTextDelegate('foo'); | |
191 var delegateBar = new SimpleTextDelegate('bar'); | |
192 | |
193 var div = createTestHtml('<template bind>[[ 2x: bar ]]</template>'); | |
194 var template = templateBind(div.firstChild); | |
195 template..bindingDelegate = delegateFoo..model = {}; | |
196 | |
197 return new Future(() { | |
198 expect(div.nodes.length, 2); | |
199 expect(div.lastChild.text, 'foo'); | |
200 | |
201 var fragment = template.createInstance({}); | |
202 expect(fragment.nodes.length, 1); | |
203 expect(fragment.lastChild.text, 'foo'); | |
204 | |
205 fragment = template.createInstance({}, delegateBar); | |
206 expect(fragment.nodes.length, 1); | |
207 expect(fragment.lastChild.text, 'bar'); | |
208 }); | |
209 }); | |
210 | |
211 // Note: issue-141 test not included here as it's not related to the | |
212 // BindingDelegate | |
213 } | |
214 | |
215 // TODO(jmesserly): mocks would be cleaner here. | |
216 | |
217 class TestBindingSyntax extends BindingDelegate { | |
218 var log = []; | |
219 | |
220 prepareBinding(String path, String name, Node node) { | |
221 var tagName = node is Element ? node.tagName : 'TEXT'; | |
222 int id = log.length; | |
223 log.add(['prepare', path, name, tagName]); | |
224 final outerNode = node; | |
225 return (model, node, oneTime) { | |
226 var tagName = node is Element ? node.tagName : 'TEXT'; | |
227 log.add(['bindFn', model, tagName, id]); | |
228 return oneTime ? new PropertyPath(path).getValueFrom(model) : | |
229 new PathObserver(model, path); | |
230 }; | |
231 } | |
232 } | |
233 | |
234 class SimpleTextDelegate extends BindingDelegate { | |
235 final String text; | |
236 SimpleTextDelegate(this.text); | |
237 | |
238 prepareBinding(path, name, node) => | |
239 name != 'text' ? null : (_, __, ___) => text; | |
240 } | |
241 | |
242 class TestModelSyntax extends BindingDelegate { | |
243 var log = []; | |
244 var altModels = new ListQueue(); | |
245 | |
246 prepareInstanceModel(template) { | |
247 log.add(['prepare', template]); | |
248 return (model) { | |
249 log.add(['bindFn', model]); | |
250 return altModels.removeFirst(); | |
251 }; | |
252 } | |
253 } | |
254 | |
255 class TestInstanceModelSyntax extends BindingDelegate { | |
256 int prepareCount = 0; | |
257 int callCount = 0; | |
258 prepareInstanceModel(template) { | |
259 prepareCount++; | |
260 return (model) { | |
261 callCount++; | |
262 return model; | |
263 }; | |
264 } | |
265 } | |
266 | |
267 | |
268 class TestPositionChangedSyntax extends BindingDelegate { | |
269 var log = []; | |
270 | |
271 prepareInstancePositionChanged(template) { | |
272 int id = log.length; | |
273 log.add(['prepare', template]); | |
274 return (templateInstance, index) { | |
275 log.add(['bindFn', templateInstance.model, index]); | |
276 }; | |
277 } | |
278 } | |
279 | |
280 | |
281 class TimesTwoSyntax extends BindingDelegate { | |
282 prepareBinding(path, name, node) { | |
283 path = path.trim(); | |
284 if (!path.startsWith('2x:')) return null; | |
285 | |
286 path = path.substring(3); | |
287 return (model, _, oneTime) { | |
288 return new ObserverTransform(new PathObserver(model, path), (x) => 2 * x); | |
289 }; | |
290 } | |
291 } | |
292 | |
293 class UpdateBindingDelegateBase extends BindingDelegate { | |
294 bindingHandler(prefix, path) => (model, _, oneTime) => | |
295 new ObserverTransform(new PathObserver(model, path), (x) => '$prefix:$x'); | |
296 } | |
297 | |
298 class UpdateBindingDelegateA extends UpdateBindingDelegateBase { | |
299 prepareBinding(path, name, node) { | |
300 if (path == '\$ident') return bindingHandler('a', 'id'); | |
301 if (path == '\$index') return bindingHandler('i', 'index'); | |
302 } | |
303 | |
304 prepareInstanceModel(template) => (model) => toObservable({ 'id': model }); | |
305 | |
306 prepareInstancePositionChanged(template) => (templateInstance, index) { | |
307 templateInstance.model['index'] = index; | |
308 }; | |
309 } | |
310 | |
311 class UpdateBindingDelegateB extends UpdateBindingDelegateBase { | |
312 prepareBinding(path, name, node) { | |
313 if (path == '\$ident') return bindingHandler('A', 'id'); | |
314 if (path == '\$index') return bindingHandler('I', 'index'); | |
315 } | |
316 | |
317 prepareInstanceModel(template) => | |
318 (model) => toObservable({ 'id': '${model}-narg' }); | |
319 | |
320 | |
321 prepareInstancePositionChanged(template) => (templateInstance, index) { | |
322 templateInstance.model['index'] = 2 * index; | |
323 }; | |
324 } | |
325 | |
OLD | NEW |