| 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 binding_syntax_test; | 5 library binding_syntax_test; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; |
| 8 import 'dart:html'; | 9 import 'dart:html'; |
| 9 import 'package:mdv_observe/mdv_observe.dart'; | 10 import 'package:mdv_observe/mdv_observe.dart'; |
| 10 import 'package:unittest/html_config.dart'; | 11 import 'package:unittest/html_config.dart'; |
| 11 import 'package:unittest/unittest.dart'; | 12 import 'package:unittest/unittest.dart'; |
| 12 import 'mdv_observe_utils.dart'; | 13 import 'mdv_observe_utils.dart'; |
| 13 | 14 |
| 14 // Note: this file ported from | 15 // Note: this file ported from |
| 15 // https://github.com/toolkitchen/mdv/blob/master/tests/syntax.js | 16 // https://github.com/toolkitchen/mdv/blob/master/tests/syntax.js |
| 16 | 17 |
| 17 main() { | 18 main() { |
| (...skipping 29 matching lines...) Expand all Loading... |
| 47 for (var node in element.queryAll('*')) { | 48 for (var node in element.queryAll('*')) { |
| 48 if (node.isTemplate) node.model = model; | 49 if (node.isTemplate) node.model = model; |
| 49 } | 50 } |
| 50 } | 51 } |
| 51 | 52 |
| 52 test('Registration', () { | 53 test('Registration', () { |
| 53 var model = toSymbolMap({'foo': 'bar'}); | 54 var model = toSymbolMap({'foo': 'bar'}); |
| 54 | 55 |
| 55 var testSyntax = new TestBindingSyntax(); | 56 var testSyntax = new TestBindingSyntax(); |
| 56 TemplateElement.syntax['Test'] = testSyntax; | 57 TemplateElement.syntax['Test'] = testSyntax; |
| 58 try { |
| 59 var div = createTestHtml( |
| 60 '<template bind syntax="Test">{{ foo }}' + |
| 61 '<template bind>{{ foo }}</template></template>'); |
| 62 recursivelySetTemplateModel(div, model); |
| 63 deliverChangeRecords(); |
| 64 expect(div.nodes.length, 4); |
| 65 expect(div.nodes.last.text, 'bar'); |
| 66 expect(div.nodes[2].tagName, 'TEMPLATE'); |
| 67 expect(div.nodes[2].attributes['syntax'], 'Test'); |
| 57 | 68 |
| 58 var div = createTestHtml( | 69 expect(testSyntax.log, [ |
| 59 '<template bind syntax="Test">{{ foo }}' + | 70 [model, '', 'bind', 'TEMPLATE'], |
| 60 '<template bind>{{ foo }}</template></template>'); | 71 [model, 'foo', 'text', null], |
| 61 recursivelySetTemplateModel(div, model); | 72 [model, '', 'bind', 'TEMPLATE'], |
| 62 deliverChangeRecords(); | 73 [model, 'foo', 'text', null], |
| 63 expect(div.nodes.length, 4); | 74 ]); |
| 64 expect(div.nodes.last.text, 'bar'); | 75 } finally { |
| 65 expect(div.nodes[2].tagName, 'TEMPLATE'); | 76 TemplateElement.syntax.remove('Test'); |
| 66 expect(div.nodes[2].attributes['syntax'], 'Test'); | 77 } |
| 78 }); |
| 67 | 79 |
| 68 expect(testSyntax.log, [ | 80 test('getInstanceModel', () { |
| 69 [model, 'foo', 'text', null], | 81 var model = toObservable([{'foo': 1}, {'foo': 2}, {'foo': 3}] |
| 70 [model, '', 'bind', 'TEMPLATE'], | 82 .map(toSymbolMap)); |
| 71 [model, 'foo', 'text', null], | |
| 72 ]); | |
| 73 | 83 |
| 74 TemplateElement.syntax.remove('Test'); | 84 var testSyntax = new TestModelSyntax(); |
| 85 testSyntax.altModels.addAll([{'foo': 'a'}, {'foo': 'b'}, {'foo': 'c'}] |
| 86 .map(toSymbolMap)); |
| 87 |
| 88 TemplateElement.syntax['Test'] = testSyntax; |
| 89 try { |
| 90 |
| 91 var div = createTestHtml( |
| 92 '<template repeat syntax="Test">' + |
| 93 '{{ foo }}</template>'); |
| 94 |
| 95 var template = div.nodes[0]; |
| 96 recursivelySetTemplateModel(div, model); |
| 97 deliverChangeRecords(); |
| 98 |
| 99 expect(div.nodes.length, 4); |
| 100 expect(div.nodes[0].tagName, 'TEMPLATE'); |
| 101 expect(div.nodes[1].text, 'a'); |
| 102 expect(div.nodes[2].text, 'b'); |
| 103 expect(div.nodes[3].text, 'c'); |
| 104 |
| 105 expect(testSyntax.log, [ |
| 106 [template, model[0]], |
| 107 [template, model[1]], |
| 108 [template, model[2]], |
| 109 ]); |
| 110 |
| 111 } finally { |
| 112 TemplateElement.syntax.remove('Test'); |
| 113 } |
| 114 }); |
| 115 |
| 116 // Note: this test was original, not a port of an existing test. |
| 117 test('getInstanceFragment', () { |
| 118 var model = toSymbolMap({'foo': 'bar'}); |
| 119 |
| 120 var testSyntax = new WhitespaceRemover(); |
| 121 TemplateElement.syntax['Test'] = testSyntax; |
| 122 try { |
| 123 var div = createTestHtml( |
| 124 '''<template bind syntax="Test"> |
| 125 {{ foo }} |
| 126 <template bind> |
| 127 {{ foo }} |
| 128 </template> |
| 129 </template>'''); |
| 130 |
| 131 recursivelySetTemplateModel(div, model); |
| 132 deliverChangeRecords(); |
| 133 |
| 134 expect(testSyntax.trimmed, 2); |
| 135 expect(testSyntax.removed, 1); |
| 136 |
| 137 expect(div.nodes.length, 4); |
| 138 expect(div.nodes.last.text, 'bar'); |
| 139 expect(div.nodes[2].tagName, 'TEMPLATE'); |
| 140 expect(div.nodes[2].attributes['syntax'], 'Test'); |
| 141 |
| 142 } finally { |
| 143 TemplateElement.syntax.remove('Test'); |
| 144 } |
| 75 }); | 145 }); |
| 76 | 146 |
| 77 test('Basic', () { | 147 test('Basic', () { |
| 78 var model = toSymbolMap({'foo': 2, 'bar': 4}); | 148 var model = toSymbolMap({'foo': 2, 'bar': 4}); |
| 79 | 149 |
| 80 TemplateElement.syntax['2x'] = new TimesTwoSyntax(); | 150 TemplateElement.syntax['2x'] = new TimesTwoSyntax(); |
| 81 | 151 |
| 82 var div = createTestHtml( | 152 var div = createTestHtml( |
| 83 '<template bind syntax="2x">' | 153 '<template bind syntax="2x">' |
| 84 '{{ foo }} + {{ 2x: bar }} + {{ 4x: bar }}</template>'); | 154 '{{ foo }} + {{ 2x: bar }} + {{ 4x: bar }}</template>'); |
| (...skipping 23 matching lines...) Expand all Loading... |
| 108 deliverChangeRecords(); | 178 deliverChangeRecords(); |
| 109 expect(div.nodes.length, 4); | 179 expect(div.nodes.length, 4); |
| 110 expect(div.nodes.last.text, 'bar'); | 180 expect(div.nodes.last.text, 'bar'); |
| 111 expect(div.nodes[2].tagName, 'TEMPLATE'); | 181 expect(div.nodes[2].tagName, 'TEMPLATE'); |
| 112 expect(div.nodes[2].attributes['syntax'], 'Test2'); | 182 expect(div.nodes[2].attributes['syntax'], 'Test2'); |
| 113 | 183 |
| 114 var testLog = TemplateElement.syntax['Test'].log; | 184 var testLog = TemplateElement.syntax['Test'].log; |
| 115 var test2Log = TemplateElement.syntax['Test2'].log; | 185 var test2Log = TemplateElement.syntax['Test2'].log; |
| 116 | 186 |
| 117 expect(testLog, [ | 187 expect(testLog, [ |
| 188 [model, '', 'bind', 'TEMPLATE'], |
| 118 [model, 'foo', 'text', null], | 189 [model, 'foo', 'text', null], |
| 119 [model, '', 'bind', 'TEMPLATE'] | 190 [model, '', 'bind', 'TEMPLATE'] |
| 120 ]); | 191 ]); |
| 121 | 192 |
| 122 expect(test2Log, [[model, 'foo', 'text', null]]); | 193 expect(test2Log, [[model, 'foo', 'text', null]]); |
| 123 | 194 |
| 124 TemplateElement.syntax.remove('Test'); | 195 TemplateElement.syntax.remove('Test'); |
| 125 TemplateElement.syntax.remove('Test2'); | 196 TemplateElement.syntax.remove('Test2'); |
| 126 }); | 197 }); |
| 127 } | 198 } |
| 128 | 199 |
| 200 // TODO(jmesserly): mocks would be cleaner here. |
| 201 |
| 129 class TestBindingSyntax extends CustomBindingSyntax { | 202 class TestBindingSyntax extends CustomBindingSyntax { |
| 130 var log = []; | 203 var log = []; |
| 131 | 204 |
| 132 getBinding(model, String path, String name, Node node) { | 205 getBinding(model, String path, String name, Node node) { |
| 133 log.add([model, path, name, node is Element ? node.tagName : null]); | 206 log.add([model, path, name, node is Element ? node.tagName : null]); |
| 134 } | 207 } |
| 135 } | 208 } |
| 136 | 209 |
| 210 class TestModelSyntax extends CustomBindingSyntax { |
| 211 var log = []; |
| 212 var altModels = new ListQueue(); |
| 213 |
| 214 getInstanceModel(template, model) { |
| 215 log.add([template, model]); |
| 216 return altModels.removeFirst(); |
| 217 } |
| 218 } |
| 219 |
| 220 // Note: this isn't a very smart whitespace handler. A smarter one would only |
| 221 // trim indentation, not all whitespace. |
| 222 // See "trimOrCompact" in the web_ui Pub package. |
| 223 class WhitespaceRemover extends CustomBindingSyntax { |
| 224 int trimmed = 0; |
| 225 int removed = 0; |
| 226 |
| 227 DocumentFragment getInstanceFragment(Element template) { |
| 228 var instance = template.createInstance(); |
| 229 var walker = new TreeWalker(instance, NodeFilter.SHOW_TEXT); |
| 230 |
| 231 var toRemove = []; |
| 232 while (walker.nextNode() != null) { |
| 233 var node = walker.currentNode; |
| 234 var text = node.text.replaceAll('\n', '').trim(); |
| 235 if (text.length != node.text.length) { |
| 236 if (text.length == 0) { |
| 237 toRemove.add(node); |
| 238 } else { |
| 239 trimmed++; |
| 240 node.text = text; |
| 241 } |
| 242 } |
| 243 } |
| 244 |
| 245 for (var node in toRemove) node.remove(); |
| 246 removed += toRemove.length; |
| 247 |
| 248 return instance; |
| 249 } |
| 250 } |
| 251 |
| 252 |
| 137 class TimesTwoSyntax extends CustomBindingSyntax { | 253 class TimesTwoSyntax extends CustomBindingSyntax { |
| 138 getBinding(model, path, name, node) { | 254 getBinding(model, path, name, node) { |
| 139 path = path.trim(); | 255 path = path.trim(); |
| 140 if (!path.startsWith('2x:')) return null; | 256 if (!path.startsWith('2x:')) return null; |
| 141 | 257 |
| 142 path = path.substring(3); | 258 path = path.substring(3); |
| 143 return new CompoundBinding((values) => values['value'] * 2) | 259 return new CompoundBinding((values) => values['value'] * 2) |
| 144 ..bind('value', model, path); | 260 ..bind('value', model, path); |
| 145 } | 261 } |
| 146 } | 262 } |
| OLD | NEW |