| 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 |