OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2011, 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 /** | |
6 * A simple unit test library for running tests in a browser. | |
7 * | |
8 * Provides enhanced HTML output with collapsible group headers | |
9 * and other at-a-glance information about the test results. | |
10 */ | |
11 #library('unittest'); | |
12 | |
13 #import('dart:html'); | |
14 #import('unittest.dart'); | |
15 | |
16 | |
17 class HtmlEnhancedConfiguration extends Configuration { | |
18 /** Whether this is run within dartium layout tests. */ | |
19 final bool _isLayoutTest; | |
20 HtmlEnhancedConfiguration(this._isLayoutTest); | |
21 | |
22 // TODO(rnystrom): Get rid of this if we get canonical closures for methods. | |
23 EventListener _onErrorClosure; | |
24 | |
25 void onInit() { | |
26 //initialize and load CSS | |
27 final String _CSSID = '_unittestcss_'; | |
28 | |
29 var cssElement = document.head.query('#${_CSSID}'); | |
30 if (cssElement == null){ | |
31 document.head.elements.add(new Element.html( | |
32 '<style id="${_CSSID}"></style>')); | |
33 cssElement = document.head.query('#${_CSSID}'); | |
34 } | |
35 | |
36 cssElement.innerHTML = _htmlTestCSS; | |
37 | |
38 _onErrorClosure = (e) { | |
39 // TODO(vsm): figure out how to expose the stack trace here | |
40 // Currently e.message works in dartium, but not in dartc. | |
41 notifyError('(DOM callback has errors) Caught ${e}', ''); | |
42 }; | |
43 } | |
44 | |
45 void onStart() { | |
46 window.postMessage('unittest-suite-wait-for-done', '*'); | |
47 // Listen for uncaught errors. | |
48 window.on.error.add(_onErrorClosure); | |
49 } | |
50 | |
51 void onTestResult(TestCase testCase) {} | |
52 | |
53 void onDone(int passed, int failed, int errors, List<TestCase> results) { | |
54 window.on.error.remove(_onErrorClosure); | |
55 | |
56 _showInteractiveResultsInPage(passed, failed, errors, results, | |
57 _isLayoutTest); | |
58 | |
59 window.postMessage('unittest-suite-done', '*'); | |
60 } | |
61 | |
62 void _showInteractiveResultsInPage(int passed, int failed, int errors, | |
63 List<TestCase> results, bool isLayoutTest){ | |
64 if (isLayoutTest && passed == results.length) { | |
65 document.body.innerHTML = "PASS"; | |
66 } else { | |
67 // changed the StringBuffer to an Element fragment | |
68 Element te = new Element.html('<div class="unittest-table"></div>'); | |
69 | |
70 te.elements.add(new Element.html(passed == results.length | |
71 ? "<div class='unittest-overall unittest-pass'>PASS</div>" | |
72 : "<div class='unittest-overall unittest-fail'>FAIL</div>")); | |
73 | |
74 // moved summary to the top since web browsers | |
75 // don't auto-scroll to the bottom like consoles typically do. | |
76 if (passed == results.length) { | |
77 te.elements.add(new Element.html(""" | |
78 <div class='unittest-pass'>All ${passed} tests passed</div>""")); | |
79 } else { | |
80 | |
81 te.elements.add(new Element.html(""" | |
82 <div class='unittest-summary'> | |
83 <span class='unittest-pass'>Total ${passed} passed</span>, | |
84 <span class='unittest-fail'>${failed} failed</span>, | |
85 <span class='unittest-error'>${errors} errors</span> | |
86 </div>""")); | |
87 } | |
88 | |
89 te.elements.add(new Element.html(""" | |
90 <div><button id='btnCollapseAll'>Collapse All</button></div> | |
91 """)); | |
92 | |
93 // handle the click event for the collapse all button | |
94 te.query('#btnCollapseAll').on.click.add((_){ | |
95 document | |
96 .queryAll('.unittest-row') | |
97 .forEach((el) => el.attributes['class'] = el.attributes['class'] | |
98 .replaceAll('unittest-row ', 'unittest-row-hidden ')); | |
99 }); | |
100 | |
101 var previousGroup = ''; | |
102 var groupPassFail = true; | |
103 final indentAmount = 50; | |
104 | |
105 // order by group and sort numerically within each group | |
106 var groupedBy = new LinkedHashMap<String, List<TestCase>>(); | |
107 | |
108 for (final t in results){ | |
109 if (!groupedBy.containsKey(t.currentGroup)){ | |
110 groupedBy[t.currentGroup] = new List<TestCase>(); | |
111 } | |
112 | |
113 groupedBy[t.currentGroup].add(t); | |
114 } | |
115 | |
116 // flatten the list again with tests ordered | |
117 List<TestCase> flattened = new List<TestCase>(); | |
118 | |
119 groupedBy | |
120 .getValues() | |
121 .forEach((tList){ | |
122 tList.sort((tcA, tcB) => tcA.id - tcB.id); | |
123 flattened.addAll(tList); | |
124 } | |
125 ); | |
126 | |
127 // output group headers and test rows | |
128 for (final test_ in flattened) { | |
129 | |
130 // replace everything but numbers and letters from the group name with | |
131 // '_' so we can use in id and class properties. | |
132 var safeGroup = test_.currentGroup | |
133 .replaceAll("(?:[^a-z0-9 ]|(?<=['\"])s)",'_') | |
Siggi Cherem (dart-lang)
2012/04/24 23:26:30
Can you help me understand this expression better.
| |
134 .replaceAll(' ','_'); | |
135 | |
136 if (test_.currentGroup != previousGroup){ | |
137 | |
138 previousGroup = test_.currentGroup; | |
139 | |
140 var testsInGroup = results.filter( | |
141 (TestCase t) => t.currentGroup == previousGroup); | |
142 var groupTotalTestCount = testsInGroup.length; | |
143 var groupTestPassedCount = testsInGroup.filter( | |
144 (TestCase t) => t.result == 'pass').length; | |
145 groupPassFail = groupTotalTestCount == groupTestPassedCount; | |
146 | |
147 te.elements.add(new Element.html(""" | |
148 <div> | |
149 <div id='${safeGroup}' | |
150 class='unittest-group ${safeGroup} test${safeGroup}'> | |
151 <div ${_isIE ? "style='display:inline-block' ": ""} | |
152 class='unittest-row-status'> | |
153 <div class='unittest-group-status unittest-group-status- | |
154 ${groupPassFail ? 'pass' : 'fail'}'></div> | |
155 </div> | |
156 <div ${_isIE ? "style='display:inline-block' ": ""}> | |
157 ${test_.currentGroup}</div> | |
158 <div ${_isIE ? "style='display:inline-block' ": ""}> | |
159 (${groupTestPassedCount}/${groupTotalTestCount})</div> | |
160 </div> | |
161 </div>""")); | |
162 | |
163 var grp = te.query('#${safeGroup}'); | |
164 if (grp != null){ | |
165 grp.on.click.add((_){ | |
166 var row = document.query('.unittest-row-${safeGroup}'); | |
167 if (row.attributes['class'].contains('unittest-row ')){ | |
168 document.queryAll('.unittest-row-${safeGroup}').forEach( | |
169 (e) => e.attributes['class'] = e.attributes['class'] | |
170 .replaceAll('unittest-row ', 'unittest-row-hidden ')); | |
171 }else{ | |
172 document.queryAll('.unittest-row-${safeGroup}').forEach( | |
173 (e) => e.attributes['class'] = e.attributes['class'] | |
174 .replaceAll('unittest-row-hidden', 'unittest-row')); | |
175 } | |
176 }); | |
177 } | |
178 } | |
179 | |
180 _buildRow(test_, te, safeGroup, !groupPassFail); | |
181 } | |
182 | |
183 document.body.elements.clear(); | |
184 document.body.elements.add(te); | |
185 } | |
186 } | |
187 | |
188 void _buildRow(TestCase test_, Element te, String groupID, bool isVisible) { | |
189 var background = 'unittest-row-${test_.id % 2 == 0 ? "even" : "odd"}'; | |
190 var display = '${isVisible ? "unittest-row" : "unittest-row-hidden"}'; | |
191 | |
192 // TODO (prujohn@gmail.com) I had to borrow this from html_print.dart | |
193 // Probably should put it in some more common location. | |
194 String _htmlEscape(String string) { | |
195 return string.replaceAll('&', '&') | |
196 .replaceAll('<','<') | |
197 .replaceAll('>','>'); | |
198 } | |
199 | |
200 addRowElement(id, status, description){ | |
201 te.elements.add( | |
202 new Element.html( | |
203 ''' <div> | |
204 <div class='$display unittest-row-${groupID} $background'> | |
205 <div ${_isIE ? "style='display:inline-block' ": ""} | |
206 class='unittest-row-id'>$id</div> | |
207 <div ${_isIE ? "style='display:inline-block' ": ""} | |
208 class="unittest-row-status unittest-${test_.result}"> | |
209 $status</div> | |
210 <div ${_isIE ? "style='display:inline-block' ": ""} | |
211 class='unittest-row-description'>$description</div> | |
212 </div> | |
213 </div>''' | |
214 ) | |
215 ); | |
216 } | |
217 | |
218 if (!test_.isComplete) { | |
219 addRowElement('${test_.id}', 'NO STATUS', 'Test did not complete.'); | |
220 return; | |
221 } | |
222 | |
223 addRowElement('${test_.id}', '${test_.result.toUpperCase()}', | |
224 '${test_.description}. ${_htmlEscape(test_.message)}'); | |
225 | |
226 if (test_.stackTrace != null) { | |
227 addRowElement('', '', '<pre>${_htmlEscape(test_.stackTrace)}</pre>'); | |
228 } | |
229 } | |
230 | |
231 | |
232 static bool get _isIE() => document.window.navigator.userAgent.contains('MSIE' ); | |
Siggi Cherem (dart-lang)
2012/04/21 00:02:13
still a few more lines with > 80 (here and below)
| |
233 | |
234 String get _htmlTestCSS() => | |
235 ''' | |
236 body{ | |
237 font-size: 14px; | |
238 font-family: 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande', sans-serif ; | |
239 background: WhiteSmoke; | |
240 } | |
241 | |
242 .unittest-group | |
243 { | |
244 background: rgb(75,75,75); | |
245 width:98%; | |
246 color: WhiteSmoke; | |
247 font-weight: bold; | |
248 padding: 6px; | |
249 | |
250 /* Provide some visual separation between groups for IE */ | |
251 ${_isIE ? "border-bottom:solid black 1px;": ""} | |
252 ${_isIE ? "border-top:solid #777777 1px;": ""} | |
253 | |
254 background-image: -webkit-linear-gradient(bottom, rgb(50,50,50) 0%, rgb(100, 100,100) 100%); | |
255 background-image: -moz-linear-gradient(bottom, rgb(50,50,50) 0%, rgb(100,100 ,100) 100%); | |
256 background-image: -ms-linear-gradient(bottom, rgb(50,50,50) 0%, rgb(100,100, 100) 100%); | |
257 background-image: linear-gradient(bottom, rgb(50,50,50) 0%, rgb(100,100,100) 100%); | |
258 | |
259 display: -webkit-box; | |
260 display: -moz-box; | |
261 display: -ms-box; | |
262 display: box; | |
263 | |
264 -webkit-box-orient: horizontal; | |
265 -moz-box-orient: horizontal; | |
266 -ms-box-orient: horizontal; | |
267 box-orient: horizontal; | |
268 | |
269 -webkit-box-align: center; | |
270 -moz-box-align: center; | |
271 -ms-box-align: center; | |
272 box-align: center; | |
273 } | |
274 | |
275 .unittest-group-status | |
276 { | |
277 width: 20px; | |
278 height: 20px; | |
279 border-radius: 20px; | |
280 margin-left: 10px; | |
281 } | |
282 | |
283 .unittest-group-status-pass{ | |
284 background: Green; | |
285 background: -webkit-radial-gradient(center, ellipse cover, #AAFFAA 0%,Green 100%); | |
286 background: -moz-radial-gradient(center, ellipse cover, #AAFFAA 0%,Green 100 %); | |
287 background: -ms-radial-gradient(center, ellipse cover, #AAFFAA 0%,Green 100% ); | |
288 background: radial-gradient(center, ellipse cover, #AAFFAA 0%,Green 100%); | |
289 } | |
290 | |
291 .unittest-group-status-fail{ | |
292 background: Red; | |
293 background: -webkit-radial-gradient(center, ellipse cover, #FFAAAA 0%,Red 10 0%); | |
294 background: -moz-radial-gradient(center, ellipse cover, #FFAAAA 0%,Red 100%) ; | |
295 background: -ms-radial-gradient(center, ellipse cover, #AAFFAA 0%,Green 100% ); | |
296 background: radial-gradient(center, ellipse cover, #FFAAAA 0%,Red 100%); | |
297 } | |
298 | |
299 .unittest-overall{ | |
300 font-size: 20px; | |
301 } | |
302 | |
303 .unittest-summary{ | |
304 font-size: 18px; | |
305 } | |
306 | |
307 .unittest-pass{ | |
308 color: Green; | |
309 } | |
310 | |
311 .unittest-fail, .unittest-error | |
312 { | |
313 color: Red; | |
314 } | |
315 | |
316 .unittest-row | |
317 { | |
318 display: -webkit-box; | |
319 display: -moz-box; | |
320 display: -ms-box; | |
321 display: box; | |
322 -webkit-box-orient: horizontal; | |
323 -moz-box-orient: horizontal; | |
324 -ms-box-orient: horizontal; | |
325 box-orient: horizontal; | |
326 width: 100%; | |
327 } | |
328 | |
329 .unittest-row-hidden | |
330 { | |
331 display: none; | |
332 } | |
333 | |
334 .unittest-row-odd | |
335 { | |
336 background: WhiteSmoke; | |
337 } | |
338 | |
339 .unittest-row-even | |
340 { | |
341 background: #E5E5E5; | |
342 } | |
343 | |
344 .unittest-row-id | |
345 { | |
346 width: 3em; | |
347 } | |
348 | |
349 .unittest-row-status | |
350 { | |
351 width: 4em; | |
352 } | |
353 | |
354 .unittest-row-description | |
355 { | |
356 } | |
357 | |
358 '''; | |
359 } | |
360 | |
361 void useHtmlEnhancedConfiguration([bool isLayoutTest = false]) { | |
362 configure(new HtmlEnhancedConfiguration(isLayoutTest)); | |
363 } | |
OLD | NEW |