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:collection'; | 8 import 'dart:collection'; |
8 import 'dart:html'; | 9 import 'dart:html'; |
9 import 'package:template_binding/template_binding.dart'; | 10 import 'package:template_binding/template_binding.dart'; |
10 import 'package:observe/observe.dart'; | 11 import 'package:observe/observe.dart'; |
11 import 'package:unittest/html_config.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 observeTest('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 recursivelySetTemplateModel(div, model, testSyntax); |
26 performMicrotaskCheckpoint(); | 26 return new Future(() { |
27 expect(div.nodes.length, 4); | 27 expect(div.nodes.length, 4); |
28 expect(div.nodes.last.text, 'bar'); | 28 expect(div.nodes.last.text, 'bar'); |
29 expect(div.nodes[2].tagName, 'TEMPLATE'); | 29 expect(div.nodes[2].tagName, 'TEMPLATE'); |
30 expect(testSyntax.log, [ | 30 expect(testSyntax.log, [ |
31 ['prepare', '', 'bind', 'TEMPLATE'], | 31 ['prepare', '', 'bind', 'TEMPLATE'], |
32 ['bindFn', model, 'TEMPLATE', 0], | 32 ['bindFn', model, 'TEMPLATE', 0], |
33 ['prepare', 'foo', 'text', 'TEXT'], | 33 ['prepare', 'foo', 'text', 'TEXT'], |
34 ['prepare', '', 'bind', 'TEMPLATE'], | 34 ['prepare', '', 'bind', 'TEMPLATE'], |
35 ['bindFn', model, 'TEXT', 2], | 35 ['bindFn', model, 'TEXT', 2], |
36 ['bindFn', model, 'TEMPLATE', 3], | 36 ['bindFn', model, 'TEMPLATE', 3], |
37 ['prepare', 'foo', 'text', 'TEXT'], | 37 ['prepare', 'foo', 'text', 'TEXT'], |
38 ['bindFn', model, 'TEXT', 6], | 38 ['bindFn', model, 'TEXT', 6], |
39 ]); | 39 ]); |
| 40 }); |
40 }); | 41 }); |
41 | 42 |
42 observeTest('prepareInstanceModel', () { | 43 test('prepareInstanceModel', () { |
43 var model = toObservable([fooModel(1), fooModel(2), fooModel(3)]); | 44 var model = toObservable([fooModel(1), fooModel(2), fooModel(3)]); |
44 | 45 |
45 var testSyntax = new TestModelSyntax(); | 46 var testSyntax = new TestModelSyntax(); |
46 testSyntax.altModels.addAll([fooModel('a'), fooModel('b'), fooModel('c')]); | 47 testSyntax.altModels.addAll([fooModel('a'), fooModel('b'), fooModel('c')]); |
47 | 48 |
48 var div = createTestHtml('<template repeat>{{ foo }}</template>'); | 49 var div = createTestHtml('<template repeat>{{ foo }}</template>'); |
49 | 50 |
50 var template = div.nodes[0]; | 51 var template = div.nodes[0]; |
51 recursivelySetTemplateModel(div, model, testSyntax); | 52 recursivelySetTemplateModel(div, model, testSyntax); |
52 performMicrotaskCheckpoint(); | 53 return new Future(() { |
53 | 54 |
54 expect(div.nodes.length, 4); | 55 expect(div.nodes.length, 4); |
55 expect(div.nodes[0].tagName, 'TEMPLATE'); | 56 expect(div.nodes[0].tagName, 'TEMPLATE'); |
56 expect(div.nodes[1].text, 'a'); | 57 expect(div.nodes[1].text, 'a'); |
57 expect(div.nodes[2].text, 'b'); | 58 expect(div.nodes[2].text, 'b'); |
58 expect(div.nodes[3].text, 'c'); | 59 expect(div.nodes[3].text, 'c'); |
59 | 60 |
60 expect(testSyntax.log, [ | 61 expect(testSyntax.log, [ |
61 ['prepare', template], | 62 ['prepare', template], |
62 ['bindFn', model[0]], | 63 ['bindFn', model[0]], |
63 ['bindFn', model[1]], | 64 ['bindFn', model[1]], |
64 ['bindFn', model[2]], | 65 ['bindFn', model[2]], |
65 ]); | 66 ]); |
| 67 }); |
66 }); | 68 }); |
67 | 69 |
68 observeTest('prepareInstanceModel - reorder instances', () { | 70 test('prepareInstanceModel - reorder instances', () { |
69 var model = toObservable([0, 1, 2]); | 71 var model = toObservable([0, 1, 2]); |
70 | 72 |
71 var div = createTestHtml('<template repeat>{{}}</template>'); | 73 var div = createTestHtml('<template repeat>{{}}</template>'); |
72 var template = div.firstChild; | 74 var template = div.firstChild; |
73 var delegate = new TestInstanceModelSyntax(); | 75 var delegate = new TestInstanceModelSyntax(); |
74 | 76 |
75 recursivelySetTemplateModel(div, model, delegate); | 77 recursivelySetTemplateModel(div, model, delegate); |
76 performMicrotaskCheckpoint(); | 78 return new Future(() { |
77 expect(delegate.prepareCount, 1); | 79 expect(delegate.prepareCount, 1); |
78 expect(delegate.callCount, 3); | 80 expect(delegate.callCount, 3); |
79 | 81 |
80 // Note: intentionally mutate in place. | 82 // Note: intentionally mutate in place. |
81 model.replaceRange(0, model.length, model.reversed.toList()); | 83 model.replaceRange(0, model.length, model.reversed.toList()); |
82 performMicrotaskCheckpoint(); | 84 }).then(endOfMicrotask).then((_) { |
83 expect(delegate.prepareCount, 1); | 85 expect(delegate.prepareCount, 1); |
84 expect(delegate.callCount, 3); | 86 expect(delegate.callCount, 3); |
| 87 }); |
85 }); | 88 }); |
86 | 89 |
87 observeTest('prepareInstancePositionChanged', () { | 90 test('prepareInstancePositionChanged', () { |
88 var model = toObservable(['a', 'b', 'c']); | 91 var model = toObservable(['a', 'b', 'c']); |
89 | 92 |
90 var div = createTestHtml('<template repeat>{{}}</template>'); | 93 var div = createTestHtml('<template repeat>{{}}</template>'); |
91 var delegate = new TestPositionChangedSyntax(); | 94 var delegate = new TestPositionChangedSyntax(); |
92 | 95 |
93 var template = div.nodes[0]; | 96 var template = div.nodes[0]; |
94 recursivelySetTemplateModel(div, model, delegate); | 97 recursivelySetTemplateModel(div, model, delegate); |
95 performMicrotaskCheckpoint(); | 98 return new Future(() { |
96 | 99 |
97 expect(div.nodes.length, 4); | 100 expect(div.nodes.length, 4); |
98 expect(div.nodes[0].tagName, 'TEMPLATE'); | 101 expect(div.nodes[0].tagName, 'TEMPLATE'); |
99 expect(div.nodes[1].text, 'a'); | 102 expect(div.nodes[1].text, 'a'); |
100 expect(div.nodes[2].text, 'b'); | 103 expect(div.nodes[2].text, 'b'); |
101 expect(div.nodes[3].text, 'c'); | 104 expect(div.nodes[3].text, 'c'); |
102 | 105 |
103 expect(delegate.log, [ | 106 expect(delegate.log, [ |
104 ['prepare', template], | 107 ['prepare', template], |
105 ['bindFn', model[0], 0], | 108 ['bindFn', model[0], 0], |
106 ['bindFn', model[1], 1], | 109 ['bindFn', model[1], 1], |
107 ['bindFn', model[2], 2], | 110 ['bindFn', model[2], 2], |
108 ]); | 111 ]); |
109 | 112 |
110 delegate.log.clear(); | 113 delegate.log.clear(); |
111 | 114 |
112 model.removeAt(1); | 115 model.removeAt(1); |
113 performMicrotaskCheckpoint(); | 116 }).then(endOfMicrotask).then((_) { |
114 | 117 |
115 expect(delegate.log, [['bindFn', 'c', 1]], reason: 'removed item'); | 118 expect(delegate.log, [['bindFn', 'c', 1]], reason: 'removed item'); |
116 | 119 |
117 expect(div.nodes.skip(1).map((n) => n.text), ['a', 'c']); | 120 expect(div.nodes.skip(1).map((n) => n.text), ['a', 'c']); |
| 121 }); |
118 }); | 122 }); |
119 | 123 |
120 observeTest('Basic', () { | 124 |
| 125 test('Update bindingDelegate with active template', () { |
| 126 var model = toObservable([1, 2]); |
| 127 |
| 128 var div = createTestHtml( |
| 129 '<template repeat>{{ \$index }} - {{ \$ident }}</template>'); |
| 130 var template = templateBind(div.firstChild) |
| 131 ..bindingDelegate = new UpdateBindingDelegateA() |
| 132 ..model = model; |
| 133 |
| 134 return new Future(() { |
| 135 expect(div.nodes.length, 3); |
| 136 expect(div.nodes[1].text, 'i:0 - a:1'); |
| 137 expect(div.nodes[2].text, 'i:1 - a:2'); |
| 138 |
| 139 template.bindingDelegate = new UpdateBindingDelegateB(); |
| 140 model.add(3); |
| 141 }).then(nextMicrotask).then((_) { |
| 142 expect(4, div.nodes.length); |
| 143 expect(div.nodes[1].text, 'i:0 - a:1'); |
| 144 expect(div.nodes[2].text, 'i:1 - a:2'); |
| 145 expect(div.nodes[3].text, 'I:4 - A:3-narg'); |
| 146 }); |
| 147 }); |
| 148 |
| 149 test('Basic', () { |
121 var model = fooModel(2, 4); | 150 var model = fooModel(2, 4); |
122 var div = createTestHtml( | 151 var div = createTestHtml( |
123 '<template bind>' | 152 '<template bind>' |
124 '{{ foo }} + {{ 2x: bar }} + {{ 4x: bar }}</template>'); | 153 '{{ foo }} + {{ 2x: bar }} + {{ 4x: bar }}</template>'); |
125 recursivelySetTemplateModel(div, model, new TimesTwoSyntax()); | 154 recursivelySetTemplateModel(div, model, new TimesTwoSyntax()); |
126 performMicrotaskCheckpoint(); | 155 return new Future(() { |
127 expect(div.nodes.length, 2); | 156 expect(div.nodes.length, 2); |
128 expect(div.nodes.last.text, '2 + 8 + '); | 157 expect(div.nodes.last.text, '2 + 8 + '); |
129 | 158 |
130 model.foo = 4; | 159 model.foo = 4; |
131 model.bar = 8; | 160 model.bar = 8; |
132 performMicrotaskCheckpoint(); | 161 }).then(endOfMicrotask).then((_) { |
133 expect(div.nodes.last.text, '4 + 16 + '); | 162 expect(div.nodes.last.text, '4 + 16 + '); |
| 163 }); |
134 }); | 164 }); |
135 | 165 |
136 // Note: issue-141 test not included here as it's not related to the | 166 // Note: issue-141 test not included here as it's not related to the |
137 // BindingDelegate | 167 // BindingDelegate |
138 } | 168 } |
139 | 169 |
140 // TODO(jmesserly): mocks would be cleaner here. | 170 // TODO(jmesserly): mocks would be cleaner here. |
141 | 171 |
142 class TestBindingSyntax extends BindingDelegate { | 172 class TestBindingSyntax extends BindingDelegate { |
143 var log = []; | 173 var log = []; |
144 | 174 |
145 prepareBinding(String path, String name, Node node) { | 175 prepareBinding(String path, String name, Node node) { |
146 var tagName = node is Element ? node.tagName : 'TEXT'; | 176 var tagName = node is Element ? node.tagName : 'TEXT'; |
147 int id = log.length; | 177 int id = log.length; |
148 log.add(['prepare', path, name, tagName]); | 178 log.add(['prepare', path, name, tagName]); |
149 final outerNode = node; | 179 final outerNode = node; |
150 return (model, node) { | 180 return (model, node, oneTime) { |
151 var tagName = node is Element ? node.tagName : 'TEXT'; | 181 var tagName = node is Element ? node.tagName : 'TEXT'; |
152 log.add(['bindFn', model, tagName, id]); | 182 log.add(['bindFn', model, tagName, id]); |
| 183 return oneTime ? new PropertyPath(path).getValueFrom(model) : |
| 184 new PathObserver(model, path); |
153 }; | 185 }; |
154 } | 186 } |
155 } | 187 } |
156 | 188 |
157 class TestModelSyntax extends BindingDelegate { | 189 class TestModelSyntax extends BindingDelegate { |
158 var log = []; | 190 var log = []; |
159 var altModels = new ListQueue(); | 191 var altModels = new ListQueue(); |
160 | 192 |
161 prepareInstanceModel(template) { | 193 prepareInstanceModel(template) { |
162 log.add(['prepare', template]); | 194 log.add(['prepare', template]); |
(...skipping 29 matching lines...) Expand all Loading... |
192 } | 224 } |
193 } | 225 } |
194 | 226 |
195 | 227 |
196 class TimesTwoSyntax extends BindingDelegate { | 228 class TimesTwoSyntax extends BindingDelegate { |
197 prepareBinding(path, name, node) { | 229 prepareBinding(path, name, node) { |
198 path = path.trim(); | 230 path = path.trim(); |
199 if (!path.startsWith('2x:')) return null; | 231 if (!path.startsWith('2x:')) return null; |
200 | 232 |
201 path = path.substring(3); | 233 path = path.substring(3); |
202 return (model, _) { | 234 return (model, _, oneTime) { |
203 return new PathObserver(model, path, computeValue: (x) => 2 * x); | 235 return new ObserverTransform(new PathObserver(model, path), (x) => 2 * x); |
204 }; | 236 }; |
205 } | 237 } |
206 } | 238 } |
| 239 |
| 240 class UpdateBindingDelegateBase extends BindingDelegate { |
| 241 bindingHandler(prefix, path) => (model, _, oneTime) => |
| 242 new ObserverTransform(new PathObserver(model, path), (x) => '$prefix:$x'); |
| 243 } |
| 244 |
| 245 class UpdateBindingDelegateA extends UpdateBindingDelegateBase { |
| 246 prepareBinding(path, name, node) { |
| 247 if (path == '\$ident') return bindingHandler('a', 'id'); |
| 248 if (path == '\$index') return bindingHandler('i', 'index'); |
| 249 } |
| 250 |
| 251 prepareInstanceModel(template) => (model) => toObservable({ 'id': model }); |
| 252 |
| 253 prepareInstancePositionChanged(template) => (templateInstance, index) { |
| 254 templateInstance.model['index'] = index; |
| 255 }; |
| 256 } |
| 257 |
| 258 class UpdateBindingDelegateB extends UpdateBindingDelegateBase { |
| 259 prepareBinding(path, name, node) { |
| 260 if (path == '\$ident') return bindingHandler('A', 'id'); |
| 261 if (path == '\$index') return bindingHandler('I', 'index'); |
| 262 } |
| 263 |
| 264 prepareInstanceModel(template) => |
| 265 (model) => toObservable({ 'id': '${model}-narg' }); |
| 266 |
| 267 |
| 268 prepareInstancePositionChanged(template) => (templateInstance, index) { |
| 269 templateInstance.model['index'] = 2 * index; |
| 270 }; |
| 271 } |
| 272 |
OLD | NEW |