| 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:collection'; |
| 9 import 'dart:html'; | 9 import 'dart:html'; |
| 10 import 'package:mdv/mdv.dart' as mdv; | 10 import 'package:mdv/mdv.dart' as mdv; |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 45 div.innerHtml = s; | 45 div.innerHtml = s; |
| 46 testDiv.append(div); | 46 testDiv.append(div); |
| 47 | 47 |
| 48 for (var node in div.queryAll('*')) { | 48 for (var node in div.queryAll('*')) { |
| 49 if (node.isTemplate) TemplateElement.decorate(node); | 49 if (node.isTemplate) TemplateElement.decorate(node); |
| 50 } | 50 } |
| 51 | 51 |
| 52 return div; | 52 return div; |
| 53 } | 53 } |
| 54 | 54 |
| 55 recursivelySetTemplateModel(element, model) { | |
| 56 for (var node in element.queryAll('*')) { | |
| 57 if (node.isTemplate) node.model = model; | |
| 58 } | |
| 59 } | |
| 60 | |
| 61 observeTest('Registration', () { | 55 observeTest('Registration', () { |
| 62 var model = fooModel('bar'); | 56 var model = fooModel('bar'); |
| 63 | 57 |
| 64 var testSyntax = new TestBindingSyntax(); | 58 var testSyntax = new TestBindingSyntax(); |
| 65 TemplateElement.syntax['Test'] = testSyntax; | 59 var div = createTestHtml('<template bind>{{ foo }}' |
| 66 try { | 60 '<template bind>{{ foo }}</template></template>'); |
| 67 var div = createTestHtml( | 61 recursivelySetTemplateModel(div, model, testSyntax); |
| 68 '<template bind syntax="Test">{{ foo }}' + | 62 performMicrotaskCheckpoint(); |
| 69 '<template bind>{{ foo }}</template></template>'); | 63 expect(div.nodes.length, 4); |
| 70 recursivelySetTemplateModel(div, model); | 64 expect(div.nodes.last.text, 'bar'); |
| 71 performMicrotaskCheckpoint(); | 65 expect(div.nodes[2].tagName, 'TEMPLATE'); |
| 72 expect(div.nodes.length, 4); | |
| 73 expect(div.nodes.last.text, 'bar'); | |
| 74 expect(div.nodes[2].tagName, 'TEMPLATE'); | |
| 75 expect(div.nodes[2].attributes['syntax'], 'Test'); | |
| 76 | 66 |
| 77 expect(testSyntax.log, [ | 67 expect(testSyntax.log, [ |
| 78 [model, '', 'bind', 'TEMPLATE'], | 68 [model, '', 'bind', 'TEMPLATE'], |
| 79 [model, 'foo', 'text', null], | 69 [model, 'foo', 'text', null], |
| 80 [model, '', 'bind', 'TEMPLATE'], | 70 [model, '', 'bind', 'TEMPLATE'], |
| 81 [model, 'foo', 'text', null], | 71 [model, 'foo', 'text', null], |
| 82 ]); | 72 ]); |
| 83 } finally { | |
| 84 TemplateElement.syntax.remove('Test'); | |
| 85 } | |
| 86 }); | 73 }); |
| 87 | 74 |
| 88 observeTest('getInstanceModel', () { | 75 observeTest('getInstanceModel', () { |
| 89 var model = toObservable([fooModel(1), fooModel(2), fooModel(3)]); | 76 var model = toObservable([fooModel(1), fooModel(2), fooModel(3)]); |
| 90 | 77 |
| 91 var testSyntax = new TestModelSyntax(); | 78 var testSyntax = new TestModelSyntax(); |
| 92 testSyntax.altModels.addAll([fooModel('a'), fooModel('b'), fooModel('c')]); | 79 testSyntax.altModels.addAll([fooModel('a'), fooModel('b'), fooModel('c')]); |
| 93 | 80 |
| 94 TemplateElement.syntax['Test'] = testSyntax; | 81 var div = createTestHtml('<template repeat>{{ foo }}</template>'); |
| 95 try { | |
| 96 | 82 |
| 97 var div = createTestHtml( | 83 var template = div.nodes[0]; |
| 98 '<template repeat syntax="Test">' + | 84 recursivelySetTemplateModel(div, model, testSyntax); |
| 99 '{{ foo }}</template>'); | 85 performMicrotaskCheckpoint(); |
| 100 | 86 |
| 101 var template = div.nodes[0]; | 87 expect(div.nodes.length, 4); |
| 102 recursivelySetTemplateModel(div, model); | 88 expect(div.nodes[0].tagName, 'TEMPLATE'); |
| 103 performMicrotaskCheckpoint(); | 89 expect(div.nodes[1].text, 'a'); |
| 90 expect(div.nodes[2].text, 'b'); |
| 91 expect(div.nodes[3].text, 'c'); |
| 104 | 92 |
| 105 expect(div.nodes.length, 4); | 93 expect(testSyntax.log, [ |
| 106 expect(div.nodes[0].tagName, 'TEMPLATE'); | 94 [template, model[0]], |
| 107 expect(div.nodes[1].text, 'a'); | 95 [template, model[1]], |
| 108 expect(div.nodes[2].text, 'b'); | 96 [template, model[2]], |
| 109 expect(div.nodes[3].text, 'c'); | 97 ]); |
| 110 | |
| 111 expect(testSyntax.log, [ | |
| 112 [template, model[0]], | |
| 113 [template, model[1]], | |
| 114 [template, model[2]], | |
| 115 ]); | |
| 116 | |
| 117 } finally { | |
| 118 TemplateElement.syntax.remove('Test'); | |
| 119 } | |
| 120 }); | 98 }); |
| 121 | 99 |
| 122 observeTest('Basic', () { | 100 observeTest('Basic', () { |
| 123 var model = fooModel(2, 4); | 101 var model = fooModel(2, 4); |
| 124 | |
| 125 TemplateElement.syntax['2x'] = new TimesTwoSyntax(); | |
| 126 | |
| 127 var div = createTestHtml( | 102 var div = createTestHtml( |
| 128 '<template bind syntax="2x">' | 103 '<template bind syntax="2x">' |
| 129 '{{ foo }} + {{ 2x: bar }} + {{ 4x: bar }}</template>'); | 104 '{{ foo }} + {{ 2x: bar }} + {{ 4x: bar }}</template>'); |
| 130 recursivelySetTemplateModel(div, model); | 105 recursivelySetTemplateModel(div, model, new TimesTwoSyntax()); |
| 131 performMicrotaskCheckpoint(); | 106 performMicrotaskCheckpoint(); |
| 132 expect(div.nodes.length, 2); | 107 expect(div.nodes.length, 2); |
| 133 expect(div.nodes.last.text, '2 + 8 + '); | 108 expect(div.nodes.last.text, '2 + 8 + '); |
| 134 | 109 |
| 135 model.foo = 4; | 110 model.foo = 4; |
| 136 model.bar = 8; | 111 model.bar = 8; |
| 137 performMicrotaskCheckpoint(); | 112 performMicrotaskCheckpoint(); |
| 138 expect(div.nodes.last.text, '4 + 16 + '); | 113 expect(div.nodes.last.text, '4 + 16 + '); |
| 139 | |
| 140 TemplateElement.syntax.remove('2x'); | |
| 141 }); | |
| 142 | |
| 143 observeTest('Different Sub-Template Syntax', () { | |
| 144 var model = fooModel('bar'); | |
| 145 | |
| 146 TemplateElement.syntax['Test'] = new TestBindingSyntax(); | |
| 147 TemplateElement.syntax['Test2'] = new TestBindingSyntax(); | |
| 148 | |
| 149 var div = createTestHtml( | |
| 150 '<template bind syntax="Test">{{ foo }}' | |
| 151 '<template bind syntax="Test2">{{ foo }}</template></template>'); | |
| 152 recursivelySetTemplateModel(div, model); | |
| 153 performMicrotaskCheckpoint(); | |
| 154 expect(div.nodes.length, 4); | |
| 155 expect(div.nodes.last.text, 'bar'); | |
| 156 expect(div.nodes[2].tagName, 'TEMPLATE'); | |
| 157 expect(div.nodes[2].attributes['syntax'], 'Test2'); | |
| 158 | |
| 159 var testLog = TemplateElement.syntax['Test'].log; | |
| 160 var test2Log = TemplateElement.syntax['Test2'].log; | |
| 161 | |
| 162 expect(testLog, [ | |
| 163 [model, '', 'bind', 'TEMPLATE'], | |
| 164 [model, 'foo', 'text', null], | |
| 165 [model, '', 'bind', 'TEMPLATE'] | |
| 166 ]); | |
| 167 | |
| 168 expect(test2Log, [[model, 'foo', 'text', null]]); | |
| 169 | |
| 170 TemplateElement.syntax.remove('Test'); | |
| 171 TemplateElement.syntax.remove('Test2'); | |
| 172 }); | 114 }); |
| 173 } | 115 } |
| 174 | 116 |
| 175 // TODO(jmesserly): mocks would be cleaner here. | 117 // TODO(jmesserly): mocks would be cleaner here. |
| 176 | 118 |
| 177 class TestBindingSyntax extends CustomBindingSyntax { | 119 class TestBindingSyntax extends BindingDelegate { |
| 178 var log = []; | 120 var log = []; |
| 179 | 121 |
| 180 getBinding(model, String path, String name, Node node) { | 122 getBinding(model, String path, String name, Node node) { |
| 181 log.add([model, path, name, node is Element ? node.tagName : null]); | 123 log.add([model, path, name, node is Element ? node.tagName : null]); |
| 182 } | 124 } |
| 183 } | 125 } |
| 184 | 126 |
| 185 class TestModelSyntax extends CustomBindingSyntax { | 127 class TestModelSyntax extends BindingDelegate { |
| 186 var log = []; | 128 var log = []; |
| 187 var altModels = new ListQueue(); | 129 var altModels = new ListQueue(); |
| 188 | 130 |
| 189 getInstanceModel(template, model) { | 131 getInstanceModel(template, model) { |
| 190 log.add([template, model]); | 132 log.add([template, model]); |
| 191 return altModels.removeFirst(); | 133 return altModels.removeFirst(); |
| 192 } | 134 } |
| 193 } | 135 } |
| 194 | 136 |
| 195 // Note: this isn't a very smart whitespace handler. A smarter one would only | 137 // Note: this isn't a very smart whitespace handler. A smarter one would only |
| 196 // trim indentation, not all whitespace. | 138 // trim indentation, not all whitespace. |
| 197 // See "trimOrCompact" in the web_ui Pub package. | 139 // See "trimOrCompact" in the web_ui Pub package. |
| 198 class WhitespaceRemover extends CustomBindingSyntax { | 140 class WhitespaceRemover extends BindingDelegate { |
| 199 int trimmed = 0; | 141 int trimmed = 0; |
| 200 int removed = 0; | 142 int removed = 0; |
| 201 | 143 |
| 202 DocumentFragment getInstanceFragment(Element template) { | 144 DocumentFragment getInstanceFragment(Element template) { |
| 203 var instance = template.createInstance(); | 145 var instance = template.createInstance(); |
| 204 var walker = new TreeWalker(instance, NodeFilter.SHOW_TEXT); | 146 var walker = new TreeWalker(instance, NodeFilter.SHOW_TEXT); |
| 205 | 147 |
| 206 var toRemove = []; | 148 var toRemove = []; |
| 207 while (walker.nextNode() != null) { | 149 while (walker.nextNode() != null) { |
| 208 var node = walker.currentNode; | 150 var node = walker.currentNode; |
| 209 var text = node.text.replaceAll('\n', '').trim(); | 151 var text = node.text.replaceAll('\n', '').trim(); |
| 210 if (text.length != node.text.length) { | 152 if (text.length != node.text.length) { |
| 211 if (text.length == 0) { | 153 if (text.length == 0) { |
| 212 toRemove.add(node); | 154 toRemove.add(node); |
| 213 } else { | 155 } else { |
| 214 trimmed++; | 156 trimmed++; |
| 215 node.text = text; | 157 node.text = text; |
| 216 } | 158 } |
| 217 } | 159 } |
| 218 } | 160 } |
| 219 | 161 |
| 220 for (var node in toRemove) node.remove(); | 162 for (var node in toRemove) node.remove(); |
| 221 removed += toRemove.length; | 163 removed += toRemove.length; |
| 222 | 164 |
| 223 return instance; | 165 return instance; |
| 224 } | 166 } |
| 225 } | 167 } |
| 226 | 168 |
| 227 class TimesTwoSyntax extends CustomBindingSyntax { | 169 class TimesTwoSyntax extends BindingDelegate { |
| 228 getBinding(model, path, name, node) { | 170 getBinding(model, path, name, node) { |
| 229 path = path.trim(); | 171 path = path.trim(); |
| 230 if (!path.startsWith('2x:')) return null; | 172 if (!path.startsWith('2x:')) return null; |
| 231 | 173 |
| 232 path = path.substring(3); | 174 path = path.substring(3); |
| 233 return new CompoundBinding((values) => values['value'] * 2) | 175 return new CompoundBinding((values) => values['value'] * 2) |
| 234 ..bind('value', model, path); | 176 ..bind('value', model, path); |
| 235 } | 177 } |
| 236 } | 178 } |
| OLD | NEW |