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.binding_syntax; | 5 library template_binding.test.binding_syntax; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:collection'; | 8 import 'dart:collection'; |
9 import 'dart:html'; | 9 import 'dart:html'; |
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/unittest.dart'; | 12 import 'package:unittest/unittest.dart'; |
13 import 'utils.dart'; | 13 import 'utils.dart'; |
14 | 14 |
15 // Note: this test is executed by template_element_test.dart | 15 // Note: this test is executed by template_element_test.dart |
16 | 16 |
17 syntaxTests(FooBarModel fooModel([foo, bar])) { | 17 syntaxTests(FooBarModel fooModel([foo, bar])) { |
18 test('prepareBinding', () { | 18 test('prepareBinding', () { |
19 var model = fooModel('bar'); | 19 var model = fooModel('bar'); |
20 var testSyntax = new TestBindingSyntax(); | 20 var testSyntax = new TestBindingSyntax(); |
21 var div = createTestHtml( | 21 var div = createTestHtml( |
22 '<template bind>{{ foo }}' | 22 '<template bind>{{ foo }}' |
23 '<template bind>{{ foo }}</template>' | 23 '<template bind>{{ foo }}</template>' |
24 '</template>'); | 24 '</template>'); |
25 recursivelySetTemplateModel(div, model, testSyntax); | 25 var template = templateBind(div.firstChild); |
| 26 template |
| 27 ..model = model |
| 28 ..bindingDelegate = testSyntax; |
26 return new Future(() { | 29 return new Future(() { |
27 expect(div.nodes.length, 4); | 30 expect(div.nodes.length, 4); |
28 expect(div.nodes.last.text, 'bar'); | 31 expect(div.nodes.last.text, 'bar'); |
29 expect(div.nodes[2].tagName, 'TEMPLATE'); | 32 expect(div.nodes[2].tagName, 'TEMPLATE'); |
30 expect(testSyntax.log, [ | 33 expect(testSyntax.log, [ |
31 ['prepare', '', 'bind', 'TEMPLATE'], | 34 ['prepare', '', 'bind', 'TEMPLATE'], |
32 ['bindFn', model, 'TEMPLATE', 0], | 35 ['bindFn', model, 'TEMPLATE', 0], |
33 ['prepare', 'foo', 'text', 'TEXT'], | 36 ['prepare', 'foo', 'text', 'TEXT'], |
34 ['prepare', '', 'bind', 'TEMPLATE'], | 37 ['prepare', '', 'bind', 'TEMPLATE'], |
35 ['bindFn', model, 'TEXT', 2], | 38 ['bindFn', model, 'TEXT', 2], |
36 ['bindFn', model, 'TEMPLATE', 3], | 39 ['bindFn', model, 'TEMPLATE', 3], |
37 ['prepare', 'foo', 'text', 'TEXT'], | 40 ['prepare', 'foo', 'text', 'TEXT'], |
38 ['bindFn', model, 'TEXT', 6], | 41 ['bindFn', model, 'TEXT', 6], |
39 ]); | 42 ]); |
40 }); | 43 }); |
41 }); | 44 }); |
42 | 45 |
43 test('prepareInstanceModel', () { | 46 test('prepareInstanceModel', () { |
44 var model = toObservable([fooModel(1), fooModel(2), fooModel(3)]); | 47 var model = toObservable([fooModel(1), fooModel(2), fooModel(3)]); |
45 | 48 |
46 var testSyntax = new TestModelSyntax(); | 49 var testSyntax = new TestModelSyntax(); |
47 testSyntax.altModels.addAll([fooModel('a'), fooModel('b'), fooModel('c')]); | 50 testSyntax.altModels.addAll([fooModel('a'), fooModel('b'), fooModel('c')]); |
48 | 51 |
49 var div = createTestHtml('<template repeat>{{ foo }}</template>'); | 52 var div = createTestHtml('<template repeat>{{ foo }}</template>'); |
50 | 53 |
51 var template = div.nodes[0]; | 54 var template = div.nodes[0]; |
52 recursivelySetTemplateModel(div, model, testSyntax); | 55 templateBind(template) |
| 56 ..model = model |
| 57 ..bindingDelegate = testSyntax; |
53 return new Future(() { | 58 return new Future(() { |
54 | 59 |
55 expect(div.nodes.length, 4); | 60 expect(div.nodes.length, 4); |
56 expect(div.nodes[0].tagName, 'TEMPLATE'); | 61 expect(div.nodes[0].tagName, 'TEMPLATE'); |
57 expect(div.nodes[1].text, 'a'); | 62 expect(div.nodes[1].text, 'a'); |
58 expect(div.nodes[2].text, 'b'); | 63 expect(div.nodes[2].text, 'b'); |
59 expect(div.nodes[3].text, 'c'); | 64 expect(div.nodes[3].text, 'c'); |
60 | 65 |
61 expect(testSyntax.log, [ | 66 expect(testSyntax.log, [ |
62 ['prepare', template], | 67 ['prepare', template], |
63 ['bindFn', model[0]], | 68 ['bindFn', model[0]], |
64 ['bindFn', model[1]], | 69 ['bindFn', model[1]], |
65 ['bindFn', model[2]], | 70 ['bindFn', model[2]], |
66 ]); | 71 ]); |
67 }); | 72 }); |
68 }); | 73 }); |
69 | 74 |
70 test('prepareInstanceModel - reorder instances', () { | 75 test('prepareInstanceModel - reorder instances', () { |
71 var model = toObservable([0, 1, 2]); | 76 var model = toObservable([0, 1, 2]); |
72 | 77 |
73 var div = createTestHtml('<template repeat>{{}}</template>'); | 78 var div = createTestHtml('<template repeat>{{}}</template>'); |
74 var template = div.firstChild; | 79 var template = div.firstChild; |
75 var delegate = new TestInstanceModelSyntax(); | 80 var delegate = new TestInstanceModelSyntax(); |
76 | 81 |
77 recursivelySetTemplateModel(div, model, delegate); | 82 templateBind(template) |
| 83 ..model = model |
| 84 ..bindingDelegate = delegate; |
78 return new Future(() { | 85 return new Future(() { |
79 expect(delegate.prepareCount, 1); | 86 expect(delegate.prepareCount, 1); |
80 expect(delegate.callCount, 3); | 87 expect(delegate.callCount, 3); |
81 | 88 |
82 // Note: intentionally mutate in place. | 89 // Note: intentionally mutate in place. |
83 model.replaceRange(0, model.length, model.reversed.toList()); | 90 model.replaceRange(0, model.length, model.reversed.toList()); |
84 }).then(endOfMicrotask).then((_) { | 91 }).then(endOfMicrotask).then((_) { |
85 expect(delegate.prepareCount, 1); | 92 expect(delegate.prepareCount, 1); |
86 expect(delegate.callCount, 3); | 93 expect(delegate.callCount, 3); |
87 }); | 94 }); |
88 }); | 95 }); |
89 | 96 |
90 test('prepareInstancePositionChanged', () { | 97 test('prepareInstancePositionChanged', () { |
91 var model = toObservable(['a', 'b', 'c']); | 98 var model = toObservable(['a', 'b', 'c']); |
92 | 99 |
93 var div = createTestHtml('<template repeat>{{}}</template>'); | 100 var div = createTestHtml('<template repeat>{{}}</template>'); |
94 var delegate = new TestPositionChangedSyntax(); | 101 var delegate = new TestPositionChangedSyntax(); |
95 | 102 |
96 var template = div.nodes[0]; | 103 var template = div.nodes[0]; |
97 recursivelySetTemplateModel(div, model, delegate); | 104 templateBind(template) |
| 105 ..model = model |
| 106 ..bindingDelegate = delegate; |
98 return new Future(() { | 107 return new Future(() { |
99 | 108 |
100 expect(div.nodes.length, 4); | 109 expect(div.nodes.length, 4); |
101 expect(div.nodes[0].tagName, 'TEMPLATE'); | 110 expect(div.nodes[0].tagName, 'TEMPLATE'); |
102 expect(div.nodes[1].text, 'a'); | 111 expect(div.nodes[1].text, 'a'); |
103 expect(div.nodes[2].text, 'b'); | 112 expect(div.nodes[2].text, 'b'); |
104 expect(div.nodes[3].text, 'c'); | 113 expect(div.nodes[3].text, 'c'); |
105 | 114 |
106 expect(delegate.log, [ | 115 expect(delegate.log, [ |
107 ['prepare', template], | 116 ['prepare', template], |
(...skipping 21 matching lines...) Expand all Loading... |
129 '<template repeat>{{ \$index }} - {{ \$ident }}</template>'); | 138 '<template repeat>{{ \$index }} - {{ \$ident }}</template>'); |
130 var template = templateBind(div.firstChild) | 139 var template = templateBind(div.firstChild) |
131 ..bindingDelegate = new UpdateBindingDelegateA() | 140 ..bindingDelegate = new UpdateBindingDelegateA() |
132 ..model = model; | 141 ..model = model; |
133 | 142 |
134 return new Future(() { | 143 return new Future(() { |
135 expect(div.nodes.length, 3); | 144 expect(div.nodes.length, 3); |
136 expect(div.nodes[1].text, 'i:0 - a:1'); | 145 expect(div.nodes[1].text, 'i:0 - a:1'); |
137 expect(div.nodes[2].text, 'i:1 - a:2'); | 146 expect(div.nodes[2].text, 'i:1 - a:2'); |
138 | 147 |
139 template.bindingDelegate = new UpdateBindingDelegateB(); | 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 |
140 model.add(3); | 159 model.add(3); |
141 }).then(nextMicrotask).then((_) { | 160 }).then(nextMicrotask).then((_) { |
| 161 // All instances should reflect delegateB |
142 expect(4, div.nodes.length); | 162 expect(4, div.nodes.length); |
143 expect(div.nodes[1].text, 'i:0 - a:1'); | 163 expect(div.nodes[1].text, 'I:0 - A:1-narg'); |
144 expect(div.nodes[2].text, 'i:1 - a:2'); | 164 expect(div.nodes[2].text, 'I:2 - A:2-narg'); |
145 expect(div.nodes[3].text, 'I:4 - A:3-narg'); | 165 expect(div.nodes[3].text, 'I:4 - A:3-narg'); |
146 }); | 166 }); |
147 }); | 167 }); |
148 | 168 |
149 test('Basic', () { | 169 test('Basic', () { |
150 var model = fooModel(2, 4); | 170 var model = fooModel(2, 4); |
151 var div = createTestHtml( | 171 var div = createTestHtml( |
152 '<template bind>' | 172 '<template bind>' |
153 '{{ foo }} + {{ 2x: bar }} + {{ 4x: bar }}</template>'); | 173 '{{ foo }} + {{ 2x: bar }} + {{ 4x: bar }}</template>'); |
154 recursivelySetTemplateModel(div, model, new TimesTwoSyntax()); | 174 var template = templateBind(div.firstChild); |
| 175 template |
| 176 ..model = model |
| 177 ..bindingDelegate = new TimesTwoSyntax(); |
155 return new Future(() { | 178 return new Future(() { |
156 expect(div.nodes.length, 2); | 179 expect(div.nodes.length, 2); |
157 expect(div.nodes.last.text, '2 + 8 + '); | 180 expect(div.nodes.last.text, '2 + 8 + '); |
158 | 181 |
159 model.foo = 4; | 182 model.foo = 4; |
160 model.bar = 8; | 183 model.bar = 8; |
161 }).then(endOfMicrotask).then((_) { | 184 }).then(endOfMicrotask).then((_) { |
162 expect(div.nodes.last.text, '4 + 16 + '); | 185 expect(div.nodes.last.text, '4 + 16 + '); |
163 }); | 186 }); |
164 }); | 187 }); |
165 | 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 |
166 // Note: issue-141 test not included here as it's not related to the | 211 // Note: issue-141 test not included here as it's not related to the |
167 // BindingDelegate | 212 // BindingDelegate |
168 } | 213 } |
169 | 214 |
170 // TODO(jmesserly): mocks would be cleaner here. | 215 // TODO(jmesserly): mocks would be cleaner here. |
171 | 216 |
172 class TestBindingSyntax extends BindingDelegate { | 217 class TestBindingSyntax extends BindingDelegate { |
173 var log = []; | 218 var log = []; |
174 | 219 |
175 prepareBinding(String path, String name, Node node) { | 220 prepareBinding(String path, String name, Node node) { |
176 var tagName = node is Element ? node.tagName : 'TEXT'; | 221 var tagName = node is Element ? node.tagName : 'TEXT'; |
177 int id = log.length; | 222 int id = log.length; |
178 log.add(['prepare', path, name, tagName]); | 223 log.add(['prepare', path, name, tagName]); |
179 final outerNode = node; | 224 final outerNode = node; |
180 return (model, node, oneTime) { | 225 return (model, node, oneTime) { |
181 var tagName = node is Element ? node.tagName : 'TEXT'; | 226 var tagName = node is Element ? node.tagName : 'TEXT'; |
182 log.add(['bindFn', model, tagName, id]); | 227 log.add(['bindFn', model, tagName, id]); |
183 return oneTime ? new PropertyPath(path).getValueFrom(model) : | 228 return oneTime ? new PropertyPath(path).getValueFrom(model) : |
184 new PathObserver(model, path); | 229 new PathObserver(model, path); |
185 }; | 230 }; |
186 } | 231 } |
187 } | 232 } |
188 | 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 |
189 class TestModelSyntax extends BindingDelegate { | 242 class TestModelSyntax extends BindingDelegate { |
190 var log = []; | 243 var log = []; |
191 var altModels = new ListQueue(); | 244 var altModels = new ListQueue(); |
192 | 245 |
193 prepareInstanceModel(template) { | 246 prepareInstanceModel(template) { |
194 log.add(['prepare', template]); | 247 log.add(['prepare', template]); |
195 return (model) { | 248 return (model) { |
196 log.add(['bindFn', model]); | 249 log.add(['bindFn', model]); |
197 return altModels.removeFirst(); | 250 return altModels.removeFirst(); |
198 }; | 251 }; |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
263 | 316 |
264 prepareInstanceModel(template) => | 317 prepareInstanceModel(template) => |
265 (model) => toObservable({ 'id': '${model}-narg' }); | 318 (model) => toObservable({ 'id': '${model}-narg' }); |
266 | 319 |
267 | 320 |
268 prepareInstancePositionChanged(template) => (templateInstance, index) { | 321 prepareInstancePositionChanged(template) => (templateInstance, index) { |
269 templateInstance.model['index'] = 2 * index; | 322 templateInstance.model['index'] = 2 * index; |
270 }; | 323 }; |
271 } | 324 } |
272 | 325 |
OLD | NEW |