OLD | NEW |
(Empty) | |
| 1 library TestUtils; |
| 2 |
| 3 import 'dart:async'; |
| 4 import 'dart:html'; |
| 5 import 'dart:js' as js; |
| 6 import 'dart:typed_data'; |
| 7 import 'package:unittest/unittest.dart'; |
| 8 export 'package:web_components/polyfill.dart'; |
| 9 |
| 10 /** |
| 11 * Verifies that [actual] has the same graph structure as [expected]. |
| 12 * Detects cycles and DAG structure in Maps and Lists. |
| 13 */ |
| 14 verifyGraph(expected, actual) { |
| 15 var eItems = []; |
| 16 var aItems = []; |
| 17 |
| 18 message(path, reason) => path == '' |
| 19 ? reason |
| 20 : reason == null ? "path: $path" : "path: $path, $reason"; |
| 21 |
| 22 walk(path, expected, actual) { |
| 23 if (expected is String || expected is num || expected == null) { |
| 24 expect(actual, equals(expected), reason: message(path, 'not equal')); |
| 25 return; |
| 26 } |
| 27 |
| 28 // Cycle or DAG? |
| 29 for (int i = 0; i < eItems.length; i++) { |
| 30 if (identical(expected, eItems[i])) { |
| 31 expect(actual, same(aItems[i]), |
| 32 reason: message(path, 'missing back or side edge')); |
| 33 return; |
| 34 } |
| 35 } |
| 36 for (int i = 0; i < aItems.length; i++) { |
| 37 if (identical(actual, aItems[i])) { |
| 38 expect(expected, same(eItems[i]), |
| 39 reason: message(path, 'extra back or side edge')); |
| 40 return; |
| 41 } |
| 42 } |
| 43 eItems.add(expected); |
| 44 aItems.add(actual); |
| 45 |
| 46 if (expected is Blob) { |
| 47 expect(actual is Blob, isTrue, |
| 48 reason: '$actual is Blob'); |
| 49 expect(expected.type, equals(actual.type), |
| 50 reason: message(path, '.type')); |
| 51 expect(expected.size, equals(actual.size), |
| 52 reason: message(path, '.size')); |
| 53 return; |
| 54 } |
| 55 |
| 56 if (expected is ByteBuffer) { |
| 57 expect(actual is ByteBuffer, isTrue, |
| 58 reason: '$actual is ByteBuffer'); |
| 59 expect(expected.lengthInBytes, equals(actual.lengthInBytes), |
| 60 reason: message(path, '.lengthInBytes')); |
| 61 // TODO(antonm): one can create a view on top of those |
| 62 // and check if contents identical. Let's do it later. |
| 63 return; |
| 64 } |
| 65 |
| 66 if (expected is DateTime) { |
| 67 expect(actual is DateTime, isTrue, |
| 68 reason: '$actual is DateTime'); |
| 69 expect(expected.millisecondsSinceEpoch, |
| 70 equals(actual.millisecondsSinceEpoch), |
| 71 reason: message(path, '.millisecondsSinceEpoch')); |
| 72 return; |
| 73 } |
| 74 |
| 75 if (expected is ImageData) { |
| 76 expect(actual is ImageData, isTrue, |
| 77 reason: '$actual is ImageData'); |
| 78 expect(expected.width, equals(actual.width), |
| 79 reason: message(path, '.width')); |
| 80 expect(expected.height, equals(actual.height), |
| 81 reason: message(path, '.height')); |
| 82 walk('$path.data', expected.data, actual.data); |
| 83 return; |
| 84 } |
| 85 |
| 86 if (expected is TypedData) { |
| 87 expect(actual is TypedData, isTrue, |
| 88 reason: '$actual is TypedData'); |
| 89 walk('$path/.buffer', expected.buffer, actual.buffer); |
| 90 expect(expected.offsetInBytes, equals(actual.offsetInBytes), |
| 91 reason: message(path, '.offsetInBytes')); |
| 92 expect(expected.lengthInBytes, equals(actual.lengthInBytes), |
| 93 reason: message(path, '.lengthInBytes')); |
| 94 // And also fallback to elements check below. |
| 95 } |
| 96 |
| 97 if (expected is List) { |
| 98 expect(actual, isList, reason: message(path, '$actual is List')); |
| 99 expect(actual.length, expected.length, |
| 100 reason: message(path, 'different list lengths')); |
| 101 for (var i = 0; i < expected.length; i++) { |
| 102 walk('$path[$i]', expected[i], actual[i]); |
| 103 } |
| 104 return; |
| 105 } |
| 106 |
| 107 if (expected is Map) { |
| 108 expect(actual, isMap, reason: message(path, '$actual is Map')); |
| 109 for (var key in expected.keys) { |
| 110 if (!actual.containsKey(key)) { |
| 111 expect(false, isTrue, reason: message(path, 'missing key "$key"')); |
| 112 } |
| 113 walk('$path["$key"]', expected[key], actual[key]); |
| 114 } |
| 115 for (var key in actual.keys) { |
| 116 if (!expected.containsKey(key)) { |
| 117 expect(false, isTrue, reason: message(path, 'extra key "$key"')); |
| 118 } |
| 119 } |
| 120 return; |
| 121 } |
| 122 |
| 123 expect(false, isTrue, reason: 'Unhandled type: $expected'); |
| 124 } |
| 125 |
| 126 walk('', expected, actual); |
| 127 } |
| 128 |
| 129 |
| 130 /** |
| 131 * Sanitizer which does nothing. |
| 132 */ |
| 133 class NullTreeSanitizer implements NodeTreeSanitizer { |
| 134 void sanitizeTree(Node node) {} |
| 135 } |
| 136 |
| 137 |
| 138 /** |
| 139 * Validate that two DOM trees are equivalent. |
| 140 */ |
| 141 void validateNodeTree(Node a, Node b, [String path = '']) { |
| 142 path = '${path}${a.runtimeType}'; |
| 143 expect(a.nodeType, b.nodeType, reason: '$path nodeTypes differ'); |
| 144 expect(a.nodeValue, b.nodeValue, reason: '$path nodeValues differ'); |
| 145 expect(a.text, b.text, reason: '$path texts differ'); |
| 146 expect(a.nodes.length, b.nodes.length, reason: '$path nodes.lengths differ'); |
| 147 |
| 148 if (a is Element) { |
| 149 Element bE = b; |
| 150 Element aE = a; |
| 151 |
| 152 expect(aE.tagName, bE.tagName, reason: '$path tagNames differ'); |
| 153 expect(aE.attributes.length, bE.attributes.length, |
| 154 reason: '$path attributes.lengths differ'); |
| 155 for (var key in aE.attributes.keys) { |
| 156 expect(aE.attributes[key], bE.attributes[key], |
| 157 reason: '$path attribute [$key] values differ'); |
| 158 } |
| 159 } |
| 160 for (var i = 0; i < a.nodes.length; ++i) { |
| 161 validateNodeTree(a.nodes[i], b.nodes[i], '$path[$i].'); |
| 162 } |
| 163 } |
| 164 |
| 165 /** |
| 166 * Upgrade all custom elements in the subtree which have not been upgraded. |
| 167 * |
| 168 * This is needed to cover timing scenarios which the custom element polyfill |
| 169 * does not cover. |
| 170 */ |
| 171 void upgradeCustomElements(Node node) { |
| 172 if (js.context.hasProperty('CustomElements') && |
| 173 js.context['CustomElements'].hasProperty('upgradeAll')) { |
| 174 js.context['CustomElements'].callMethod('upgradeAll', [node]); |
| 175 } |
| 176 } |
OLD | NEW |