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'); | |
pdr
2011/12/14 21:02:36
I would strongly recommend using dart:html. It's g
shauvik
2011/12/16 07:19:27
When I started, you couldn't do many things using
| |
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{ | |
pdr
2011/12/14 21:02:36
Under the names section of the style guide (http:/
shauvik
2011/12/16 07:19:27
Kelly and I decided to have the same name across a
| |
14 Map<String, HTMLElement> inAppElements, fullAppElements, appElements; | |
pdr
2011/12/14 21:02:36
I don't think these are necessary (if they are, ca
shauvik
2011/12/16 07:19:27
I spoke to Kelly and Joel and its difficult and so
| |
15 bool inAppMode; | |
Kelly Norton
2011/12/15 18:35:31
I suspect you want these to be private _inAppMode?
shauvik
2011/12/16 07:19:27
Done.
| |
16 DOMWindow win, appWindow; | |
Kelly Norton
2011/12/15 18:35:31
private?
shauvik
2011/12/16 07:19:27
Done.
| |
17 | |
18 DARTest(){ | |
19 win = window; | |
Kelly Norton
2011/12/15 18:35:31
It seems unnecessary to store a reference to windo
shauvik
2011/12/16 07:19:27
Done.
| |
20 appWindow = window; | |
21 dartestLoggingFunc = log; | |
22 inAppMode = true; | |
23 inAppElements = new HashMap<String, HTMLElement>(); | |
24 appElements = inAppElements; | |
25 | |
26 DARTestCss.inject(win.document, true); | |
Kelly Norton
2011/12/15 18:35:31
document should also be available as a top-level g
shauvik
2011/12/16 07:19:27
Done.
| |
27 } | |
28 | |
29 void run() { | |
30 renderMain(); | |
31 createResultsTable(); | |
32 } | |
33 | |
34 void log(String message){ | |
pdr
2011/12/14 21:02:36
Log calls should generally be behind a flag at wor
shauvik
2011/12/16 07:19:27
Whole of dartest is behind a compiler flag. In dis
| |
35 appWindow.console.log(message); | |
36 } | |
37 | |
38 void addChildToElem(String key, HTMLElement child) { | |
Kelly Norton
2011/12/15 18:35:31
This seems like a sizeable public API. Are all of
shauvik
2011/12/16 07:19:27
Made it private.
On 2011/12/15 18:35:31, Kelly No
| |
39 if(appElements[key] != null) { | |
40 appElements[key].appendChild(child); | |
41 } | |
42 } | |
43 | |
44 /** Create the results table after loading tests */ | |
45 void createResultsTable(){ | |
46 log('Creating results table'); | |
47 HTMLTableElement table = win.document.createElement('table'); | |
Kelly Norton
2011/12/15 18:35:31
I'm really confused by all the references to diffe
shauvik
2011/12/16 07:19:27
Done.
| |
48 table.className = 'dt-results'; | |
49 HTMLTableSectionElement head = win.document.createElement('thead'); | |
50 head.innerHTML = '<tr><th>ID <th>Description <th>Result'; | |
51 table.appendChild(head); | |
52 | |
53 HTMLTableSectionElement body = win.document.createElement('tbody'); | |
54 body.id = 'dt-results-body'; | |
55 body.innerHTML = ''; | |
56 for (TestCase t in tests){ | |
57 body.innerHTML += "<tr id='dt-test-${t.id}'>" + getTestDetails(t); | |
58 body.innerHTML += "<tr id='dt-detail-${t.id}' class='dt-hide'></span>"; | |
59 } | |
60 table.appendChild(body); | |
61 addChildToElem('testBody', table); | |
62 } | |
63 | |
64 /** Update the results table for test */ | |
65 void updateResultsTable(TestCase t, DOMWindow domWin){ | |
66 HTMLTableRowElement row = domWin.document.getElementById('dt-test-${t.id}'); | |
67 row.className = 'dt-result-row'; | |
68 row.innerHTML = getTestDetails(t); | |
69 | |
70 HTMLTableRowElement details = domWin.document.getElementById('dt-detail-${t. id}'); | |
pdr
2011/12/14 21:02:36
Line > 100 chars
shauvik
2011/12/16 07:19:27
Done.
| |
71 details.innerHTML = getTestStats(t); | |
72 | |
73 row.addEventListener('click', (Event e) { | |
74 if(details.className == 'dt-hide') { | |
75 details.className = ''; | |
76 } else { | |
77 details.className = 'dt-hide'; | |
78 } | |
79 }, true); | |
80 } | |
81 | |
82 /** Escape HTML Special Chars */ | |
83 String escape(String str){ | |
84 str = str.replaceAll('&','&'); | |
85 str = str.replaceAll('<','<'); | |
86 str = str.replaceAll('>','>'); | |
87 str = str.replaceAll('"','"'); | |
88 str = str.replaceAll("'",'''); | |
89 str = str.replaceAll('/','/'); | |
90 return str; | |
91 } | |
92 | |
93 /** Get test results as table cells */ | |
pdr
2011/12/14 21:02:36
This can be private so you can rename it using the
shauvik
2011/12/16 07:19:27
Done.
| |
94 String getTestDetails(TestCase t) { | |
pdr
2011/12/14 21:02:36
Instead of building up this table by strings, I wo
pdr
2011/12/14 21:02:36
Without escaping and innerHTML-based DOM manipulat
shauvik
2011/12/16 07:19:27
Done.
shauvik
2011/12/16 07:19:27
Actually I need that value in the class too. So, c
| |
95 String desc = escape(t.description); | |
96 String result = "none"; | |
97 if(t.result != null){ | |
98 result = t.result; | |
99 } | |
100 return "<td>${t.id} <td>$desc <td class='dt-$result' " + | |
101 "title='${escape(t.message)}'> ${result.toUpperCase()}"; | |
102 } | |
103 | |
104 String getTestStats(TestCase t) { | |
pdr
2011/12/14 21:02:36
See comments in getTestDetails
shauvik
2011/12/16 07:19:27
Done.
| |
105 String message = ''; | |
106 if(t.message != '') { | |
107 message = t.message + '<br>'; | |
Kelly Norton
2011/12/15 18:35:31
This looks problematic. where does t.message get h
shauvik
2011/12/16 07:19:27
Done.
| |
108 } | |
109 if(t.stackTrace != null) { | |
110 message += '<pre>${t.stackTrace}</pre>'; | |
Kelly Norton
2011/12/15 18:35:31
Similar here, I don't see where t.stackTrace is es
shauvik
2011/12/16 07:19:27
Done.
| |
111 } | |
112 return "<td colspan='3'> $message took ${printDuration(t.timeDuration)}</td> "; | |
pdr
2011/12/14 21:02:36
Line > 100 cols
shauvik
2011/12/16 07:19:27
Done.
| |
113 } | |
114 | |
115 /** Update the UI after running test */ | |
pdr
2011/12/14 21:02:36
Nit: make sure to use a period in your comments :)
shauvik
2011/12/16 07:19:27
Done.
| |
116 void updateDARTestUI(TestCase test){ | |
Kelly Norton
2011/12/15 18:35:31
space before {
shauvik
2011/12/16 07:19:27
Done.
| |
117 // Update table | |
pdr
2011/12/14 21:02:36
unnecessary comment
shauvik
2011/12/16 07:19:27
Done.
| |
118 updateResultsTable(test, win); | |
119 if(!inAppMode) { | |
120 updateResultsTable(test, appWindow); | |
121 } | |
122 | |
123 if(test.result != null) { | |
124 log(' Result: ${test.result.toUpperCase()} ${test.message}'); | |
125 } | |
126 if(test.timeDuration != null){ | |
127 log(' took ${printDuration(test.timeDuration)}'); | |
128 } | |
129 updateStatusProgress(appElements); | |
130 if(!inAppMode) { | |
131 updateStatusProgress(inAppElements); | |
132 } | |
133 } | |
134 | |
135 void updateStatusProgress(Map<String, HTMLElement> elements){ | |
Kelly Norton
2011/12/15 18:35:31
space before {
shauvik
2011/12/16 07:19:27
Done.
| |
136 // Update progressbar | |
137 var pPass = ((numTestsRun - numTestsFailed - numTestsErrors) / tests.length) * 100; | |
pdr
2011/12/14 21:02:36
Line > 100 cols (here and elsewhere).
shauvik
2011/12/16 07:19:27
Done.
| |
138 elements['green'].setAttribute('style', 'width:$pPass%'); | |
Kelly Norton
2011/12/15 18:35:31
Why do you use a map to store references to things
shauvik
2011/12/16 07:19:27
Actually everything is stored in maps. We need to
Kelly Norton
2011/12/16 19:48:13
I'm just saying that a map is definitely not the b
| |
139 var pFailed = pPass + (numTestsFailed / tests.length) * 100; | |
140 elements['red'].setAttribute('style', 'width:$pFailed%'); | |
141 var pErrors = pFailed + (numTestsErrors / tests.length) * 100; | |
142 elements['orange'].setAttribute('style', 'width:$pErrors%'); | |
143 | |
144 // Update status | |
145 elements['testsRunElem'].innerText = '$numTestsRun'; | |
pdr
2011/12/14 21:02:36
There's no need to do this for your strings. Just
Kelly Norton
2011/12/15 18:35:31
You want to use textContent instead of innerText a
shauvik
2011/12/16 07:19:27
Yes, I did that to get rid of int-to-string warnin
shauvik
2011/12/16 07:19:27
Cool, Thanks for the tip. I didn't know innerText
| |
146 elements['testsFailedElem'].innerText = '$numTestsFailed'; | |
147 elements['testsErrorsElem'].innerText = '$numTestsErrors'; | |
148 } | |
149 | |
150 String printDuration(Duration timeDuration) { | |
151 StringBuffer out = new StringBuffer(); | |
152 if(timeDuration.inDays > 0){ | |
153 out.add('${timeDuration.inDays} days '); | |
154 } | |
155 if(timeDuration.inHours > 0){ | |
156 out.add('${timeDuration.inHours} hrs '); | |
157 } | |
158 if(timeDuration.inMinutes > 0){ | |
159 out.add('${timeDuration.inMinutes} mins '); | |
160 } | |
161 if(timeDuration.inSeconds > 0){ | |
162 out.add('${timeDuration.inSeconds} s '); | |
163 } | |
164 if(timeDuration.inMilliseconds > 0 || out.length == 0){ | |
165 out.add('${timeDuration.inMilliseconds} ms'); | |
166 } | |
167 return out.toString(); | |
168 } | |
169 | |
170 /** Populates the floating div with controls and toolbar */ | |
171 HTMLDivElement renderMain(){ | |
172 HTMLDivElement containerDiv = win.document.createElement('div'); | |
173 containerDiv.className = 'dt-container'; | |
174 appElements['containerDiv'] = containerDiv; | |
175 | |
176 // Add the test controls | |
177 HTMLDivElement mainElem = win.document.createElement('div'); | |
178 mainElem.className = 'dt-main'; | |
179 appElements['mainElem'] = mainElem; | |
180 | |
181 showTestControls(); | |
182 | |
183 // Create header to hold window controls | |
184 if(inAppMode) { | |
185 HTMLDivElement headDiv = win.document.createElement('div'); | |
186 headDiv.className = 'dt-header'; | |
187 headDiv.innerHTML = '<b>DARTest: In-App View</b>'; | |
188 HTMLImageElement close = win.document.createElement('img'); | |
189 close.className = 'dt-header-close'; | |
190 close.addEventListener('click', (Event){ | |
191 containerDiv.className = 'dt-hide'; | |
192 }, true); | |
193 HTMLImageElement pop = win.document.createElement('img'); | |
194 pop.className = 'dt-header-pop'; | |
195 pop.addEventListener('click', (Event) => dartestMaximize(), true); | |
196 HTMLImageElement minMax = win.document.createElement('img'); | |
197 minMax.className = 'dt-header-min'; | |
198 minMax.addEventListener('click', (Event) { | |
199 if (mainElem.classList.contains('dt-hide')){ | |
200 mainElem.classList.remove('dt-hide'); | |
201 mainElem.classList.add('dt-show'); | |
202 minMax.className = 'dt-header-min'; | |
203 } else { | |
204 if (mainElem.classList.contains('dt-show')){ | |
205 mainElem.classList.remove('dt-show'); | |
206 } | |
207 mainElem.classList.add('dt-hide'); | |
208 minMax.className = 'dt-header-max'; | |
209 } | |
210 }, true); | |
211 headDiv.appendChild(close); | |
212 headDiv.appendChild(pop); | |
213 headDiv.appendChild(minMax); | |
214 | |
215 containerDiv.appendChild(headDiv); | |
216 } | |
217 | |
218 HTMLDivElement tabDiv = win.document.createElement('div'); | |
219 tabDiv.className = 'dt-tab'; | |
220 HTMLUListElement tabList = win.document.createElement('ul'); | |
221 HTMLLIElement testingTab = win.document.createElement('li'); | |
222 HTMLLIElement coverageTab = win.document.createElement('li'); | |
223 testingTab.className = 'dt-tab-selected'; | |
224 testingTab.innerText = 'Testing'; | |
225 testingTab.addEventListener('click', (Event) { | |
226 showTestControls(); | |
227 changeTabs(testingTab, coverageTab); | |
228 }, true); | |
229 tabList.appendChild(testingTab); | |
230 coverageTab.innerText = 'Coverage'; | |
231 coverageTab.addEventListener('click', (Event) { | |
232 showCoverageControls(); | |
233 changeTabs(coverageTab, testingTab); | |
234 }, true); | |
235 tabList.appendChild(coverageTab); | |
236 tabDiv.appendChild(tabList); | |
237 containerDiv.appendChild(tabDiv); | |
238 | |
239 if(!inAppMode) { | |
240 HTMLDivElement popIn = win.document.createElement('div'); | |
241 popIn.className = 'dt-minimize'; | |
242 popIn.innerHTML = 'Pop In ⇲'; | |
243 popIn.addEventListener('click', (Event) => dartestMinimize(), true); | |
244 containerDiv.appendChild(popIn); | |
245 } | |
246 | |
247 containerDiv.appendChild(mainElem); | |
248 win.document.body.appendChild(containerDiv); | |
249 } | |
250 | |
251 void changeTabs(HTMLLIElement clickedTab, HTMLLIElement oldTab){ | |
252 oldTab.className = ''; | |
253 clickedTab.className = 'dt-tab-selected'; | |
254 } | |
255 | |
256 void showTestControls(){ | |
257 HTMLDivElement testBody = appElements['testBody']; | |
258 if(testBody == null){ | |
259 testBody = win.document.createElement('div'); | |
260 appElements['testBody'] = testBody; | |
261 | |
262 // Create a toolbar to hold action buttons | |
263 HTMLDivElement toolDiv = win.document.createElement('div'); | |
264 toolDiv.className = 'dt-toolbar'; | |
265 HTMLButtonElement runBtn = win.document.createElement('button'); | |
266 runBtn.innerHTML = '►'; | |
267 runBtn.title = 'Run Tests'; | |
268 runBtn.className = 'dt-button dt-run'; | |
269 runBtn.addEventListener('click', (Event) { | |
270 log('Running tests'); | |
271 uiUpdateFunc = updateDARTestUI; | |
272 runDARTests(); | |
273 }, true); | |
274 toolDiv.appendChild(runBtn); | |
275 HTMLButtonElement exportBtn = win.document.createElement('button'); | |
276 exportBtn.innerHTML = '↷'; | |
277 exportBtn.title = 'Export Results'; | |
278 exportBtn.className = 'dt-button dt-run'; | |
279 exportBtn.addEventListener('click', (Event e) { | |
280 log('Exporting results'); | |
281 exportTestResults(); | |
282 }, true); | |
283 toolDiv.appendChild(exportBtn); | |
284 testBody.appendChild(toolDiv); | |
285 | |
286 // Create a datalist element for showing test status"<td class='dt-pass'> PASS"; | |
287 HTMLDListElement statList = win.document.createElement('dl'); | |
288 statList.className = 'dt-status'; | |
289 HTMLElement runsDt = win.document.createElement('dt'); | |
290 runsDt.innerText = 'Runs:'; | |
291 statList.appendChild(runsDt); | |
292 HTMLElement testsRunElem = win.document.createElement('dd'); | |
293 appElements['testsRunElem'] = testsRunElem; | |
294 testsRunElem.innerText = '$numTestsRun'; | |
295 statList.appendChild(testsRunElem); | |
296 | |
297 HTMLElement failDt = win.document.createElement('dt'); | |
298 failDt.innerText = 'Failed:'; | |
299 statList.appendChild(failDt); | |
300 HTMLElement testsFailedElem = win.document.createElement('dd'); | |
301 appElements['testsFailedElem'] = testsFailedElem; | |
302 testsFailedElem.innerText = '$numTestsFailed'; | |
303 statList.appendChild(testsFailedElem); | |
304 | |
305 HTMLElement errDt = win.document.createElement('dt'); | |
306 errDt.innerText = 'Errors:'; | |
307 statList.appendChild(errDt); | |
308 HTMLElement testsErrorsElem = win.document.createElement('dd'); | |
309 appElements['testsErrorsElem'] = testsErrorsElem; | |
310 testsErrorsElem.innerText = '$numTestsErrors'; | |
311 statList.appendChild(testsErrorsElem); | |
312 testBody.appendChild(statList); | |
313 | |
314 // Create progressbar and add red, green, orange bars | |
315 HTMLDivElement progressDiv = win.document.createElement('div'); | |
316 progressDiv.className = 'dt-progressbar'; | |
317 progressDiv.innerHTML = "<span style='width:100%'></span>"; | |
318 | |
319 HTMLSpanElement orange = win.document.createElement('span'); | |
320 appElements['orange'] = orange; | |
321 orange.className = 'orange'; | |
322 progressDiv.appendChild(orange); | |
323 | |
324 HTMLSpanElement red = win.document.createElement('span'); | |
325 appElements['red'] = red; | |
326 red.className = 'red'; | |
327 progressDiv.appendChild(red); | |
328 | |
329 HTMLSpanElement green = win.document.createElement('span'); | |
330 appElements['green'] = green; | |
331 green.className = 'green'; | |
332 | |
333 progressDiv.appendChild(green); | |
334 testBody.appendChild(progressDiv); | |
335 | |
336 HTMLDivElement hiddenElem = win.document.createElement('div'); | |
337 hiddenElem.className = 'dt-hide'; | |
338 hiddenElem.innerHTML = "<a id='dt-export' download='test_results.csv' href ='#' />"; | |
339 testBody.appendChild(hiddenElem); | |
340 | |
341 addChildToElem('mainElem', testBody); | |
342 } | |
343 | |
344 // Show hide divs | |
345 showHide('testBody', 'coverageBody'); | |
346 } | |
347 | |
348 void showCoverageControls(){ | |
349 HTMLDivElement coverageBody = appElements['coverageBody']; | |
350 if(coverageBody == null){ | |
351 coverageBody = win.document.createElement('div'); | |
352 appElements['coverageBody'] = coverageBody; | |
353 | |
354 HTMLPreElement covPreElem = win.document.createElement('pre'); | |
355 appElements['covPreElem'] = covPreElem; | |
356 coverageBody.appendChild(covPreElem); | |
357 | |
358 HTMLTableElement covTable = win.document.createElement('table'); | |
359 covTable.className = 'dt-results'; | |
360 HTMLTableSectionElement head = win.document.createElement('thead'); | |
361 head.innerHTML = '<tr><th>Unit <th>Function <th>Statement <th>Branch'; | |
362 covTable.appendChild(head); | |
363 HTMLTableSectionElement covTableBody = win.document.createElement('tbody') ; | |
364 appElements['covTableBody'] = covTableBody; | |
365 covTableBody.id = 'dt-results-body'; | |
366 covTable.appendChild(covTableBody); | |
367 coverageBody.appendChild(covTable); | |
368 | |
369 addChildToElem('mainElem', coverageBody); | |
370 } | |
371 showHide('coverageBody', 'testBody'); | |
372 | |
373 appElements['covPreElem'].innerText = getCoverageSummary(); | |
374 appElements['covTableBody'].innerHTML = getCoverageDetails(); | |
375 } | |
376 | |
377 void showHide(String toShow, String toHide) { | |
pdr
2011/12/14 21:02:36
I'd split this into show and hide.
shauvik
2011/12/16 07:19:27
Done.
| |
378 HTMLElement show = appElements[toShow]; | |
379 HTMLElement hide = appElements[toHide]; | |
380 if(show != null){ | |
381 if(show.classList.contains('dt-hide')) { | |
382 show.classList.remove('dt-hide'); | |
383 } | |
384 show.classList.add('dt-show'); | |
385 } | |
386 if(hide != null){ | |
387 if(hide.classList.contains('dt-show')){ | |
388 hide.classList.remove('dt-show'); | |
389 } | |
390 hide.classList.add('dt-hide'); | |
391 } | |
392 } | |
393 | |
394 void dartestMaximize(){ | |
395 showHide('', 'containerDiv'); | |
396 win = win.open('', 'dartest-window', 'width=600,height=750'); | |
397 log('Window:'+win.toString()); | |
pdr
2011/12/14 21:02:36
I would remove these logging calls, or put them be
shauvik
2011/12/16 07:19:27
Done.
| |
398 win.document.title = 'Dartest'; | |
399 inAppMode = false; | |
400 fullAppElements = new HashMap<String, HTMLElement>(); | |
401 appElements = fullAppElements; | |
402 DARTestCss.inject(win.document, false); | |
403 run(); | |
404 if(numTestsRun > 0) { | |
405 populateResults(); | |
406 } | |
407 } | |
408 | |
409 void populateResults() { | |
pdr
2011/12/14 21:02:36
I think this can be:
tests.forEach(TestCase t) =>
shauvik
2011/12/16 07:19:27
Thanks. Its slightly different that what you propo
| |
410 for(TestCase t in tests) { | |
411 updateDARTestUI(t); | |
412 } | |
413 } | |
414 | |
415 void dartestMinimize(){ | |
416 win.close(); | |
417 win = appWindow; | |
418 inAppMode = true; | |
419 appElements = inAppElements; | |
420 showHide('containerDiv', ''); | |
421 } | |
422 | |
423 void exportTestResults(){ | |
424 String csvData = getTestResultsCSV(); | |
425 log(csvData); | |
426 HTMLAnchorElement exportLink = win.document.getElementById('dt-export'); | |
427 | |
428 /** Bug: Can't instantiate WebKitBlobBuilder | |
429 * If this bug is fixed, we can remove the urlencode and lpad function. | |
430 WebKitBlobBuilder bb = new WebKitBlobBuilder(); | |
431 bb.append(csvData); | |
432 Blob blob = bb.getBlob('text/plain;charset=${document.characterSet}'); | |
433 exportLink.href = window.webkitURL.createObjectURL(blob); | |
434 **/ | |
435 | |
436 exportLink.href = 'data:text/csv,'+urlencode(csvData); | |
437 | |
438 MouseEvent ev = document.createEvent("MouseEvents"); | |
439 ev.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0 | |
440 , false, false, false, false, 0, null); | |
441 exportLink.dispatchEvent(ev); | |
442 | |
443 } | |
444 | |
445 String urlencode(String s) { | |
Kelly Norton
2011/12/15 18:35:31
How is the different from the native encodeURI?
shauvik
2011/12/16 07:19:27
Actually I think encodeURI isn't supported in dart
| |
446 StringBuffer out = new StringBuffer(); | |
447 for(int i=0;i<s.length;i++) { | |
448 int cc = s.charCodeAt(i); | |
449 if((cc>=48 && cc<=57) || (cc>=65 && cc<=90) || (cc>=97 && cc<=122)) { | |
450 out.add(s[i]); | |
451 } else { | |
452 out.add('%${lpad(cc.toRadixString(16),2).toUpperCase()}'); | |
453 } | |
454 } | |
455 return out.toString(); | |
456 } | |
457 | |
458 String lpad(String s, int n) { | |
459 if(s.length<n) { | |
Kelly Norton
2011/12/15 18:35:31
s.length < n
Lots of other space problems in this
shauvik
2011/12/16 07:19:27
Done.
| |
460 for(int i=0;i<n-s.length;i++) { | |
461 s = '0'+s; | |
462 } | |
463 } | |
464 return s; | |
465 } | |
466 } | |
OLD | NEW |