| OLD | NEW |
| 1 library ng_specs; | 1 library ng_specs; |
| 2 | 2 |
| 3 import 'dart:html' hide Animation; | 3 import 'dart:html'; |
| 4 | 4 import 'package:unittest/unittest.dart' as unit; |
| 5 import 'package:angular/angular.dart'; | 5 import 'package:angular/angular.dart'; |
| 6 import 'package:angular/mock/module.dart'; | 6 import 'package:angular/mock/module.dart'; |
| 7 import 'package:unittest/unittest.dart' as unit; | 7 import 'package:collection/wrappers.dart' show DelegatingList; |
| 8 | 8 |
| 9 import 'jasmine_syntax.dart' as jasmine_syntax; | 9 import 'jasmine_syntax.dart'; |
| 10 | 10 |
| 11 export 'dart:html' hide Animation; | 11 export 'dart:html'; |
| 12 export 'jasmine_syntax.dart' hide main; |
| 12 export 'package:unittest/unittest.dart'; | 13 export 'package:unittest/unittest.dart'; |
| 13 export 'package:mock/mock.dart'; | 14 export 'package:unittest/mock.dart'; |
| 14 export 'package:di/di.dart'; | |
| 15 export 'package:di/dynamic_injector.dart'; | 15 export 'package:di/dynamic_injector.dart'; |
| 16 export 'package:angular/angular.dart'; | 16 export 'package:angular/angular.dart'; |
| 17 export 'package:angular/application.dart'; | |
| 18 export 'package:angular/introspection.dart'; | |
| 19 export 'package:angular/core/annotation.dart'; | |
| 20 export 'package:angular/core/registry.dart'; | |
| 21 export 'package:angular/core/module_internal.dart'; | |
| 22 export 'package:angular/core_dom/module_internal.dart'; | |
| 23 export 'package:angular/core/parser/parser.dart'; | |
| 24 export 'package:angular/core/parser/lexer.dart'; | |
| 25 export 'package:angular/directive/module.dart'; | |
| 26 export 'package:angular/formatter/module.dart'; | |
| 27 export 'package:angular/routing/module.dart'; | |
| 28 export 'package:angular/animate/module.dart'; | |
| 29 export 'package:angular/mock/module.dart'; | 17 export 'package:angular/mock/module.dart'; |
| 30 export 'package:perf_api/perf_api.dart'; | 18 export 'package:perf_api/perf_api.dart'; |
| 31 | 19 |
| 32 es(String html) { | 20 es(String html) { |
| 33 var div = new DivElement(); | 21 var div = new DivElement(); |
| 34 div.setInnerHtml(html, treeSanitizer: new NullTreeSanitizer()); | 22 div.setInnerHtml(html, treeSanitizer: new NullTreeSanitizer()); |
| 35 return new List.from(div.nodes); | 23 return div.nodes; |
| 36 } | 24 } |
| 37 | 25 |
| 38 e(String html) => es(html).first; | 26 e(String html) => es(html).first; |
| 39 | 27 |
| 28 renderedText(n, [bool notShadow = false]) { |
| 29 if (n is List) { |
| 30 return n.map((nn) => renderedText(nn)).join(""); |
| 31 } |
| 32 |
| 33 if (n is Comment) return ''; |
| 34 |
| 35 if (!notShadow && n is Element && n.shadowRoot != null) { |
| 36 var shadowText = n.shadowRoot.text; |
| 37 var domText = renderedText(n, true); |
| 38 return shadowText.replaceFirst("SHADOW-CONTENT", domText); |
| 39 } |
| 40 |
| 41 if (n.nodes == null || n.nodes.length == 0) return n.text; |
| 42 |
| 43 return n.nodes.map((cn) => renderedText(cn)).join(""); |
| 44 } |
| 45 |
| 40 Expect expect(actual, [unit.Matcher matcher = null]) { | 46 Expect expect(actual, [unit.Matcher matcher = null]) { |
| 41 if (matcher != null) unit.expect(actual, matcher); | 47 if (matcher != null) { |
| 48 unit.expect(actual, matcher); |
| 49 } |
| 42 return new Expect(actual); | 50 return new Expect(actual); |
| 43 } | 51 } |
| 44 | 52 |
| 45 class Expect { | 53 class Expect { |
| 46 var actual; | 54 var actual; |
| 47 NotExpect not; | 55 var not; |
| 48 | |
| 49 Expect(this.actual) { | 56 Expect(this.actual) { |
| 50 not = new NotExpect(this); | 57 not = new NotExpect(this); |
| 51 } | 58 } |
| 52 | 59 |
| 53 toEqual(expected) => unit.expect(actual, unit.equals(expected)); | 60 toEqual(expected) => unit.expect(actual, unit.equals(expected)); |
| 54 toContain(expected) => unit.expect(actual, unit.contains(expected)); | 61 toContain(expected) => unit.expect(actual, unit.contains(expected)); |
| 55 toBe(expected) => unit.expect(actual, | 62 toBe(expected) => unit.expect(actual, |
| 56 unit.predicate((actual) => identical(expected, actual), '$expected')); | 63 unit.predicate((actual) => identical(expected, actual), '$expected')); |
| 57 toThrow([exception]) => unit.expect(actual, exception == null ? | 64 toThrow([exception]) => unit.expect(actual, exception == null ? unit.throws :
unit.throwsA(new ExceptionContains(exception))); |
| 58 unit.throws: | 65 toBeFalsy() => unit.expect(actual, (v) => v == null ? true : v is bool ? v ==
false : false); |
| 59 unit.throwsA(new ExceptionContains(exception))); | 66 toBeTruthy() => unit.expect(actual, (v) => v is bool ? v == true : true); |
| 60 toBeFalsy() => unit.expect(actual, _isFalsy, | 67 toBeDefined() => unit.expect(actual, (v) => v != null); |
| 61 reason: '"$actual" is not Falsy'); | |
| 62 toBeTruthy() => unit.expect(actual, (v) => !_isFalsy(v), | |
| 63 reason: '"$actual" is not Truthy'); | |
| 64 toBeDefined() => unit.expect(actual, unit.isNotNull); | |
| 65 toBeNull() => unit.expect(actual, unit.isNull); | 68 toBeNull() => unit.expect(actual, unit.isNull); |
| 66 toBeNotNull() => unit.expect(actual, unit.isNotNull); | 69 toBeNotNull() => unit.expect(actual, unit.isNotNull); |
| 67 | 70 |
| 68 toHaveHtml(expected) => unit.expect(_toHtml(actual), unit.equals(expected)); | 71 toHaveBeenCalled() => unit.expect(actual.called, true, reason: 'method not cal
led'); |
| 69 toHaveText(expected) => | 72 toHaveBeenCalledOnce() => unit.expect(actual.count, 1, reason: 'method invoked
${actual.count} expected once'); |
| 70 unit.expect(_elementText(actual), unit.equals(expected)); | |
| 71 | |
| 72 toHaveBeenCalled() => | |
| 73 unit.expect(actual.called, true, reason: 'method not called'); | |
| 74 toHaveBeenCalledOnce() => unit.expect(actual.count, 1, | |
| 75 reason: 'method invoked ${actual.count} expected once'); | |
| 76 toHaveBeenCalledWith([a,b,c,d,e,f]) => | 73 toHaveBeenCalledWith([a,b,c,d,e,f]) => |
| 77 unit.expect(actual.firstArgsMatch(a,b,c,d,e,f), true, | 74 unit.expect(actual.firstArgsMatch(a,b,c,d,e,f), true, |
| 78 reason: 'method invoked with correct arguments'); | 75 reason: 'method invoked with correct arguments'); |
| 79 toHaveBeenCalledOnceWith([a,b,c,d,e,f]) => | 76 toHaveBeenCalledOnceWith([a,b,c,d,e,f]) => |
| 80 unit.expect(actual.count == 1 && actual.firstArgsMatch(a,b,c,d,e,f), | 77 unit.expect(actual.count == 1 && actual.firstArgsMatch(a,b,c,d,e,f), |
| 81 true, | 78 true, |
| 82 reason: 'method invoked once with correct arguments. ' | 79 reason: 'method invoked once with correct arguments. (Called ${
actual.count} times)'); |
| 83 '(Called ${actual.count} times)'); | |
| 84 | 80 |
| 85 toHaveClass(cls) => unit.expect(actual.classes.contains(cls), true, | 81 toHaveClass(cls) => unit.expect(actual.classes.contains(cls), true, reason: '
Expected ${actual} to have css class ${cls}'); |
| 86 reason: ' Expected ${actual} to have css class ${cls}'); | |
| 87 | |
| 88 toHaveAttribute(name, [value = null]) { | |
| 89 unit.expect(actual.attributes.containsKey(name), true, | |
| 90 reason: 'Epxected $actual to have attribute $name'); | |
| 91 if (value != null) { | |
| 92 unit.expect(actual.attributes[name], value, | |
| 93 reason: 'Epxected $actual attribute "$name" to be "$value"'); | |
| 94 } | |
| 95 } | |
| 96 | 82 |
| 97 toEqualSelect(options) { | 83 toEqualSelect(options) { |
| 98 var actualOptions = []; | 84 var actualOptions = []; |
| 99 | 85 |
| 100 for (var option in actual.querySelectorAll('option')) { | 86 for (var option in actual.querySelectorAll('option')) { |
| 101 actualOptions.add(option.selected ? [option.value] : option.value); | 87 if (option.selected) { |
| 88 actualOptions.add([option.value]); |
| 89 } else { |
| 90 actualOptions.add(option.value); |
| 91 } |
| 102 } | 92 } |
| 103 return unit.expect(actualOptions, options); | 93 return unit.expect(actualOptions, options); |
| 104 } | 94 } |
| 105 | 95 |
| 106 toBeValid() => unit.expect(actual.valid && !actual.invalid, true, | 96 toEqualValid() { |
| 107 reason: 'Form is not valid'); | 97 // TODO: implement onece we have forms |
| 108 toBePristine() => | |
| 109 unit.expect(actual.pristine && !actual.dirty, true, | |
| 110 reason: 'Form is dirty'); | |
| 111 | |
| 112 _isFalsy(v) => v == null ? true: v is bool ? v == false : false; | |
| 113 | |
| 114 _toHtml(node, [bool outer = false]) { | |
| 115 if (node is Comment) { | |
| 116 return '<!--${node.text}-->'; | |
| 117 } else if (node is DocumentFragment) { | |
| 118 var acc = ''; | |
| 119 node.childNodes.forEach((n) { acc += _toHtml(n, true); }); | |
| 120 return acc; | |
| 121 } else if (node is List) { | |
| 122 var acc = ''; | |
| 123 node.forEach((n) { acc += _toHtml(n); }); | |
| 124 return acc; | |
| 125 } else if (node is Element) { | |
| 126 // Remove all the "ng-binding" internal classes | |
| 127 node = node.clone(true) as Element; | |
| 128 node.classes.remove('ng-binding'); | |
| 129 node.querySelectorAll(".ng-binding").forEach((Element e) { | |
| 130 e.classes.remove('ng-binding'); | |
| 131 }); | |
| 132 var htmlString = outer ? node.outerHtml : node.innerHtml; | |
| 133 // Strip out empty class attributes. This seems like a Dart bug... | |
| 134 return htmlString.replaceAll(' class=""', '').trim(); | |
| 135 } else if (node is Text) { | |
| 136 return node.text; | |
| 137 } else { | |
| 138 throw "JQuery._toHtml not implemented for node type [${node.nodeType}]"; | |
| 139 } | |
| 140 } | 98 } |
| 141 | 99 toEqualInvalid() { |
| 142 _elementText(n, [bool notShadow = false]) { | 100 // TODO: implement onece we have forms |
| 143 if (n is Iterable) { | 101 } |
| 144 return n.map((nn) => _elementText(nn)).join(""); | 102 toEqualPristine() { |
| 145 } | 103 // TODO: implement onece we have forms |
| 146 | 104 } |
| 147 if (n is Comment) return ''; | 105 toEqualDirty() { |
| 148 | 106 // TODO: implement onece we have forms |
| 149 if (!notShadow && n is Element && n.shadowRoot != null) { | |
| 150 var cShadows = n.shadowRoot.nodes.map((n) => n.clone(true)).toList(); | |
| 151 for (var i = 0, ii = cShadows.length; i < ii; i++) { | |
| 152 var n = cShadows[i]; | |
| 153 if (n is Element) { | |
| 154 var updateElement = (e) { | |
| 155 var text = new Text('SHADOW-CONTENT'); | |
| 156 if (e.parent == null) { | |
| 157 cShadows[i] = text; | |
| 158 } else { | |
| 159 e.parent.insertBefore(text, e); | |
| 160 } | |
| 161 e.nodes = []; | |
| 162 }; | |
| 163 if (n is ContentElement) { updateElement(n); } | |
| 164 n.querySelectorAll('content').forEach(updateElement); | |
| 165 } | |
| 166 }; | |
| 167 var shadowText = _elementText(cShadows, true); | |
| 168 var domText = _elementText(n, true); | |
| 169 | |
| 170 return shadowText.replaceFirst("SHADOW-CONTENT", domText); | |
| 171 } | |
| 172 | |
| 173 if (n.nodes == null || n.nodes.length == 0) return n.text; | |
| 174 | |
| 175 return n.nodes.map((cn) => _elementText(cn)).join(""); | |
| 176 } | 107 } |
| 177 } | 108 } |
| 178 | 109 |
| 179 class NotExpect { | 110 class NotExpect { |
| 180 Expect _expect; | 111 Expect expect; |
| 181 get actual => _expect.actual; | 112 get actual => expect.actual; |
| 113 NotExpect(this.expect); |
| 182 | 114 |
| 183 NotExpect(this._expect); | 115 toHaveBeenCalled() => unit.expect(actual.called, false, reason: 'method called
'); |
| 184 | |
| 185 toHaveBeenCalled() => | |
| 186 unit.expect(actual.called, false, reason: 'method called'); | |
| 187 toThrow() => actual(); | 116 toThrow() => actual(); |
| 188 | 117 |
| 189 toHaveClass(cls) => unit.expect(actual.classes.contains(cls), false, | 118 toHaveClass(cls) => unit.expect(actual.classes.contains(cls), false, reason: '
Expected ${actual} to not have css class ${cls}'); |
| 190 reason: ' Expected ${actual} to not have css class ${cls}'); | |
| 191 toHaveAttribute(name) => unit.expect(actual.attributes.containsKey(name), | |
| 192 false, reason: ' Expected $actual to not have attribute "$name"'); | |
| 193 toBe(expected) => unit.expect(actual, | 119 toBe(expected) => unit.expect(actual, |
| 194 unit.predicate((actual) => !identical(expected, actual), 'not $expected'))
; | 120 unit.predicate((actual) => !identical(expected, actual), 'not $expected'))
; |
| 195 toEqual(expected) => unit.expect(actual, | 121 toEqual(expected) => unit.expect(actual, |
| 196 unit.predicate((actual) => expected != actual, 'not $expected')); | 122 unit.predicate((actual) => expected != actual, 'not $expected')); |
| 197 toContain(expected) => unit.expect(actual, | 123 toContain(expected) => unit.expect(actual, |
| 198 unit.predicate((actual) => !actual.contains(expected), 'not $expected')); | 124 unit.predicate((actual) => !actual.contains(expected), 'not $expected')); |
| 199 toBePristine() => unit.expect(actual.pristine && !actual.dirty, false, | |
| 200 reason: 'Form is pristine'); | |
| 201 toBeValid() => unit.expect(actual.valid && !actual.invalid, false, | |
| 202 reason: 'Form is valid'); | |
| 203 } | 125 } |
| 204 | 126 |
| 205 class ExceptionContains extends unit.Matcher { | 127 class ExceptionContains extends unit.Matcher { |
| 206 | 128 |
| 207 final _expected; | 129 final _expected; |
| 208 | 130 |
| 209 const ExceptionContains(this._expected); | 131 const ExceptionContains(this._expected); |
| 210 | 132 |
| 211 bool matches(item, Map matchState) { | 133 bool matches(item, Map matchState) { |
| 212 if (item is String) { | 134 if (item is String) { |
| 213 return item.indexOf(_expected) >= 0; | 135 return item.indexOf(_expected) >= 0; |
| 214 } | 136 } |
| 215 return matches('$item', matchState); | 137 return matches('$item', matchState); |
| 216 } | 138 } |
| 217 | 139 |
| 218 unit.Description describe(unit.Description description) => | 140 unit.Description describe(unit.Description description) => |
| 219 description.add('exception contains ').addDescriptionOf(_expected); | 141 description.add('exception contains ').addDescriptionOf(_expected); |
| 220 | 142 |
| 221 unit.Description describeMismatch(item, unit.Description mismatchDescription, | 143 unit.Description describeMismatch(item, unit.Description mismatchDescription, |
| 222 Map matchState, bool verbose) { | 144 Map matchState, bool verbose) { |
| 223 return super.describeMismatch('$item', mismatchDescription, matchState, | 145 return super.describeMismatch('$item', mismatchDescription, matchState, |
| 224 verbose); | 146 verbose); |
| 225 } | 147 } |
| 226 } | 148 } |
| 227 | 149 |
| 228 _injectify(fn) { | 150 $(selector) { |
| 229 // The function does two things: | 151 return new JQuery(selector); |
| 230 // First: if the it() passed a function, we wrap it in | |
| 231 // the "sync" FunctionComposition. | |
| 232 // Second: when we are calling the FunctionComposition, | |
| 233 // we inject "inject" into the middle of the | |
| 234 // composition. | |
| 235 if (fn is! FunctionComposition) fn = sync(fn); | |
| 236 return fn.outer(inject(fn.inner)); | |
| 237 } | 152 } |
| 238 | 153 |
| 239 // Jasmine syntax | |
| 240 beforeEachModule(fn) => jasmine_syntax.beforeEach(module(fn), priority:1); | |
| 241 beforeEach(fn) => jasmine_syntax.beforeEach(_injectify(fn)); | |
| 242 afterEach(fn) => jasmine_syntax.afterEach(_injectify(fn)); | |
| 243 it(name, fn) => jasmine_syntax.it(name, _injectify(fn)); | |
| 244 iit(name, fn) => jasmine_syntax.iit(name, _injectify(fn)); | |
| 245 xit(name, fn) => jasmine_syntax.xit(name, fn); | |
| 246 xdescribe(name, fn) => jasmine_syntax.xdescribe(name, fn); | |
| 247 ddescribe(name, fn) => jasmine_syntax.ddescribe(name, fn); | |
| 248 describe(name, fn) => jasmine_syntax.describe(name, fn); | |
| 249 | 154 |
| 250 var jasmine = jasmine_syntax.jasmine; | 155 class GetterSetter { |
| 156 Getter getter(String key) => null; |
| 157 Setter setter(String key) => null; |
| 158 } |
| 159 var getterSetter = new GetterSetter(); |
| 160 |
| 161 class JQuery extends DelegatingList<Node> { |
| 162 JQuery([selector]) : super([]) { |
| 163 if (selector == null) { |
| 164 // do nothing; |
| 165 } else if (selector is String) { |
| 166 addAll(es(selector)); |
| 167 } else if (selector is List) { |
| 168 addAll(selector); |
| 169 } else if (selector is Node) { |
| 170 add(selector); |
| 171 } else { |
| 172 throw selector; |
| 173 } |
| 174 } |
| 175 |
| 176 _toHtml(node, [bool outer = false]) { |
| 177 if (node is Comment) { |
| 178 return '<!--${node.text}-->'; |
| 179 } else { |
| 180 return outer ? node.outerHtml : node.innerHtml; |
| 181 } |
| 182 } |
| 183 |
| 184 accessor(Function getter, Function setter, [value, single=false]) { |
| 185 // TODO(dart): ?value does not work, since value was passed. :-( |
| 186 var setterMode = value != null; |
| 187 var result = setterMode ? this : ''; |
| 188 forEach((node) { |
| 189 if (setterMode) { |
| 190 setter(node, value); |
| 191 } else { |
| 192 result = single ? getter(node) : '$result${getter(node)}'; |
| 193 } |
| 194 }); |
| 195 return result; |
| 196 } |
| 197 |
| 198 html([String html]) => accessor( |
| 199 (n) => _toHtml(n), |
| 200 (n, v) => n.setInnerHtml(v, treeSanitizer: new NullTreeSanitizer()), |
| 201 html); |
| 202 val([String text]) => accessor((n) => n.value, (n, v) => n.value = v); |
| 203 text([String text]) => accessor((n) => n.text, (n, v) => n.text = v, text); |
| 204 contents() => fold(new JQuery(), (jq, node) => jq..addAll(node.nodes)); |
| 205 toString() => fold('', (html, node) => '$html${_toHtml(node, true)}'); |
| 206 eq(num childIndex) => $(this[childIndex]); |
| 207 remove(_) => forEach((n) => n.remove()); |
| 208 attr([String name, String value]) => accessor( |
| 209 (n) => n.attributes[name], |
| 210 (n, v) => n.attributes[name] = v, |
| 211 value, |
| 212 true); |
| 213 prop([String name]) => accessor( |
| 214 (n) => getterSetter.getter(name)(n), |
| 215 (n, v) => getterSetter.setter(name)(n, v), |
| 216 null, |
| 217 true); |
| 218 textWithShadow() => fold('', (t, n) => '${t}${renderedText(n)}'); |
| 219 find(selector) => fold(new JQuery(), (jq, n) => jq..addAll( |
| 220 (n is Element ? (n as Element).querySelectorAll(selector) : []))); |
| 221 hasClass(String name) => fold(false, (hasClass, node) => |
| 222 hasClass || (node is Element && (node as Element).classes.contains(name)))
; |
| 223 addClass(String name) => forEach((node) => |
| 224 (node is Element) ? (node as Element).classes.add(name) : null); |
| 225 removeClass(String name) => forEach((node) => |
| 226 (node is Element) ? (node as Element).classes.remove(name) : null); |
| 227 css(String name, [String value]) => accessor( |
| 228 (Element n) => n.style.getPropertyValue(name), |
| 229 (Element n, v) => n.style.setProperty(name, value), value); |
| 230 children() => new JQuery(this[0].childNodes); |
| 231 } |
| 232 |
| 251 | 233 |
| 252 main() { | 234 main() { |
| 253 jasmine_syntax.beforeEach(setUpInjector, priority:3); | 235 beforeEach(setUpInjector); |
| 254 jasmine_syntax.afterEach(tearDownInjector); | 236 beforeEach(() => wrapFn(sync)); |
| 237 afterEach(tearDownInjector); |
| 255 } | 238 } |
| OLD | NEW |