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 |