Index: client/testing/dartest/dartest.dart |
=================================================================== |
--- client/testing/dartest/dartest.dart (revision 0) |
+++ client/testing/dartest/dartest.dart (revision 0) |
@@ -0,0 +1,496 @@ |
+// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+#library('dartest'); |
+ |
+#import('dart:dom'); |
+#import('../unittest/unittest_dartest.dart'); |
+ |
+#source('css.dart'); |
+ |
+/** DARTest provides a library to run tests in the app. */ |
+class DARTest{ |
+ Map<String, HTMLElement> _inAppElements, _fullAppElements, _appElements; |
+ DOMWindow _runnerWindow; |
+ |
+ DARTest() { |
+ _runnerWindow = window; |
+ dartestLogger = _log; |
+ _inAppElements = new HashMap<String, HTMLElement>(); |
+ _appElements = _inAppElements; |
+ DARTestCss.inject(document, true); |
+ } |
+ |
+ void run() { |
+ _renderMain(); |
+ _createResultsTable(); |
+ } |
+ |
+ void _log(String message) { |
+ _runnerWindow.console.log(message); |
+ } |
+ |
+ void _addChildToElem(String key, HTMLElement child) { |
+ if(_appElements[key] != null) { |
+ _appElements[key].appendChild(child); |
+ } |
+ } |
+ |
+ /** Create the results table after loading tests. */ |
+ void _createResultsTable() { |
+ _log('Creating results table'); |
+ HTMLTableElement table = _runnerWindow.document.createElement('table'); |
+ table.className = 'dt-results'; |
+ HTMLTableSectionElement head = _runnerWindow.document.createElement('thead'); |
+ head.innerHTML = '<tr><th>ID <th>Description <th>Result'; |
+ table.appendChild(head); |
+ |
+ HTMLTableSectionElement body = _runnerWindow.document.createElement('tbody'); |
+ body.id = 'dt-results-body'; |
+ tests.forEach((final t) { |
+ HTMLTableRowElement testDetailRow = _runnerWindow.document.createElement('tr'); |
+ testDetailRow.id = 'dt-test-${t.id}'; |
+ _addTestDetails(t, testDetailRow); |
+ body.appendChild(testDetailRow); |
+ |
+ HTMLTableRowElement testMessageRow = _runnerWindow.document.createElement('tr'); |
+ testMessageRow.id = 'dt-detail-${t.id}'; |
+ testMessageRow.className = 'dt-hide'; |
+ body.appendChild(testMessageRow); |
+ }); |
+ |
+ table.appendChild(body); |
+ _addChildToElem('testBody', table); |
+ } |
+ |
+ /** Update the results table for test. */ |
+ void _updateResultsTable(TestCase t, DOMWindow domWin) { |
+ HTMLTableRowElement row = domWin.document.getElementById('dt-test-${t.id}'); |
+ row.className = 'dt-result-row'; |
+ row.innerHTML = ''; // Remove all children as we will re-populate them |
+ _addTestDetails(t, row); |
+ |
+ HTMLTableRowElement details = |
+ domWin.document.getElementById('dt-detail-${t.id}'); |
+ details.appendChild(_getTestStats(t, domWin)); |
+ |
+ row.addEventListener('click', (Event e) { |
+ if(details.className == 'dt-hide') { |
+ details.className = ''; |
+ } else { |
+ details.className = 'dt-hide'; |
+ } |
+ }, true); |
+ } |
+ |
+ /** Escape HTML special chars. */ |
+ String _escape(String str) { |
+ str = str.replaceAll('&','&'); |
+ str = str.replaceAll('<','<'); |
+ str = str.replaceAll('>','>'); |
+ str = str.replaceAll('"','"'); |
+ str = str.replaceAll("'",'''); |
+ str = str.replaceAll('/','/'); |
+ return str; |
+ } |
+ |
+ /** Get test results as table cells. */ |
+ void _addTestDetails(TestCase t, HTMLTableRowElement row) { |
+ |
pdr
2011/12/16 17:00:50
Extra space
shauvik
2011/12/16 17:57:06
Done.
|
+ HTMLTableCellElement testId = _runnerWindow.document.createElement('td'); |
+ testId.textContent = t.id; |
+ row.appendChild(testId); |
+ |
+ HTMLTableCellElement testDesc = _runnerWindow.document.createElement('td'); |
+ testDesc.textContent = t.description; |
+ row.appendChild(testDesc); |
+ |
+ HTMLTableCellElement testResult = _runnerWindow.document.createElement('td'); |
+ String result = (t.result == null) ? 'none' : _escape(t.result); |
+ testResult.className = 'dt-$result'; |
+ testResult.title = '${_escape(t.message)}'; |
+ testResult.textContent = result.toUpperCase(); |
+ row.appendChild(testResult); |
+ } |
+ |
+ HTMLTableCellElement _getTestStats(TestCase t, DOMWindow domWin) { |
+ HTMLTableCellElement tableCell = domWin.document.createElement('td'); |
+ tableCell.colSpan = 3; |
+ |
+ if(t.message != '') { |
+ HTMLSpanElement messageSpan = domWin.document.createElement('span'); |
+ messageSpan.textContent = t.message; |
+ tableCell.appendChild(messageSpan); |
+ tableCell.appendChild(domWin.document.createElement('br')); |
+ } |
+ if(t.stackTrace != null) { |
+ HTMLPreElement stackTacePre = domWin.document.createElement('pre'); |
+ stackTacePre.textContent = t.stackTrace; |
+ } |
+ |
+ HTMLSpanElement durationSpan = domWin.document.createElement('span'); |
+ durationSpan.textContent = 'took ${_printDuration(t.runningTime)}'; |
+ tableCell.appendChild(durationSpan); |
+ |
+ return tableCell; |
+ } |
+ |
+ /** Update the UI after running test. */ |
+ void _updateDARTestUI(TestCase test) { |
+ _updateResultsTable(test, window); |
+ if(_runnerWindow != window) { |
+ _updateResultsTable(test, _runnerWindow); |
+ } |
+ |
+ if(test.result != null) { |
+ _log(' Result: ${test.result.toUpperCase()} ${test.message}'); |
+ } |
+ if(test.runningTime != null) { |
+ _log(' took ${_printDuration(test.runningTime)}'); |
+ } |
+ _updateStatusProgress(_appElements); |
+ if(_runnerWindow != window) { |
+ _updateStatusProgress(_inAppElements); |
+ } |
+ } |
+ |
+ void _updateStatusProgress(Map<String, HTMLElement> elements) { |
+ // Update progressbar |
+ var pPass = |
+ ((testsRun - testsFailed - testsErrors) / tests.length) * 100; |
+ elements['green'].setAttribute('style', 'width:$pPass%'); |
+ var pFailed = pPass + (testsFailed / tests.length) * 100; |
+ elements['red'].setAttribute('style', 'width:$pFailed%'); |
+ var pErrors = pFailed + (testsErrors / tests.length) * 100; |
+ elements['orange'].setAttribute('style', 'width:$pErrors%'); |
+ |
+ // Update status |
+ elements['testsRunElem'].textContent = testsRun.toString(); |
+ elements['testsFailedElem'].textContent = testsFailed.toString(); |
+ elements['testsErrorsElem'].textContent = testsErrors.toString(); |
+ } |
+ |
+ String _printDuration(Duration timeDuration) { |
+ StringBuffer out = new StringBuffer(); |
+ if(timeDuration.inDays > 0) { |
+ out.add('${timeDuration.inDays} days '); |
+ } |
+ if(timeDuration.inHours > 0) { |
+ out.add('${timeDuration.inHours} hrs '); |
+ } |
+ if(timeDuration.inMinutes > 0) { |
+ out.add('${timeDuration.inMinutes} mins '); |
+ } |
+ if(timeDuration.inSeconds > 0) { |
+ out.add('${timeDuration.inSeconds} s '); |
+ } |
+ if(timeDuration.inMilliseconds > 0 || out.length == 0) { |
+ out.add('${timeDuration.inMilliseconds} ms'); |
+ } |
+ return out.toString(); |
+ } |
+ |
+ /** Populates the floating div with controls and toolbar. */ |
+ HTMLDivElement _renderMain() { |
+ HTMLDivElement containerDiv = _runnerWindow.document.createElement('div'); |
+ containerDiv.className = 'dt-container'; |
+ _appElements['containerDiv'] = containerDiv; |
+ |
+ // Add the test controls |
+ HTMLDivElement mainElem = _runnerWindow.document.createElement('div'); |
+ mainElem.className = 'dt-main'; |
+ _appElements['mainElem'] = mainElem; |
+ |
+ _showTestControls(); |
+ |
+ // Create header to hold window controls |
+ if(_runnerWindow == window) { |
+ HTMLDivElement headDiv = _runnerWindow.document.createElement('div'); |
+ headDiv.className = 'dt-header'; |
+ 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
|
+ HTMLImageElement close = _runnerWindow.document.createElement('img'); |
+ close.className = 'dt-header-close'; |
+ close.addEventListener('click', (Event) { |
+ containerDiv.className = 'dt-hide'; |
+ }, true); |
+ HTMLImageElement pop = _runnerWindow.document.createElement('img'); |
+ pop.className = 'dt-header-pop'; |
+ pop.addEventListener('click', (Event) => dartestMaximize(), true); |
+ HTMLImageElement minMax = _runnerWindow.document.createElement('img'); |
+ minMax.className = 'dt-header-min'; |
+ minMax.addEventListener('click', (Event) { |
+ if (mainElem.classList.contains('dt-hide')) { |
+ mainElem.classList.remove('dt-hide'); |
+ mainElem.classList.add('dt-show'); |
+ minMax.className = 'dt-header-min'; |
+ } else { |
+ if (mainElem.classList.contains('dt-show')) { |
+ mainElem.classList.remove('dt-show'); |
+ } |
+ mainElem.classList.add('dt-hide'); |
+ minMax.className = 'dt-header-max'; |
+ } |
+ }, true); |
+ headDiv.appendChild(close); |
+ headDiv.appendChild(pop); |
+ headDiv.appendChild(minMax); |
+ |
+ containerDiv.appendChild(headDiv); |
+ } |
+ |
+ HTMLDivElement tabDiv = _runnerWindow.document.createElement('div'); |
+ tabDiv.className = 'dt-tab'; |
+ HTMLUListElement tabList = _runnerWindow.document.createElement('ul'); |
+ HTMLLIElement testingTab = _runnerWindow.document.createElement('li'); |
+ HTMLLIElement coverageTab = _runnerWindow.document.createElement('li'); |
+ testingTab.className = 'dt-tab-selected'; |
+ testingTab.textContent = 'Testing'; |
+ testingTab.addEventListener('click', (Event) { |
+ _showTestControls(); |
+ _changeTabs(testingTab, coverageTab); |
+ }, true); |
+ tabList.appendChild(testingTab); |
+ coverageTab.textContent = 'Coverage'; |
+ coverageTab.addEventListener('click', (Event) { |
+ _showCoverageControls(); |
+ _changeTabs(coverageTab, testingTab); |
+ }, true); |
+ tabList.appendChild(coverageTab); |
+ tabDiv.appendChild(tabList); |
+ containerDiv.appendChild(tabDiv); |
+ |
+ if(_runnerWindow != window) { |
+ HTMLDivElement popIn = _runnerWindow.document.createElement('div'); |
+ popIn.className = 'dt-minimize'; |
+ popIn.innerHTML = 'Pop In ⇲'; |
+ popIn.addEventListener('click', (Event) => dartestMinimize(), true); |
+ containerDiv.appendChild(popIn); |
+ } |
+ |
+ containerDiv.appendChild(mainElem); |
+ _runnerWindow.document.body.appendChild(containerDiv); |
+ } |
+ |
+ void _changeTabs(HTMLLIElement clickedTab, HTMLLIElement oldTab) { |
+ oldTab.className = ''; |
+ clickedTab.className = 'dt-tab-selected'; |
+ } |
+ |
+ void _showTestControls() { |
+ HTMLDivElement testBody = _appElements['testBody']; |
+ if(testBody == null) { |
+ testBody = _runnerWindow.document.createElement('div'); |
+ _appElements['testBody'] = testBody; |
+ |
+ // Create a toolbar to hold action buttons |
+ HTMLDivElement toolDiv = _runnerWindow.document.createElement('div'); |
+ toolDiv.className = 'dt-toolbar'; |
+ HTMLButtonElement runBtn = _runnerWindow.document.createElement('button'); |
+ runBtn.innerHTML = '►'; |
+ runBtn.title = 'Run Tests'; |
+ runBtn.className = 'dt-button dt-run'; |
+ runBtn.addEventListener('click', (Event) { |
+ _log('Running tests'); |
+ updateUI = _updateDARTestUI; |
+ runDartests(); |
+ }, true); |
+ toolDiv.appendChild(runBtn); |
+ HTMLButtonElement exportBtn = |
+ _runnerWindow.document.createElement('button'); |
+ exportBtn.innerHTML = '↷'; |
+ exportBtn.title = 'Export Results'; |
+ exportBtn.className = 'dt-button dt-run'; |
+ exportBtn.addEventListener('click', (Event e) { |
+ _log('Exporting results'); |
+ exportTestResults(); |
+ }, true); |
+ toolDiv.appendChild(exportBtn); |
+ testBody.appendChild(toolDiv); |
+ |
+ // Create a datalist element for showing test status |
+ HTMLDListElement statList = _runnerWindow.document.createElement('dl'); |
+ statList.className = 'dt-status'; |
+ HTMLElement runsDt = _runnerWindow.document.createElement('dt'); |
+ runsDt.textContent = 'Runs:'; |
+ statList.appendChild(runsDt); |
+ HTMLElement testsRunElem = _runnerWindow.document.createElement('dd'); |
+ _appElements['testsRunElem'] = testsRunElem; |
+ testsRunElem.textContent = testsRun.toString(); |
+ statList.appendChild(testsRunElem); |
+ |
+ HTMLElement failDt = _runnerWindow.document.createElement('dt'); |
+ failDt.textContent = 'Failed:'; |
+ statList.appendChild(failDt); |
+ HTMLElement testsFailedElem = _runnerWindow.document.createElement('dd'); |
+ _appElements['testsFailedElem'] = testsFailedElem; |
+ testsFailedElem.textContent = testsFailed.toString(); |
+ statList.appendChild(testsFailedElem); |
+ |
+ HTMLElement errDt = _runnerWindow.document.createElement('dt'); |
+ errDt.textContent = 'Errors:'; |
+ statList.appendChild(errDt); |
+ HTMLElement testsErrorsElem = _runnerWindow.document.createElement('dd'); |
+ _appElements['testsErrorsElem'] = testsErrorsElem; |
+ testsErrorsElem.textContent = testsErrors.toString(); |
+ statList.appendChild(testsErrorsElem); |
+ testBody.appendChild(statList); |
+ |
+ // Create progressbar and add red, green, orange bars |
+ HTMLDivElement progressDiv = _runnerWindow.document.createElement('div'); |
+ progressDiv.className = 'dt-progressbar'; |
+ progressDiv.innerHTML = "<span style='width:100%'></span>"; |
+ |
+ HTMLSpanElement orange = _runnerWindow.document.createElement('span'); |
+ _appElements['orange'] = orange; |
+ orange.className = 'orange'; |
+ progressDiv.appendChild(orange); |
+ |
+ HTMLSpanElement red = _runnerWindow.document.createElement('span'); |
+ _appElements['red'] = red; |
+ red.className = 'red'; |
+ progressDiv.appendChild(red); |
+ |
+ HTMLSpanElement green = _runnerWindow.document.createElement('span'); |
+ _appElements['green'] = green; |
+ green.className = 'green'; |
+ |
+ progressDiv.appendChild(green); |
+ testBody.appendChild(progressDiv); |
+ |
+ HTMLDivElement hiddenElem = _runnerWindow.document.createElement('div'); |
+ hiddenElem.className = 'dt-hide'; |
+ hiddenElem.innerHTML = |
+ "<a id='dt-export' download='test_results.csv' href='#' />"; |
+ testBody.appendChild(hiddenElem); |
+ |
+ _addChildToElem('mainElem', testBody); |
+ } |
+ |
+ // Show hide divs |
+ _show('testBody'); |
+ _hide('coverageBody'); |
+ } |
+ |
+ void _showCoverageControls() { |
+ HTMLDivElement coverageBody = _appElements['coverageBody']; |
+ if(coverageBody == null) { |
+ coverageBody = _runnerWindow.document.createElement('div'); |
+ _appElements['coverageBody'] = coverageBody; |
+ |
+ HTMLPreElement covPreElem = _runnerWindow.document.createElement('pre'); |
+ _appElements['covPreElem'] = covPreElem; |
+ coverageBody.appendChild(covPreElem); |
+ |
+ HTMLTableElement covTable = _runnerWindow.document.createElement('table'); |
+ covTable.className = 'dt-results'; |
+ HTMLTableSectionElement head = |
+ _runnerWindow.document.createElement('thead'); |
+ head.innerHTML = '<tr><th>Unit <th>Function <th>Statement <th>Branch'; |
+ covTable.appendChild(head); |
+ HTMLTableSectionElement covTableBody = |
+ _runnerWindow.document.createElement('tbody'); |
+ _appElements['covTableBody'] = covTableBody; |
+ covTableBody.id = 'dt-results-body'; |
+ covTable.appendChild(covTableBody); |
pdr
2011/12/16 17:00:50
indentation
shauvik
2011/12/16 17:57:06
Done.
|
+ coverageBody.appendChild(covTable); |
+ |
+ _addChildToElem('mainElem', coverageBody); |
+ } |
+ _show('coverageBody'); |
+ _hide('testBody'); |
+ |
+ _appElements['covPreElem'].textContent = getCoverageSummary(); |
+ _appElements['covTableBody'].innerHTML = getCoverageDetails(); |
+ } |
+ |
+ void _show(String toShow) { |
+ HTMLElement show = _appElements[toShow]; |
+ if(show != null) { |
+ if(show.classList.contains('dt-hide')) { |
+ show.classList.remove('dt-hide'); |
+ } |
+ show.classList.add('dt-show'); |
+ } |
+ } |
+ |
+ void _hide(String toHide) { |
+ HTMLElement hide = _appElements[toHide]; |
+ if(hide != null) { |
+ if(hide.classList.contains('dt-show')) { |
+ hide.classList.remove('dt-show'); |
+ } |
+ hide.classList.add('dt-hide'); |
+ } |
+ } |
+ |
+ void dartestMaximize() { |
+ _hide('containerDiv'); |
+ _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.
|
+ _runnerWindow.document.title = 'Dartest'; |
+ _fullAppElements = new HashMap<String, HTMLElement>(); |
+ _appElements = _fullAppElements; |
+ DARTestCss.inject(_runnerWindow.document, false); |
+ run(); |
+ if(testsRun > 0) { |
+ populateResults(); |
+ } |
+ } |
+ |
+ void populateResults() { |
+ tests.forEach((final t) => _updateDARTestUI(t)); |
+ } |
+ |
+ void dartestMinimize() { |
+ _runnerWindow.close(); |
+ _runnerWindow = window; |
+ _appElements = _inAppElements; |
+ _show('containerDiv'); |
+ } |
+ |
+ void exportTestResults() { |
+ String csvData = getTestResultsCsv(); |
+ _log(csvData); |
+ HTMLAnchorElement exportLink = |
+ _runnerWindow.document.getElementById('dt-export'); |
+ |
+ /** Bug: Can't instantiate WebKitBlobBuilder |
+ * If this bug is fixed, we can remove the urlencode and lpad function. |
+ WebKitBlobBuilder bb = new WebKitBlobBuilder(); |
+ bb.append(csvData); |
+ Blob blob = bb.getBlob('text/plain;charset=${document.characterSet}'); |
+ exportLink.href = window.webkitURL.createObjectURL(blob); |
+ **/ |
+ |
+ exportLink.href = 'data:text/csv,'+urlencode(csvData); |
+ |
+ MouseEvent ev = document.createEvent("MouseEvents"); |
+ ev.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0 |
+ , false, false, false, false, 0, null); |
+ exportLink.dispatchEvent(ev); |
+ |
+ } |
+ |
pdr
2011/12/16 17:00:50
Make these private and static
shauvik
2011/12/16 17:57:06
Done.
|
+ String urlencode(String s) { |
+ StringBuffer out = new StringBuffer(); |
+ for(int i = 0; i < s.length; i++) { |
+ int cc = s.charCodeAt(i); |
+ if((cc >= 48 && cc <= 57) || (cc >= 65 && cc <= 90) || |
+ (cc >= 97 && cc <= 122)) { |
+ out.add(s[i]); |
+ } else { |
+ out.add('%${lpad(cc.toRadixString(16),2).toUpperCase()}'); |
+ } |
+ } |
+ return out.toString(); |
+ } |
+ |
+ String lpad(String s, int n) { |
+ if(s.length < n) { |
+ for(int i = 0; i < n - s.length; i++) { |
+ s = '0'+s; |
+ } |
+ } |
+ return s; |
+ } |
+} |