| 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 binding_syntax_test; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:collection'; | |
| 9 import 'dart:html'; | |
| 10 import 'package:mdv_observe/mdv_observe.dart'; | |
| 11 import 'package:unittest/html_config.dart'; | |
| 12 import 'package:unittest/unittest.dart'; | |
| 13 import 'mdv_observe_utils.dart'; | |
| 14 | |
| 15 // Note: this file ported from | |
| 16 // https://github.com/toolkitchen/mdv/blob/master/tests/syntax.js | |
| 17 | |
| 18 main() { | |
| 19 useHtmlConfiguration(); | |
| 20 group('Syntax', syntaxTests); | |
| 21 } | |
| 22 | |
| 23 syntaxTests() { | |
| 24 var testDiv; | |
| 25 | |
| 26 setUp(() { | |
| 27 document.body.append(testDiv = new DivElement()); | |
| 28 }); | |
| 29 | |
| 30 tearDown(() { | |
| 31 testDiv.remove(); | |
| 32 testDiv = null; | |
| 33 }); | |
| 34 | |
| 35 createTestHtml(s) { | |
| 36 var div = new DivElement(); | |
| 37 div.innerHtml = s; | |
| 38 testDiv.append(div); | |
| 39 | |
| 40 for (var node in div.queryAll('*')) { | |
| 41 if (node.isTemplate) TemplateElement.decorate(node); | |
| 42 } | |
| 43 | |
| 44 return div; | |
| 45 } | |
| 46 | |
| 47 recursivelySetTemplateModel(element, model) { | |
| 48 for (var node in element.queryAll('*')) { | |
| 49 if (node.isTemplate) node.model = model; | |
| 50 } | |
| 51 } | |
| 52 | |
| 53 test('Registration', () { | |
| 54 var model = toSymbolMap({'foo': 'bar'}); | |
| 55 | |
| 56 var testSyntax = new TestBindingSyntax(); | |
| 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'); | |
| 68 | |
| 69 expect(testSyntax.log, [ | |
| 70 [model, '', 'bind', 'TEMPLATE'], | |
| 71 [model, 'foo', 'text', null], | |
| 72 [model, '', 'bind', 'TEMPLATE'], | |
| 73 [model, 'foo', 'text', null], | |
| 74 ]); | |
| 75 } finally { | |
| 76 TemplateElement.syntax.remove('Test'); | |
| 77 } | |
| 78 }); | |
| 79 | |
| 80 test('getInstanceModel', () { | |
| 81 var model = toObservable([{'foo': 1}, {'foo': 2}, {'foo': 3}] | |
| 82 .map(toSymbolMap)); | |
| 83 | |
| 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 } | |
| 145 }); | |
| 146 | |
| 147 test('Basic', () { | |
| 148 var model = toSymbolMap({'foo': 2, 'bar': 4}); | |
| 149 | |
| 150 TemplateElement.syntax['2x'] = new TimesTwoSyntax(); | |
| 151 | |
| 152 var div = createTestHtml( | |
| 153 '<template bind syntax="2x">' | |
| 154 '{{ foo }} + {{ 2x: bar }} + {{ 4x: bar }}</template>'); | |
| 155 recursivelySetTemplateModel(div, model); | |
| 156 deliverChangeRecords(); | |
| 157 expect(div.nodes.length, 2); | |
| 158 expect(div.nodes.last.text, '2 + 8 + '); | |
| 159 | |
| 160 model[const Symbol('foo')] = 4; | |
| 161 model[const Symbol('bar')] = 8; | |
| 162 deliverChangeRecords(); | |
| 163 expect(div.nodes.last.text, '4 + 16 + '); | |
| 164 | |
| 165 TemplateElement.syntax.remove('2x'); | |
| 166 }); | |
| 167 | |
| 168 test('Different Sub-Template Syntax', () { | |
| 169 var model = toSymbolMap({'foo': 'bar'}); | |
| 170 | |
| 171 TemplateElement.syntax['Test'] = new TestBindingSyntax(); | |
| 172 TemplateElement.syntax['Test2'] = new TestBindingSyntax(); | |
| 173 | |
| 174 var div = createTestHtml( | |
| 175 '<template bind syntax="Test">{{ foo }}' | |
| 176 '<template bind syntax="Test2">{{ foo }}</template></template>'); | |
| 177 recursivelySetTemplateModel(div, model); | |
| 178 deliverChangeRecords(); | |
| 179 expect(div.nodes.length, 4); | |
| 180 expect(div.nodes.last.text, 'bar'); | |
| 181 expect(div.nodes[2].tagName, 'TEMPLATE'); | |
| 182 expect(div.nodes[2].attributes['syntax'], 'Test2'); | |
| 183 | |
| 184 var testLog = TemplateElement.syntax['Test'].log; | |
| 185 var test2Log = TemplateElement.syntax['Test2'].log; | |
| 186 | |
| 187 expect(testLog, [ | |
| 188 [model, '', 'bind', 'TEMPLATE'], | |
| 189 [model, 'foo', 'text', null], | |
| 190 [model, '', 'bind', 'TEMPLATE'] | |
| 191 ]); | |
| 192 | |
| 193 expect(test2Log, [[model, 'foo', 'text', null]]); | |
| 194 | |
| 195 TemplateElement.syntax.remove('Test'); | |
| 196 TemplateElement.syntax.remove('Test2'); | |
| 197 }); | |
| 198 } | |
| 199 | |
| 200 // TODO(jmesserly): mocks would be cleaner here. | |
| 201 | |
| 202 class TestBindingSyntax extends CustomBindingSyntax { | |
| 203 var log = []; | |
| 204 | |
| 205 getBinding(model, String path, String name, Node node) { | |
| 206 log.add([model, path, name, node is Element ? node.tagName : null]); | |
| 207 } | |
| 208 } | |
| 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 | |
| 253 class TimesTwoSyntax extends CustomBindingSyntax { | |
| 254 getBinding(model, path, name, node) { | |
| 255 path = path.trim(); | |
| 256 if (!path.startsWith('2x:')) return null; | |
| 257 | |
| 258 path = path.substring(3); | |
| 259 return new CompoundBinding((values) => values['value'] * 2) | |
| 260 ..bind('value', model, path); | |
| 261 } | |
| 262 } | |
| OLD | NEW |