unity-test-runner/UnityTestRunnerResultsReporter/TestReport.files/app.js

395 lines
18 KiB
JavaScript

var app = angular.module("TreeTestReportApp", ['treeGrid', 'ui.bootstrap', 'angular-clipboard'])
.directive('selectOnClick', ['$window', function ($window) {
return {
link: function (scope, element) {
element.on('click', function () {
var selection = $window.getSelection();
var range = document.createRange();
range.selectNodeContents(element[0]);
selection.removeAllRanges();
selection.addRange(range);
});
}
};
}])
.directive('preEx', function () {
return {
restrict: 'E',
replace: true,
transclude: true,
scope: {
title: '@',
text: '=',
textArray: '=',
},
template: "<div compile=\"temp\"></div>",
link: function (scope, elm, attrs, ctrl) {
if (!scope.text && !scope.textArray) {
console.log('No data was defined for the directive!');
return;
}
var singleTextRowTemplate = "<div><div class=\"pre-ex-div\"><span>{{::title}}</span><button type=\"button\" class=\"btn btn-default btn-xs pre-ex-button\" clipboard text=\"value\" on-copied=\"success()\" on-error=\"fail(err)\"><span class=\"glyphicon glyphicon-copy\"></span> Copy to Clipboard</button></div><pre select-on-click class='pre-callstack'>{{::value}}</pre></div>";
if(scope.text) {
scope.temp = singleTextRowTemplate;
scope.value = scope.text;
}
else if(scope.textArray) {
if(scope.textArray.length === 1) {
scope.value = scope.textArray[0];
scope.temp = singleTextRowTemplate;
}
else {
scope.temp = "<div><p><span>{{::title}}</span></p><div ng-class=\"!$last ? 'pre-ex-div-default' : ''\" ng-repeat=\"text in ::value\"><div class=\"pre-ex-div\"><button type=\"button\" class=\"btn btn-default btn-xs pre-ex-button-default\" clipboard text=\"::text\" on-copied=\"success()\" on-error=\"fail(err)\"><span class=\"glyphicon glyphicon-copy\"></span> Copy to Clipboard</button></div><pre select-on-click class='pre-callstack'>{{::text}}</pre></div></div>";
scope.value = scope.textArray;
}
}
scope.success = function() {
console.log('copied');
}
scope.fail = function () {
console.log('copy failed');
}
}
};
})
.directive('detailsWrapper', function () {
return {
restrict: 'E',
replace: true,
transclude: true,
scope: {
iconClass: '@'
},
template: "<div><table class=\"details-table\"><tr><td class=\"details-image-cell\"><i ng-class=\"::iconClass\"></i></td><td><div ng-transclude></div></td></tr></table></div>",
link: function (scope, elm, attrs, ctrl) {
}
};
})
.controller("TreeController", function($scope, $timeout){
angular.element(function () {
$timeout(function() {
$scope.my_tree.expand_all_error();
}, 0);
});
$scope.today = new Date();
var data = JSON.parse(document.getElementById("data").innerHTML);
//$scope.expand_to = {};
$scope.row_filter = {filtered: true};
$scope.filter = {};
$scope.filter.info = {value: false, type: 'Info'};
$scope.filter.warning = {value: false, type: 'Warning'};
$scope.filter.error = {value: true, type: 'Error'};
$scope.filter.artifact = {value: false, type: 'ArtifactPublish'};
$scope.filters = ['Info', 'InfoPartial', 'Warning', 'ArtifactPublish'];
$scope.filterSelected = function(model){
if(!model.value) {
$scope.filters.push(model.type);
}
else {
var index = $scope.filters.indexOf(model.type);
if(index !== -1)
$scope.filters.splice(index, 1);
}
}
$scope.additionalFilter = {
filterPropertyKey: 'type',
filterPropertyValue: 'TestStatus',
filterAttribute: 'state',
filterAttributeValues: ['1', '2', '3', '4', '7'],
hideSuccessfulSuites: true // temporary property, should be changed to something more generic as the other properties
};
$scope.testStatusFilter = {};
$scope.testStatusFilter.ignored = {selected: false, values: ['1', '2', '3', '7']}; // NotRunnable = 1, Skipped = 2, Ignored = 3, Cancelled = 7
$scope.testStatusFilter.passed = {selected: false, values: ['4']};
$scope.testStatusFilter.failed = {selected: true, values: ['5','6']}; // Failure = 5, Error = 6
$scope.testStatusFilter.inconclusive = {selected: true, values: ['0']};
$scope.testStatusFilter.hideSuccessfulSuites = true;
$scope.testStatusFilterChanged = function(model){
if(!model.selected) {
for(var i=0; i<model.values.length; i++) {
$scope.additionalFilter.filterAttributeValues.push(model.values[i]);
}
}
else {
for(var i=0; i<model.values.length; i++) {
var index = $scope.additionalFilter.filterAttributeValues.indexOf(model.values[i]);
if(index !== -1)
$scope.additionalFilter.filterAttributeValues.splice(index, 1);
}
}
}
// $scope.$watch('filter', function(){
// if($scope.filter.info){
// //$scope.filters
// }
// }, true);
$scope.my_tree = {};
$scope.tree_data = [
data
];
$scope.summary = {};
$scope.summary = data != null && data.type === "TestSession" && data.summary != null ? data.summary : undefined;
$scope.convertTestStateToString = function(state) {
switch(state)
{
case 5:
return "Failure";
case 6:
return "Error";
case 1:
return "NotRunnable";
case 2:
return "Skipped";
case 3:
return "Ignored";
case 7:
return "Cancelled";
case 0:
return "Inconclusive";
case 4:
return "Passed";
}
}
$scope.onExpandTo = function(branch){
return branch['rootCause']; //branch['errors'].length > 0 && branch.children.length === 0;
}
$scope.getIconByTestState = function(state) {
switch(state)
{
case 5:
case 6:
return "glyphicon glyphicon-minus-sign icon-red";
case 1:
case 2:
case 3:
case 7:
return "glyphicon glyphicon-question-sign icon-gray";
case 0:
return "glyphicon glyphicon-exclamation-sign icon-yellow";
case 4:
return "glyphicon glyphicon-ok-sign icon-green";
}
}
$scope.onLeafNodeCreate = function(branch){
var type = branch['type'];
if(type === 'TestStatus') {
return $scope.getIconByTestState(branch['state']) + " leaf-icon";
//return branch['state'] === 5 || branch['state'] === 6 ? "glyphicon glyphicon-remove-sign icon-red" : "glyphicon glyphicon-ok-sign icon-green";
}
if(type === 'Info' || type === 'InfoPartial') {
return "glyphicon glyphicon-file icon-blue leaf-icon";
}
if(type === 'Warning') {
return "glyphicon glyphicon-file icon-yellow leaf-icon";
}
if(type === 'Error') {
return "glyphicon glyphicon-file icon-red leaf-icon";
}
if(type === 'TestPlan') {
return "glyphicon glyphicon-list-alt icon-black leaf-icon";
}
if(type === 'AssemblyCompilationErrors') {
return "glyphicon glyphicon-remove-sign icon-red leaf-icon";
}
return "glyphicon glyphicon-file leaf-icon";
//return $scope.icons_defs[branch['type']] ? $scope.icons_defs[branch['type']] : $scope.icons_defs['default'];
}
//var errorsMarkup = "<p ng-if=\"row.branch['errors'].length > 0\">Errors ({{row.branch['errors'].length}}):</p>" +
// "<ul ng-if=\"row.branch['errors'].length > 0\"><li ng-repeat=\"error in row.branch['errors']\" ng-class=\"!$last ? 'list-item-padding' : ''\"><pre class='pre-callstack'>{{error}}</pre></li></ul>";
var errorsMarkup = "<p ng-if=\"row.branch['errors'].length > 0\"><pre-ex title=\"Errors ({{::row.branch['errors'].length}}):\" text-array=\"row.branch['errors']\"></pre-ex></p>";
var artifactsMarkup = "<p ng-if=\"row.branch['artifacts'].length > 0\"><span>Artifacts ({{::row.branch['artifacts'].length}}):</span><ul><li ng-repeat=\"link in ::row.branch['artifacts']\"><a href=\"{{::link}}\" target=\"_blank\"><i class=\"glyphicon glyphicon-paperclip icon-black icon-artifact\"></i>{{::link}}</a></li></ul></p>";
$scope.details_defs = {
default: "<div><pre class='pre-callstack'>{{::row.branch['message'] ? row.branch['message'] : row.branch['description']}}</pre></div>" +
"<p/>" +
errorsMarkup,
ArtifactPublish: "<div><pre class='pre-callstack'>{{::row.branch['destination']}}</pre></div>",
ProcessInfo: "<div>" +
"<details-wrapper icon-class=\"{{::row.branch['errors'].length === 0 ? 'glyphicon glyphicon-ok-sign icon-green details-icon' : 'glyphicon glyphicon-remove-sign icon-red details-icon'}}\">" +
"<p>Process Id: <b>{{::row.branch['processId']}}</b></p>"+
"<p><pre-ex title=\"Path:\" text=\"row.branch['path']\"/></p>"+
"<p><pre-ex title=\"Arguments:\" text=\"row.branch['arguments']\"/></p>"+
errorsMarkup +
artifactsMarkup +
"</details-wrapper>" +
"</div>",
TestGroup: "<div>" +
"<details-wrapper icon-class=\"{{::row.branch['errors'].length === 0 ? 'glyphicon glyphicon-ok-sign icon-green details-icon' : 'glyphicon glyphicon-remove-sign icon-red details-icon'}}\">" +
artifactsMarkup +
errorsMarkup +
"</details-wrapper>" +
"</div>",
TestPlan: "<div>"+
"<details-wrapper icon-class=\"glyphicon glyphicon-list-alt icon-black details-icon\">" +
"<p>Ready to run <b>{{::row.branch['tests'].length}}</b> tests:</p><ul><li ng-repeat=\"t in ::row.branch['tests']\">{{::t}}</li></ul>" +
"</details-wrapper>" +
"</div>",
TestSuite: "<div>" +
"<details-wrapper icon-class=\"{{::row.branch['errors'].length === 0 ? 'glyphicon glyphicon-ok-sign icon-green details-icon' : 'glyphicon glyphicon-remove-sign icon-red details-icon'}}\">" +
"<p>Name: <b>{{::row.branch['name']}}</b></p>" +
"<p>Scope: <b>{{::row.branch['scope']}}</b></p>" +
"<p>Platform: <b>{{::row.branch['platform']}}</b></p>" +
"<p><pre-ex title=\"Command to run locally:\" text=\"$parent.$parent.$parent.$parent.$parent.formatCommandLine(row.branch['minimalCommandLine'])\"></pre-ex></p>"+
artifactsMarkup +
errorsMarkup +
"</details-wrapper>" +
"</div>",
TestStatus: "<div>" +
"<details-wrapper icon-class=\"{{::$parent.$parent.$parent.getIconByTestState(row.branch['state']) + ' details-icon'}}\">" +
//"<table class=\"details-table\"><tr><td class=\"details-image-cell\"><i ng-class=\"$parent.$parent.$parent.getIconByTestState(row.branch['state']) + ' details-icon'\"></i></td><td>" +
"<p>Test Name: <b>{{::row.branch['name']}}</b></p>" +
"<p>Test Fixture: <b>{{::row.branch['fixture']}}</b></p>" +
"<p>Test Status: <b>{{::row.branch['state'] ? $parent.$parent.$parent.$parent.$parent.convertTestStateToString(row.branch['state']) : $parent.$parent.$parent.$parent.$parent.convertTestStateToString(0)}}</b></p>" +
"<p>Test Execution Time: <b>{{::row.branch['durationMicroseconds'] ? (row.branch['durationMicroseconds']/1000).toHHMMSS() : (row.branch['duration']).toHHMMSS()}}</b></p>" +
"<p ng-if=\"row.branch['message'] != null && row.branch['message'] != ''\"><pre-ex title=\"Message:\" text=\"row.branch['message']\"></pre-ex></p>"+
"<p><pre-ex title=\"Command to run locally:\" text=\"$parent.$parent.$parent.$parent.$parent.formatCommandLine(row.branch['minimalCommandLine'])\"></pre-ex></p>"+
artifactsMarkup +
errorsMarkup +
//"</td></tr></table>"+
"</details-wrapper>" +
"</div>",
AssemblyCompilationErrors: "<div>" +
"<details-wrapper icon-class=\"{{::row.branch['errors'].length === 0 ? 'glyphicon glyphicon-ok-sign icon-green details-icon' : 'glyphicon glyphicon-remove-sign icon-red details-icon'}}\">" +
"<p>Assembly: <b>{{::row.branch['assembly']}}</b></p>" +
errorsMarkup +
"</details-wrapper>" +
"</div>",
TestSession: "<div>" +
"<details-wrapper icon-class=\"{{::row.branch['summary'].success ? 'glyphicon glyphicon-ok-sign icon-green details-icon' : 'glyphicon glyphicon-remove-sign icon-red details-icon'}}\">" +
//"<table class=\"details-table\"><tr><td class=\"details-image-cell\"><i ng-class=\"row.branch['summary'].success ? 'glyphicon glyphicon-ok-sign icon-green details-icon' : 'glyphicon glyphicon-remove-sign icon-red details-icon'\"\"></i></td><td>" +
"<p>Run <b>{{::row.branch['summary'].testsCount}}</b> test(s). Passed: <b>{{::row.branch['summary'].successCount}}</b>, Failed: <b>{{::row.branch['summary'].failedCount + row.branch['summary'].errorCount}}</b>, Inconclusive: <b>{{::row.branch['summary'].inconclusiveCount}}</b>, Ignored: <b>{{::row.branch['summary'].ignoredCount}}</b>, Skipped: <b>{{::row.branch['summary'].skippedCount}}</b>, Not Runnable: <b>{{::row.branch['summary'].notRunCount}}</b></p>" +
"<p>Overall result: <b><span ng-class=\"row.branch['summary'].success ? 'pass-color' : 'fail-color'\">{{::row.branch['summary'].success ? 'PASS' : 'FAIL'}}</span></b></p>" +
"<p>Suites count: <b>{{::row.branch['summary'].suitesCount}}</b></p>" +
"<p><pre-ex title=\"Command to run locally:\" text=\"$parent.$parent.$parent.$parent.$parent.formatCommandLine(row.branch['minimalCommandLine'])\"></pre-ex></p>"+
artifactsMarkup +
errorsMarkup +
//"</td></tr></table>"+
"</details-wrapper>" +
"</div>",
Action: "<div>" +
"<details-wrapper icon-class=\"{{::row.branch['errors'].length === 0 ? 'glyphicon glyphicon-ok-sign icon-green details-icon' : 'glyphicon glyphicon-remove-sign icon-red details-icon'}}\">" +
"<p>Name: <b>{{::row.branch['name']}}</b></p>" +
artifactsMarkup +
errorsMarkup +
"</details-wrapper>" +
"</div>",
};
$scope.formatCommandLine = function(cmdParams) {
if(cmdParams == null || cmdParams.length === 0) {
return "";
}
var cmdLine = "perl utr.pl ";
for(var i=0; i<cmdParams.length; i++) {
cmdLine += cmdParams[i] + " ";
}
return cmdLine;
}
$scope.expanding_property = {
field: "type",
displayName: "Action",
cellCustomTemplate:
{
ArtifactPublish: "<div class=\"clipped\">{{::row.branch['destination']}}</div>",
ProcessInfo: "<div class=\"clipped\">{{::row.branch['path']}}</div>",
TestPlan: "<div>Ready to run <b>{{::row.branch['tests'].length}}</b> tests</div>",
TestStatus: "<div class=\"clipped\">{{::row.branch['name']}}</div>",
TestSuite: "<div ng-if=\"row.branch['errors'].length === 0\" class=\"clipped\">Run <b>{{::row.branch['name']}}</b> tests for <b>{{::row.branch['scope']}}</b>: <b><span class=\"pass-color\">PASS</span></b> (<b>{{::row.branch['summary'].successCount}}</b> test(s) passed)</div>" +
"<div ng-if=\"row.branch['errors'].length > 0\" class=\"clipped\">Run <b>{{::row.branch['name']}}</b> tests for <b>{{::row.branch['scope']}}</b>: <b><span class=\"fail-color\">FAIL</span></b> (<b>{{::row.branch['summary'].successCount}}</b> test(s) passed, <b>{{::row.branch['summary'].failedCount + row.branch['summary'].errorCount + row.branch['summary'].inconclusiveCount}}</b> test(s) failed, <b>{{::row.branch['summary'].compilationErrorsCount}}</b> compilation errors)</div>",
Info: "<div class=\"clipped\">{{::row.branch['message']}}</div>",
Warning: "<div class=\"clipped\">{{::row.branch['message']}}</div>",
Error: "<div class=\"clipped\">{{::row.branch['message']}}</div>",
InfoPartial: "<div class=\"clipped\">{{::row.branch['message']}}</div>",
AssemblyCompilationErrors: "<div class=\"clipped\">Assembly compilation failed: {{::row.branch['assembly']}}</div>",
TestSession: "<div class=\"clipped\">{{::row.branch['summary'].success ? 'Test Session Passed' : 'Test Session Failed'}}: <b>{{::row.branch['summary'].successCount}}</b> test(s) passed, <b>{{::row.branch['summary'].failedCount + row.branch['summary'].errorCount + row.branch['summary'].inconclusiveCount}}</b> test(s) failed, <b>{{::row.branch['summary'].compilationErrorsCount}}</b> compilation error(s)</div>",
TestGroup: "<div class=\"clipped\">{{::row.branch['name']}}</div>",
default: "<div class=\"clipped\">{{::row.branch['name']}} {{::row.branch['description']}}</div>",
},
filterable: true,
width: "65%"
}
$scope.col_defs = [
{
field: "time",
displayName: "Time",
cellCustomTemplate: {
default: "<div>{{::row.branch[col.field] | date : \"HH:mm:ss\"}}</div>"
},
width: "5%",
class: "text-right",
},
{
displayName: "Artifacts",
cellCustomTemplate: {
//default: "<div><ul><li ng-repeat=\"item in row.branch['children']\" ng-if=\"item['type'] === 'ArtifactPublish'\"><a href=\"{{item.destination}}\" ng-click=\"on_click_with_no_propagation($event)\" target=\"_blank\">{{item.destination}}</a></li></ul></div>"
//default: "<ul><li ng-repeat=\"item in row.branch['artifacts']\"><div class=\"clipped\"><a href=\"{{item}}\" ng-click=\"on_click_with_no_propagation($event)\" target=\"_blank\">{{item}}</a></div></li></ul>"
default: "<div ng-if=\"row.branch['artifacts'].length > 0\"><i class=\"glyphicon glyphicon-paperclip icon-black\"></i> {{::row.branch['artifacts'].length}}</div>"
},
width: "23%"
},
{
field: "duration",
displayName: "Duration",
width: "7%",
cellCustomTemplate: {
default: "<div>{{::row.branch['durationMicroseconds'] ? (row.branch['durationMicroseconds']/1000).toHHMMSS() : (row.branch['duration']).toHHMMSS()}}</div>"
},
class: "text-right",
//sortable : true,
//filterable : true
},
];
});
Number.prototype.toHHMMSS = function () {
//var sec_num = parseInt(this, 10);
sec_num = this / 1000;
if(sec_num < 0.0005)
return "0";
var hours = Math.floor(sec_num / 3600);
var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
var seconds = sec_num - (hours * 3600) - (minutes * 60);
if (hours < 10) {hours = "0"+hours;}
if (minutes < 10) {minutes = "0"+minutes;}
if (seconds < 10) {seconds = "0"+seconds.toFixed(3);} else {seconds = seconds.toFixed(3);}
return hours+':'+minutes+':'+seconds;
};