OLD | NEW |
1 library ng_specs; | 1 library ng_specs; |
2 | 2 |
3 import 'dart:html'; | 3 import 'dart:html' hide Animation; |
4 import 'package:unittest/unittest.dart' as unit; | 4 |
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:collection/wrappers.dart' show DelegatingList; | 7 import 'package:unittest/unittest.dart' as unit; |
8 | 8 |
9 import 'jasmine_syntax.dart'; | 9 import 'jasmine_syntax.dart' as jasmine_syntax; |
10 | 10 |
11 export 'dart:html'; | 11 export 'dart:html' hide Animation; |
12 export 'jasmine_syntax.dart' hide main; | |
13 export 'package:unittest/unittest.dart'; | 12 export 'package:unittest/unittest.dart'; |
14 export 'package:unittest/mock.dart'; | 13 export 'package:mock/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'; |
17 export 'package:angular/mock/module.dart'; | 29 export 'package:angular/mock/module.dart'; |
18 export 'package:perf_api/perf_api.dart'; | 30 export 'package:perf_api/perf_api.dart'; |
19 | 31 |
20 es(String html) { | 32 es(String html) { |
21 var div = new DivElement(); | 33 var div = new DivElement(); |
22 div.setInnerHtml(html, treeSanitizer: new NullTreeSanitizer()); | 34 div.setInnerHtml(html, treeSanitizer: new NullTreeSanitizer()); |
23 return div.nodes; | 35 return new List.from(div.nodes); |
24 } | 36 } |
25 | 37 |
26 e(String html) => es(html).first; | 38 e(String html) => es(html).first; |
27 | 39 |
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 | |
46 Expect expect(actual, [unit.Matcher matcher = null]) { | 40 Expect expect(actual, [unit.Matcher matcher = null]) { |
47 if (matcher != null) { | 41 if (matcher != null) unit.expect(actual, matcher); |
48 unit.expect(actual, matcher); | |
49 } | |
50 return new Expect(actual); | 42 return new Expect(actual); |
51 } | 43 } |
52 | 44 |
53 class Expect { | 45 class Expect { |
54 var actual; | 46 var actual; |
55 var not; | 47 NotExpect not; |
| 48 |
56 Expect(this.actual) { | 49 Expect(this.actual) { |
57 not = new NotExpect(this); | 50 not = new NotExpect(this); |
58 } | 51 } |
59 | 52 |
60 toEqual(expected) => unit.expect(actual, unit.equals(expected)); | 53 toEqual(expected) => unit.expect(actual, unit.equals(expected)); |
61 toContain(expected) => unit.expect(actual, unit.contains(expected)); | 54 toContain(expected) => unit.expect(actual, unit.contains(expected)); |
62 toBe(expected) => unit.expect(actual, | 55 toBe(expected) => unit.expect(actual, |
63 unit.predicate((actual) => identical(expected, actual), '$expected')); | 56 unit.predicate((actual) => identical(expected, actual), '$expected')); |
64 toThrow([exception]) => unit.expect(actual, exception == null ? unit.throws :
unit.throwsA(new ExceptionContains(exception))); | 57 toThrow([exception]) => unit.expect(actual, exception == null ? |
65 toBeFalsy() => unit.expect(actual, (v) => v == null ? true : v is bool ? v ==
false : false); | 58 unit.throws: |
66 toBeTruthy() => unit.expect(actual, (v) => v is bool ? v == true : true); | 59 unit.throwsA(new ExceptionContains(exception))); |
67 toBeDefined() => unit.expect(actual, (v) => v != null); | 60 toBeFalsy() => unit.expect(actual, _isFalsy, |
| 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); |
68 toBeNull() => unit.expect(actual, unit.isNull); | 65 toBeNull() => unit.expect(actual, unit.isNull); |
69 toBeNotNull() => unit.expect(actual, unit.isNotNull); | 66 toBeNotNull() => unit.expect(actual, unit.isNotNull); |
70 | 67 |
71 toHaveBeenCalled() => unit.expect(actual.called, true, reason: 'method not cal
led'); | 68 toHaveHtml(expected) => unit.expect(_toHtml(actual), unit.equals(expected)); |
72 toHaveBeenCalledOnce() => unit.expect(actual.count, 1, reason: 'method invoked
${actual.count} expected once'); | 69 toHaveText(expected) => |
| 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'); |
73 toHaveBeenCalledWith([a,b,c,d,e,f]) => | 76 toHaveBeenCalledWith([a,b,c,d,e,f]) => |
74 unit.expect(actual.firstArgsMatch(a,b,c,d,e,f), true, | 77 unit.expect(actual.firstArgsMatch(a,b,c,d,e,f), true, |
75 reason: 'method invoked with correct arguments'); | 78 reason: 'method invoked with correct arguments'); |
76 toHaveBeenCalledOnceWith([a,b,c,d,e,f]) => | 79 toHaveBeenCalledOnceWith([a,b,c,d,e,f]) => |
77 unit.expect(actual.count == 1 && actual.firstArgsMatch(a,b,c,d,e,f), | 80 unit.expect(actual.count == 1 && actual.firstArgsMatch(a,b,c,d,e,f), |
78 true, | 81 true, |
79 reason: 'method invoked once with correct arguments. (Called ${
actual.count} times)'); | 82 reason: 'method invoked once with correct arguments. ' |
| 83 '(Called ${actual.count} times)'); |
80 | 84 |
81 toHaveClass(cls) => unit.expect(actual.classes.contains(cls), true, reason: '
Expected ${actual} to have css class ${cls}'); | 85 toHaveClass(cls) => unit.expect(actual.classes.contains(cls), true, |
| 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 } |
82 | 96 |
83 toEqualSelect(options) { | 97 toEqualSelect(options) { |
84 var actualOptions = []; | 98 var actualOptions = []; |
85 | 99 |
86 for (var option in actual.querySelectorAll('option')) { | 100 for (var option in actual.querySelectorAll('option')) { |
87 if (option.selected) { | 101 actualOptions.add(option.selected ? [option.value] : option.value); |
88 actualOptions.add([option.value]); | |
89 } else { | |
90 actualOptions.add(option.value); | |
91 } | |
92 } | 102 } |
93 return unit.expect(actualOptions, options); | 103 return unit.expect(actualOptions, options); |
94 } | 104 } |
95 | 105 |
96 toEqualValid() { | 106 toBeValid() => unit.expect(actual.valid && !actual.invalid, true, |
97 // TODO: implement onece we have forms | 107 reason: 'Form is not valid'); |
| 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 } |
98 } | 140 } |
99 toEqualInvalid() { | 141 |
100 // TODO: implement onece we have forms | 142 _elementText(n, [bool notShadow = false]) { |
101 } | 143 if (n is Iterable) { |
102 toEqualPristine() { | 144 return n.map((nn) => _elementText(nn)).join(""); |
103 // TODO: implement onece we have forms | 145 } |
104 } | 146 |
105 toEqualDirty() { | 147 if (n is Comment) return ''; |
106 // TODO: implement onece we have forms | 148 |
| 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(""); |
107 } | 176 } |
108 } | 177 } |
109 | 178 |
110 class NotExpect { | 179 class NotExpect { |
111 Expect expect; | 180 Expect _expect; |
112 get actual => expect.actual; | 181 get actual => _expect.actual; |
113 NotExpect(this.expect); | |
114 | 182 |
115 toHaveBeenCalled() => unit.expect(actual.called, false, reason: 'method called
'); | 183 NotExpect(this._expect); |
| 184 |
| 185 toHaveBeenCalled() => |
| 186 unit.expect(actual.called, false, reason: 'method called'); |
116 toThrow() => actual(); | 187 toThrow() => actual(); |
117 | 188 |
118 toHaveClass(cls) => unit.expect(actual.classes.contains(cls), false, reason: '
Expected ${actual} to not have css class ${cls}'); | 189 toHaveClass(cls) => unit.expect(actual.classes.contains(cls), false, |
| 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"'); |
119 toBe(expected) => unit.expect(actual, | 193 toBe(expected) => unit.expect(actual, |
120 unit.predicate((actual) => !identical(expected, actual), 'not $expected'))
; | 194 unit.predicate((actual) => !identical(expected, actual), 'not $expected'))
; |
121 toEqual(expected) => unit.expect(actual, | 195 toEqual(expected) => unit.expect(actual, |
122 unit.predicate((actual) => expected != actual, 'not $expected')); | 196 unit.predicate((actual) => expected != actual, 'not $expected')); |
123 toContain(expected) => unit.expect(actual, | 197 toContain(expected) => unit.expect(actual, |
124 unit.predicate((actual) => !actual.contains(expected), 'not $expected')); | 198 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'); |
125 } | 203 } |
126 | 204 |
127 class ExceptionContains extends unit.Matcher { | 205 class ExceptionContains extends unit.Matcher { |
128 | 206 |
129 final _expected; | 207 final _expected; |
130 | 208 |
131 const ExceptionContains(this._expected); | 209 const ExceptionContains(this._expected); |
132 | 210 |
133 bool matches(item, Map matchState) { | 211 bool matches(item, Map matchState) { |
134 if (item is String) { | 212 if (item is String) { |
135 return item.indexOf(_expected) >= 0; | 213 return item.indexOf(_expected) >= 0; |
136 } | 214 } |
137 return matches('$item', matchState); | 215 return matches('$item', matchState); |
138 } | 216 } |
139 | 217 |
140 unit.Description describe(unit.Description description) => | 218 unit.Description describe(unit.Description description) => |
141 description.add('exception contains ').addDescriptionOf(_expected); | 219 description.add('exception contains ').addDescriptionOf(_expected); |
142 | 220 |
143 unit.Description describeMismatch(item, unit.Description mismatchDescription, | 221 unit.Description describeMismatch(item, unit.Description mismatchDescription, |
144 Map matchState, bool verbose) { | 222 Map matchState, bool verbose) { |
145 return super.describeMismatch('$item', mismatchDescription, matchState, | 223 return super.describeMismatch('$item', mismatchDescription, matchState, |
146 verbose); | 224 verbose); |
147 } | 225 } |
148 } | 226 } |
149 | 227 |
150 $(selector) { | 228 _injectify(fn) { |
151 return new JQuery(selector); | 229 // The function does two things: |
| 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)); |
152 } | 237 } |
153 | 238 |
| 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); |
154 | 249 |
155 class GetterSetter { | 250 var jasmine = jasmine_syntax.jasmine; |
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 | |
233 | 251 |
234 main() { | 252 main() { |
235 beforeEach(setUpInjector); | 253 jasmine_syntax.beforeEach(setUpInjector, priority:3); |
236 beforeEach(() => wrapFn(sync)); | 254 jasmine_syntax.afterEach(tearDownInjector); |
237 afterEach(tearDownInjector); | |
238 } | 255 } |
OLD | NEW |