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 #library('dartest'); | |
6 | |
7 #import('dart:dom'); | |
8 #import('../unittest/unittest_dartest.dart'); | |
9 | |
10 #source('css.dart'); | |
11 | |
12 /** DARTest provides a library to run tests in the app. */ | |
13 class DARTest{ | |
14 Map<String, HTMLElement> _inAppElements, _fullAppElements, _appElements; | |
15 DOMWindow _runnerWindow; | |
16 | |
17 DARTest() { | |
18 _runnerWindow = window; | |
19 dartestLogger = _log; | |
20 _inAppElements = new HashMap<String, HTMLElement>(); | |
21 _appElements = _inAppElements; | |
22 DARTestCss.inject(document, true); | |
23 } | |
24 | |
25 void run() { | |
26 _renderMain(); | |
27 _createResultsTable(); | |
28 } | |
29 | |
30 void _log(String message) { | |
31 _runnerWindow.console.log(message); | |
32 } | |
33 | |
34 void _addChildToElem(String key, HTMLElement child) { | |
35 if(_appElements[key] != null) { | |
36 _appElements[key].appendChild(child); | |
37 } | |
38 } | |
39 | |
40 /** Create the results table after loading tests. */ | |
41 void _createResultsTable() { | |
42 _log('Creating results table'); | |
43 HTMLTableElement table = _runnerWindow.document.createElement('table'); | |
44 table.className = 'dt-results'; | |
45 HTMLTableSectionElement head = _runnerWindow.document.createElement('thead') ; | |
46 head.innerHTML = '<tr><th>ID <th>Description <th>Result'; | |
47 table.appendChild(head); | |
48 | |
49 HTMLTableSectionElement body = _runnerWindow.document.createElement('tbody') ; | |
50 body.id = 'dt-results-body'; | |
51 tests.forEach((final t) { | |
52 HTMLTableRowElement testDetailRow = _runnerWindow.document.createElement(' tr'); | |
53 testDetailRow.id = 'dt-test-${t.id}'; | |
54 _addTestDetails(t, testDetailRow); | |
55 body.appendChild(testDetailRow); | |
56 | |
57 HTMLTableRowElement testMessageRow = _runnerWindow.document.createElement( 'tr'); | |
58 testMessageRow.id = 'dt-detail-${t.id}'; | |
59 testMessageRow.className = 'dt-hide'; | |
60 body.appendChild(testMessageRow); | |
61 }); | |
62 | |
63 table.appendChild(body); | |
64 _addChildToElem('testBody', table); | |
65 } | |
66 | |
67 /** Update the results table for test. */ | |
68 void _updateResultsTable(TestCase t, DOMWindow domWin) { | |
69 HTMLTableRowElement row = domWin.document.getElementById('dt-test-${t.id}'); | |
70 row.className = 'dt-result-row'; | |
71 row.innerHTML = ''; // Remove all children as we will re-populate them | |
72 _addTestDetails(t, row); | |
73 | |
74 HTMLTableRowElement details = | |
75 domWin.document.getElementById('dt-detail-${t.id}'); | |
76 details.appendChild(_getTestStats(t, domWin)); | |
77 | |
78 row.addEventListener('click', (Event e) { | |
79 if(details.className == 'dt-hide') { | |
80 details.className = ''; | |
81 } else { | |
82 details.className = 'dt-hide'; | |
83 } | |
84 }, true); | |
85 } | |
86 | |
87 /** Escape HTML special chars. */ | |
88 String _escape(String str) { | |
89 str = str.replaceAll('&','&'); | |
90 str = str.replaceAll('<','<'); | |
91 str = str.replaceAll('>','>'); | |
92 str = str.replaceAll('"','"'); | |
93 str = str.replaceAll("'",'''); | |
94 str = str.replaceAll('/','/'); | |
95 return str; | |
96 } | |
97 | |
98 /** Get test results as table cells. */ | |
99 void _addTestDetails(TestCase t, HTMLTableRowElement row) { | |
100 | |
pdr
2011/12/16 17:00:50
Extra space
shauvik
2011/12/16 17:57:06
Done.
| |
101 HTMLTableCellElement testId = _runnerWindow.document.createElement('td'); | |
102 testId.textContent = t.id; | |
103 row.appendChild(testId); | |
104 | |
105 HTMLTableCellElement testDesc = _runnerWindow.document.createElement('td'); | |
106 testDesc.textContent = t.description; | |
107 row.appendChild(testDesc); | |
108 | |
109 HTMLTableCellElement testResult = _runnerWindow.document.createElement('td') ; | |
110 String result = (t.result == null) ? 'none' : _escape(t.result); | |
111 testResult.className = 'dt-$result'; | |
112 testResult.title = '${_escape(t.message)}'; | |
113 testResult.textContent = result.toUpperCase(); | |
114 row.appendChild(testResult); | |
115 } | |
116 | |
117 HTMLTableCellElement _getTestStats(TestCase t, DOMWindow domWin) { | |
118 HTMLTableCellElement tableCell = domWin.document.createElement('td'); | |
119 tableCell.colSpan = 3; | |
120 | |
121 if(t.message != '') { | |
122 HTMLSpanElement messageSpan = domWin.document.createElement('span'); | |
123 messageSpan.textContent = t.message; | |
124 tableCell.appendChild(messageSpan); | |
125 tableCell.appendChild(domWin.document.createElement('br')); | |
126 } | |
127 if(t.stackTrace != null) { | |
128 HTMLPreElement stackTacePre = domWin.document.createElement('pre'); | |
129 stackTacePre.textContent = t.stackTrace; | |
130 } | |
131 | |
132 HTMLSpanElement durationSpan = domWin.document.createElement('span'); | |
133 durationSpan.textContent = 'took ${_printDuration(t.runningTime)}'; | |
134 tableCell.appendChild(durationSpan); | |
135 | |
136 return tableCell; | |
137 } | |
138 | |
139 /** Update the UI after running test. */ | |
140 void _updateDARTestUI(TestCase test) { | |
141 _updateResultsTable(test, window); | |
142 if(_runnerWindow != window) { | |
143 _updateResultsTable(test, _runnerWindow); | |
144 } | |
145 | |
146 if(test.result != null) { | |
147 _log(' Result: ${test.result.toUpperCase()} ${test.message}'); | |
148 } | |
149 if(test.runningTime != null) { | |
150 _log(' took ${_printDuration(test.runningTime)}'); | |
151 } | |
152 _updateStatusProgress(_appElements); | |
153 if(_runnerWindow != window) { | |
154 _updateStatusProgress(_inAppElements); | |
155 } | |
156 } | |
157 | |
158 void _updateStatusProgress(Map<String, HTMLElement> elements) { | |
159 // Update progressbar | |
160 var pPass = | |
161 ((testsRun - testsFailed - testsErrors) / tests.length) * 100; | |
162 elements['green'].setAttribute('style', 'width:$pPass%'); | |
163 var pFailed = pPass + (testsFailed / tests.length) * 100; | |
164 elements['red'].setAttribute('style', 'width:$pFailed%'); | |
165 var pErrors = pFailed + (testsErrors / tests.length) * 100; | |
166 elements['orange'].setAttribute('style', 'width:$pErrors%'); | |
167 | |
168 // Update status | |
169 elements['testsRunElem'].textContent = testsRun.toString(); | |
170 elements['testsFailedElem'].textContent = testsFailed.toString(); | |
171 elements['testsErrorsElem'].textContent = testsErrors.toString(); | |
172 } | |
173 | |
174 String _printDuration(Duration timeDuration) { | |
175 StringBuffer out = new StringBuffer(); | |
176 if(timeDuration.inDays > 0) { | |
177 out.add('${timeDuration.inDays} days '); | |
178 } | |
179 if(timeDuration.inHours > 0) { | |
180 out.add('${timeDuration.inHours} hrs '); | |
181 } | |
182 if(timeDuration.inMinutes > 0) { | |
183 out.add('${timeDuration.inMinutes} mins '); | |
184 } | |
185 if(timeDuration.inSeconds > 0) { | |
186 out.add('${timeDuration.inSeconds} s '); | |
187 } | |
188 if(timeDuration.inMilliseconds > 0 || out.length == 0) { | |
189 out.add('${timeDuration.inMilliseconds} ms'); | |
190 } | |
191 return out.toString(); | |
192 } | |
193 | |
194 /** Populates the floating div with controls and toolbar. */ | |
195 HTMLDivElement _renderMain() { | |
196 HTMLDivElement containerDiv = _runnerWindow.document.createElement('div'); | |
197 containerDiv.className = 'dt-container'; | |
198 _appElements['containerDiv'] = containerDiv; | |
199 | |
200 // Add the test controls | |
201 HTMLDivElement mainElem = _runnerWindow.document.createElement('div'); | |
202 mainElem.className = 'dt-main'; | |
203 _appElements['mainElem'] = mainElem; | |
204 | |
205 _showTestControls(); | |
206 | |
207 // Create header to hold window controls | |
208 if(_runnerWindow == window) { | |
209 HTMLDivElement headDiv = _runnerWindow.document.createElement('div'); | |
210 headDiv.className = 'dt-header'; | |
211 headDiv.innerHTML = '<b>DARTest: In-App View</b>'; | |
pdr
2011/12/16 17:00:50
I think you should use font-weight: bold in your C
| |
212 HTMLImageElement close = _runnerWindow.document.createElement('img'); | |
213 close.className = 'dt-header-close'; | |
214 close.addEventListener('click', (Event) { | |
215 containerDiv.className = 'dt-hide'; | |
216 }, true); | |
217 HTMLImageElement pop = _runnerWindow.document.createElement('img'); | |
218 pop.className = 'dt-header-pop'; | |
219 pop.addEventListener('click', (Event) => dartestMaximize(), true); | |
220 HTMLImageElement minMax = _runnerWindow.document.createElement('img'); | |
221 minMax.className = 'dt-header-min'; | |
222 minMax.addEventListener('click', (Event) { | |
223 if (mainElem.classList.contains('dt-hide')) { | |
224 mainElem.classList.remove('dt-hide'); | |
225 mainElem.classList.add('dt-show'); | |
226 minMax.className = 'dt-header-min'; | |
227 } else { | |
228 if (mainElem.classList.contains('dt-show')) { | |
229 mainElem.classList.remove('dt-show'); | |
230 } | |
231 mainElem.classList.add('dt-hide'); | |
232 minMax.className = 'dt-header-max'; | |
233 } | |
234 }, true); | |
235 headDiv.appendChild(close); | |
236 headDiv.appendChild(pop); | |
237 headDiv.appendChild(minMax); | |
238 | |
239 containerDiv.appendChild(headDiv); | |
240 } | |
241 | |
242 HTMLDivElement tabDiv = _runnerWindow.document.createElement('div'); | |
243 tabDiv.className = 'dt-tab'; | |
244 HTMLUListElement tabList = _runnerWindow.document.createElement('ul'); | |
245 HTMLLIElement testingTab = _runnerWindow.document.createElement('li'); | |
246 HTMLLIElement coverageTab = _runnerWindow.document.createElement('li'); | |
247 testingTab.className = 'dt-tab-selected'; | |
248 testingTab.textContent = 'Testing'; | |
249 testingTab.addEventListener('click', (Event) { | |
250 _showTestControls(); | |
251 _changeTabs(testingTab, coverageTab); | |
252 }, true); | |
253 tabList.appendChild(testingTab); | |
254 coverageTab.textContent = 'Coverage'; | |
255 coverageTab.addEventListener('click', (Event) { | |
256 _showCoverageControls(); | |
257 _changeTabs(coverageTab, testingTab); | |
258 }, true); | |
259 tabList.appendChild(coverageTab); | |
260 tabDiv.appendChild(tabList); | |
261 containerDiv.appendChild(tabDiv); | |
262 | |
263 if(_runnerWindow != window) { | |
264 HTMLDivElement popIn = _runnerWindow.document.createElement('div'); | |
265 popIn.className = 'dt-minimize'; | |
266 popIn.innerHTML = 'Pop In ⇲'; | |
267 popIn.addEventListener('click', (Event) => dartestMinimize(), true); | |
268 containerDiv.appendChild(popIn); | |
269 } | |
270 | |
271 containerDiv.appendChild(mainElem); | |
272 _runnerWindow.document.body.appendChild(containerDiv); | |
273 } | |
274 | |
275 void _changeTabs(HTMLLIElement clickedTab, HTMLLIElement oldTab) { | |
276 oldTab.className = ''; | |
277 clickedTab.className = 'dt-tab-selected'; | |
278 } | |
279 | |
280 void _showTestControls() { | |
281 HTMLDivElement testBody = _appElements['testBody']; | |
282 if(testBody == null) { | |
283 testBody = _runnerWindow.document.createElement('div'); | |
284 _appElements['testBody'] = testBody; | |
285 | |
286 // Create a toolbar to hold action buttons | |
287 HTMLDivElement toolDiv = _runnerWindow.document.createElement('div'); | |
288 toolDiv.className = 'dt-toolbar'; | |
289 HTMLButtonElement runBtn = _runnerWindow.document.createElement('button'); | |
290 runBtn.innerHTML = '►'; | |
291 runBtn.title = 'Run Tests'; | |
292 runBtn.className = 'dt-button dt-run'; | |
293 runBtn.addEventListener('click', (Event) { | |
294 _log('Running tests'); | |
295 updateUI = _updateDARTestUI; | |
296 runDartests(); | |
297 }, true); | |
298 toolDiv.appendChild(runBtn); | |
299 HTMLButtonElement exportBtn = | |
300 _runnerWindow.document.createElement('button'); | |
301 exportBtn.innerHTML = '↷'; | |
302 exportBtn.title = 'Export Results'; | |
303 exportBtn.className = 'dt-button dt-run'; | |
304 exportBtn.addEventListener('click', (Event e) { | |
305 _log('Exporting results'); | |
306 exportTestResults(); | |
307 }, true); | |
308 toolDiv.appendChild(exportBtn); | |
309 testBody.appendChild(toolDiv); | |
310 | |
311 // Create a datalist element for showing test status | |
312 HTMLDListElement statList = _runnerWindow.document.createElement('dl'); | |
313 statList.className = 'dt-status'; | |
314 HTMLElement runsDt = _runnerWindow.document.createElement('dt'); | |
315 runsDt.textContent = 'Runs:'; | |
316 statList.appendChild(runsDt); | |
317 HTMLElement testsRunElem = _runnerWindow.document.createElement('dd'); | |
318 _appElements['testsRunElem'] = testsRunElem; | |
319 testsRunElem.textContent = testsRun.toString(); | |
320 statList.appendChild(testsRunElem); | |
321 | |
322 HTMLElement failDt = _runnerWindow.document.createElement('dt'); | |
323 failDt.textContent = 'Failed:'; | |
324 statList.appendChild(failDt); | |
325 HTMLElement testsFailedElem = _runnerWindow.document.createElement('dd'); | |
326 _appElements['testsFailedElem'] = testsFailedElem; | |
327 testsFailedElem.textContent = testsFailed.toString(); | |
328 statList.appendChild(testsFailedElem); | |
329 | |
330 HTMLElement errDt = _runnerWindow.document.createElement('dt'); | |
331 errDt.textContent = 'Errors:'; | |
332 statList.appendChild(errDt); | |
333 HTMLElement testsErrorsElem = _runnerWindow.document.createElement('dd'); | |
334 _appElements['testsErrorsElem'] = testsErrorsElem; | |
335 testsErrorsElem.textContent = testsErrors.toString(); | |
336 statList.appendChild(testsErrorsElem); | |
337 testBody.appendChild(statList); | |
338 | |
339 // Create progressbar and add red, green, orange bars | |
340 HTMLDivElement progressDiv = _runnerWindow.document.createElement('div'); | |
341 progressDiv.className = 'dt-progressbar'; | |
342 progressDiv.innerHTML = "<span style='width:100%'></span>"; | |
343 | |
344 HTMLSpanElement orange = _runnerWindow.document.createElement('span'); | |
345 _appElements['orange'] = orange; | |
346 orange.className = 'orange'; | |
347 progressDiv.appendChild(orange); | |
348 | |
349 HTMLSpanElement red = _runnerWindow.document.createElement('span'); | |
350 _appElements['red'] = red; | |
351 red.className = 'red'; | |
352 progressDiv.appendChild(red); | |
353 | |
354 HTMLSpanElement green = _runnerWindow.document.createElement('span'); | |
355 _appElements['green'] = green; | |
356 green.className = 'green'; | |
357 | |
358 progressDiv.appendChild(green); | |
359 testBody.appendChild(progressDiv); | |
360 | |
361 HTMLDivElement hiddenElem = _runnerWindow.document.createElement('div'); | |
362 hiddenElem.className = 'dt-hide'; | |
363 hiddenElem.innerHTML = | |
364 "<a id='dt-export' download='test_results.csv' href='#' />"; | |
365 testBody.appendChild(hiddenElem); | |
366 | |
367 _addChildToElem('mainElem', testBody); | |
368 } | |
369 | |
370 // Show hide divs | |
371 _show('testBody'); | |
372 _hide('coverageBody'); | |
373 } | |
374 | |
375 void _showCoverageControls() { | |
376 HTMLDivElement coverageBody = _appElements['coverageBody']; | |
377 if(coverageBody == null) { | |
378 coverageBody = _runnerWindow.document.createElement('div'); | |
379 _appElements['coverageBody'] = coverageBody; | |
380 | |
381 HTMLPreElement covPreElem = _runnerWindow.document.createElement('pre'); | |
382 _appElements['covPreElem'] = covPreElem; | |
383 coverageBody.appendChild(covPreElem); | |
384 | |
385 HTMLTableElement covTable = _runnerWindow.document.createElement('table'); | |
386 covTable.className = 'dt-results'; | |
387 HTMLTableSectionElement head = | |
388 _runnerWindow.document.createElement('thead'); | |
389 head.innerHTML = '<tr><th>Unit <th>Function <th>Statement <th>Branch'; | |
390 covTable.appendChild(head); | |
391 HTMLTableSectionElement covTableBody = | |
392 _runnerWindow.document.createElement('tbody'); | |
393 _appElements['covTableBody'] = covTableBody; | |
394 covTableBody.id = 'dt-results-body'; | |
395 covTable.appendChild(covTableBody); | |
pdr
2011/12/16 17:00:50
indentation
shauvik
2011/12/16 17:57:06
Done.
| |
396 coverageBody.appendChild(covTable); | |
397 | |
398 _addChildToElem('mainElem', coverageBody); | |
399 } | |
400 _show('coverageBody'); | |
401 _hide('testBody'); | |
402 | |
403 _appElements['covPreElem'].textContent = getCoverageSummary(); | |
404 _appElements['covTableBody'].innerHTML = getCoverageDetails(); | |
405 } | |
406 | |
407 void _show(String toShow) { | |
408 HTMLElement show = _appElements[toShow]; | |
409 if(show != null) { | |
410 if(show.classList.contains('dt-hide')) { | |
411 show.classList.remove('dt-hide'); | |
412 } | |
413 show.classList.add('dt-show'); | |
414 } | |
415 } | |
416 | |
417 void _hide(String toHide) { | |
418 HTMLElement hide = _appElements[toHide]; | |
419 if(hide != null) { | |
420 if(hide.classList.contains('dt-show')) { | |
421 hide.classList.remove('dt-show'); | |
422 } | |
423 hide.classList.add('dt-hide'); | |
424 } | |
425 } | |
426 | |
427 void dartestMaximize() { | |
428 _hide('containerDiv'); | |
429 _runnerWindow = window.open('', 'dartest-window', 'width=600,height=750'); | |
pdr
2011/12/16 17:00:50
Optional nit: I like moving these kinds of static
shauvik
2011/12/16 17:57:06
Done.
| |
430 _runnerWindow.document.title = 'Dartest'; | |
431 _fullAppElements = new HashMap<String, HTMLElement>(); | |
432 _appElements = _fullAppElements; | |
433 DARTestCss.inject(_runnerWindow.document, false); | |
434 run(); | |
435 if(testsRun > 0) { | |
436 populateResults(); | |
437 } | |
438 } | |
439 | |
440 void populateResults() { | |
441 tests.forEach((final t) => _updateDARTestUI(t)); | |
442 } | |
443 | |
444 void dartestMinimize() { | |
445 _runnerWindow.close(); | |
446 _runnerWindow = window; | |
447 _appElements = _inAppElements; | |
448 _show('containerDiv'); | |
449 } | |
450 | |
451 void exportTestResults() { | |
452 String csvData = getTestResultsCsv(); | |
453 _log(csvData); | |
454 HTMLAnchorElement exportLink = | |
455 _runnerWindow.document.getElementById('dt-export'); | |
456 | |
457 /** Bug: Can't instantiate WebKitBlobBuilder | |
458 * If this bug is fixed, we can remove the urlencode and lpad function. | |
459 WebKitBlobBuilder bb = new WebKitBlobBuilder(); | |
460 bb.append(csvData); | |
461 Blob blob = bb.getBlob('text/plain;charset=${document.characterSet}'); | |
462 exportLink.href = window.webkitURL.createObjectURL(blob); | |
463 **/ | |
464 | |
465 exportLink.href = 'data:text/csv,'+urlencode(csvData); | |
466 | |
467 MouseEvent ev = document.createEvent("MouseEvents"); | |
468 ev.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0 | |
469 , false, false, false, false, 0, null); | |
470 exportLink.dispatchEvent(ev); | |
471 | |
472 } | |
473 | |
pdr
2011/12/16 17:00:50
Make these private and static
shauvik
2011/12/16 17:57:06
Done.
| |
474 String urlencode(String s) { | |
475 StringBuffer out = new StringBuffer(); | |
476 for(int i = 0; i < s.length; i++) { | |
477 int cc = s.charCodeAt(i); | |
478 if((cc >= 48 && cc <= 57) || (cc >= 65 && cc <= 90) || | |
479 (cc >= 97 && cc <= 122)) { | |
480 out.add(s[i]); | |
481 } else { | |
482 out.add('%${lpad(cc.toRadixString(16),2).toUpperCase()}'); | |
483 } | |
484 } | |
485 return out.toString(); | |
486 } | |
487 | |
488 String lpad(String s, int n) { | |
489 if(s.length < n) { | |
490 for(int i = 0; i < n - s.length; i++) { | |
491 s = '0'+s; | |
492 } | |
493 } | |
494 return s; | |
495 } | |
496 } | |
OLD | NEW |