
* Provide ability for Foundation admin to mark/unmark a test run as verified. * Explicitly show a test run's associated guideline/target. * If a test is associated to a product, display that product. * Change share/unshare to yes/no in edit dropdown. Change-Id: Iffd4c6b99799be9b8f026b6c4a55922a9e5cb4e4
648 lines
26 KiB
JavaScript
648 lines
26 KiB
JavaScript
/*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
angular
|
|
.module('refstackApp')
|
|
.controller('ResultsReportController', ResultsReportController);
|
|
|
|
ResultsReportController.$inject = [
|
|
'$http', '$stateParams', '$window',
|
|
'$uibModal', 'refstackApiUrl', 'raiseAlert'
|
|
];
|
|
|
|
/**
|
|
* RefStack Results Report Controller
|
|
* This controller is for the '/results/<test run ID>' page where a user can
|
|
* view details for a specific test run.
|
|
*/
|
|
function ResultsReportController($http, $stateParams, $window,
|
|
$uibModal, refstackApiUrl, raiseAlert) {
|
|
|
|
var ctrl = this;
|
|
|
|
ctrl.getVersionList = getVersionList;
|
|
ctrl.getResults = getResults;
|
|
ctrl.isResultAdmin = isResultAdmin;
|
|
ctrl.isShared = isShared;
|
|
ctrl.shareTestRun = shareTestRun;
|
|
ctrl.deleteTestRun = deleteTestRun;
|
|
ctrl.updateVerificationStatus = updateVerificationStatus;
|
|
ctrl.updateGuidelines = updateGuidelines;
|
|
ctrl.getTargetCapabilities = getTargetCapabilities;
|
|
ctrl.buildCapabilityV1_2 = buildCapabilityV1_2;
|
|
ctrl.buildCapabilityV1_3 = buildCapabilityV1_3;
|
|
ctrl.buildCapabilitiesObject = buildCapabilitiesObject;
|
|
ctrl.isTestFlagged = isTestFlagged;
|
|
ctrl.getFlaggedReason = getFlaggedReason;
|
|
ctrl.isCapabilityShown = isCapabilityShown;
|
|
ctrl.isTestShown = isTestShown;
|
|
ctrl.getCapabilityTestCount = getCapabilityTestCount;
|
|
ctrl.getStatusTestCount = getStatusTestCount;
|
|
ctrl.openFullTestListModal = openFullTestListModal;
|
|
|
|
/** The testID extracted from the URL route. */
|
|
ctrl.testId = $stateParams.testID;
|
|
|
|
/** The target OpenStack marketing program to compare against. */
|
|
ctrl.target = 'platform';
|
|
|
|
/** Mappings of DefCore components to marketing program names. */
|
|
ctrl.targetMappings = {
|
|
'platform': 'Openstack Powered Platform',
|
|
'compute': 'OpenStack Powered Compute',
|
|
'object': 'OpenStack Powered Object Storage'
|
|
};
|
|
|
|
/** The schema version of the currently selected guideline data. */
|
|
ctrl.schemaVersion = null;
|
|
|
|
/** The selected test status used for test filtering. */
|
|
ctrl.testStatus = 'total';
|
|
|
|
/** The HTML template that all accordian groups will use. */
|
|
ctrl.detailsTemplate = 'components/results-report/partials/' +
|
|
'reportDetails.html';
|
|
|
|
/**
|
|
* Retrieve an array of available guideline files from the Refstack
|
|
* API server, sort this array reverse-alphabetically, and store it in
|
|
* a scoped variable. The scope's selected version is initialized to
|
|
* the latest (i.e. first) version here as well. After a successful API
|
|
* call, the function to update the capabilities is called.
|
|
* Sample API return array: ["2015.03.json", "2015.04.json"]
|
|
*/
|
|
function getVersionList() {
|
|
var content_url = refstackApiUrl + '/guidelines';
|
|
ctrl.versionsRequest =
|
|
$http.get(content_url).success(function (data) {
|
|
ctrl.versionList = data.sort().reverse();
|
|
if (!ctrl.version) {
|
|
// Default to the first approved guideline which is
|
|
// expected to be at index 1.
|
|
ctrl.version = ctrl.versionList[1];
|
|
}
|
|
ctrl.updateGuidelines();
|
|
}).error(function (error) {
|
|
ctrl.showError = true;
|
|
ctrl.error = 'Error retrieving version list: ' +
|
|
angular.toJson(error);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Retrieve results from the Refstack API server based on the test
|
|
* run id in the URL. This function is the first function that will
|
|
* be called from the controller. Upon successful retrieval of results,
|
|
* the function that gets the version list will be called.
|
|
*/
|
|
function getResults() {
|
|
var content_url = refstackApiUrl + '/results/' + ctrl.testId;
|
|
ctrl.resultsRequest =
|
|
$http.get(content_url).success(function (data) {
|
|
ctrl.resultsData = data;
|
|
ctrl.version = ctrl.resultsData.meta.guideline;
|
|
ctrl.isVerified = ctrl.resultsData.verification_status;
|
|
if (ctrl.resultsData.meta.target) {
|
|
ctrl.target = ctrl.resultsData.meta.target;
|
|
}
|
|
getVersionList();
|
|
}).error(function (error) {
|
|
ctrl.showError = true;
|
|
ctrl.resultsData = null;
|
|
ctrl.error = 'Error retrieving results from server: ' +
|
|
angular.toJson(error);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* This tells you whether the current user has administrative
|
|
* privileges for the test result.
|
|
* @returns {Boolean} true if the user has admin privileges.
|
|
*/
|
|
function isResultAdmin() {
|
|
return Boolean(ctrl.resultsData &&
|
|
(ctrl.resultsData.user_role === 'owner' ||
|
|
ctrl.resultsData.user_role === 'foundation'));
|
|
}
|
|
/**
|
|
* This tells you whether the current results are shared with the
|
|
* community or not.
|
|
* @returns {Boolean} true if the results are shared
|
|
*/
|
|
function isShared() {
|
|
return Boolean(ctrl.resultsData &&
|
|
'shared' in ctrl.resultsData.meta);
|
|
}
|
|
|
|
/**
|
|
* This will send an API request in order to share or unshare the
|
|
* current results based on the passed in shareState.
|
|
* @param {Boolean} shareState - Whether to share or unshare results.
|
|
*/
|
|
function shareTestRun(shareState) {
|
|
var content_url = [
|
|
refstackApiUrl, '/results/', ctrl.testId, '/meta/shared'
|
|
].join('');
|
|
if (shareState) {
|
|
ctrl.shareRequest =
|
|
$http.post(content_url, 'true').success(function () {
|
|
ctrl.resultsData.meta.shared = 'true';
|
|
raiseAlert('success', '', 'Test run shared!');
|
|
}).error(function (error) {
|
|
raiseAlert('danger', error.title, error.detail);
|
|
});
|
|
} else {
|
|
ctrl.shareRequest =
|
|
$http.delete(content_url).success(function () {
|
|
delete ctrl.resultsData.meta.shared;
|
|
raiseAlert('success', '', 'Test run unshared!');
|
|
}).error(function (error) {
|
|
raiseAlert('danger', error.title, error.detail);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This will send a request to the API to delete the current
|
|
* test results set.
|
|
*/
|
|
function deleteTestRun() {
|
|
var content_url = [
|
|
refstackApiUrl, '/results/', ctrl.testId
|
|
].join('');
|
|
ctrl.deleteRequest =
|
|
$http.delete(content_url).success(function () {
|
|
$window.history.back();
|
|
}).error(function (error) {
|
|
raiseAlert('danger', error.title, error.detail);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* This will send a request to the API to delete the current
|
|
* test results set.
|
|
*/
|
|
function updateVerificationStatus() {
|
|
var content_url = [
|
|
refstackApiUrl, '/results/', ctrl.testId
|
|
].join('');
|
|
var data = {'verification_status': ctrl.isVerified};
|
|
ctrl.updateRequest =
|
|
$http.put(content_url, data).success(
|
|
function () {
|
|
ctrl.resultsData.verification_status = ctrl.isVerified;
|
|
raiseAlert('success', '',
|
|
'Verification status changed!');
|
|
}).error(function (error) {
|
|
ctrl.isVerified = ctrl.resultsData.verification_status;
|
|
raiseAlert('danger', error.title, error.detail);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* This will contact the Refstack API server to retrieve the JSON
|
|
* content of the guideline file corresponding to the selected
|
|
* version. A function to construct an object from the capability
|
|
* data will be called upon successful retrieval.
|
|
*/
|
|
function updateGuidelines() {
|
|
ctrl.guidelineData = null;
|
|
ctrl.showError = false;
|
|
var content_url = refstackApiUrl + '/guidelines/' +
|
|
ctrl.version;
|
|
ctrl.capsRequest =
|
|
$http.get(content_url).success(function (data) {
|
|
ctrl.guidelineData = data;
|
|
ctrl.schemaVersion = data.schema;
|
|
ctrl.buildCapabilitiesObject();
|
|
}).error(function (error) {
|
|
ctrl.showError = true;
|
|
ctrl.guidelineData = null;
|
|
ctrl.error = 'Error retrieving guideline date: ' +
|
|
angular.toJson(error);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* This will get all the capabilities relevant to the target and
|
|
* their corresponding statuses.
|
|
* @returns {Object} Object containing each capability and their status
|
|
*/
|
|
function getTargetCapabilities() {
|
|
var components = ctrl.guidelineData.components;
|
|
var targetCaps = {};
|
|
|
|
// The 'platform' target is comprised of multiple components, so
|
|
// we need to get the capabilities belonging to each of its
|
|
// components.
|
|
if (ctrl.target === 'platform') {
|
|
var platform_components =
|
|
ctrl.guidelineData.platform.required;
|
|
|
|
// This will contain status priority values, where lower
|
|
// values mean higher priorities.
|
|
var statusMap = {
|
|
required: 1,
|
|
advisory: 2,
|
|
deprecated: 3,
|
|
removed: 4
|
|
};
|
|
|
|
// For each component required for the platform program.
|
|
angular.forEach(platform_components, function (component) {
|
|
// Get each capability list belonging to each status.
|
|
angular.forEach(components[component],
|
|
function (caps, status) {
|
|
// For each capability.
|
|
angular.forEach(caps, function(cap) {
|
|
// If the capability has already been added.
|
|
if (cap in targetCaps) {
|
|
// If the status priority value is less
|
|
// than the saved priority value, update
|
|
// the value.
|
|
if (statusMap[status] <
|
|
statusMap[targetCaps[cap]]) {
|
|
targetCaps[cap] = status;
|
|
}
|
|
}
|
|
else {
|
|
targetCaps[cap] = status;
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
else {
|
|
angular.forEach(components[ctrl.target],
|
|
function (caps, status) {
|
|
angular.forEach(caps, function(cap) {
|
|
targetCaps[cap] = status;
|
|
});
|
|
});
|
|
}
|
|
return targetCaps;
|
|
}
|
|
|
|
/**
|
|
* This will build the a capability object for schema version 1.2.
|
|
* This object will contain the information needed to form a report in
|
|
* the HTML template.
|
|
* @param {String} capId capability ID
|
|
*/
|
|
function buildCapabilityV1_2(capId) {
|
|
var cap = {
|
|
'id': capId,
|
|
'passedTests': [],
|
|
'notPassedTests': [],
|
|
'passedFlagged': [],
|
|
'notPassedFlagged': []
|
|
};
|
|
var capDetails = ctrl.guidelineData.capabilities[capId];
|
|
// Loop through each test belonging to the capability.
|
|
angular.forEach(capDetails.tests,
|
|
function (testId) {
|
|
// If the test ID is in the results' test list, add
|
|
// it to the passedTests array.
|
|
if (ctrl.resultsData.results.indexOf(testId) > -1) {
|
|
cap.passedTests.push(testId);
|
|
if (capDetails.flagged.indexOf(testId) > -1) {
|
|
cap.passedFlagged.push(testId);
|
|
}
|
|
}
|
|
else {
|
|
cap.notPassedTests.push(testId);
|
|
if (capDetails.flagged.indexOf(testId) > -1) {
|
|
cap.notPassedFlagged.push(testId);
|
|
}
|
|
}
|
|
});
|
|
return cap;
|
|
}
|
|
|
|
/**
|
|
* This will build the a capability object for schema version 1.3 and
|
|
* above. This object will contain the information needed to form a
|
|
* report in the HTML template.
|
|
* @param {String} capId capability ID
|
|
*/
|
|
function buildCapabilityV1_3(capId) {
|
|
var cap = {
|
|
'id': capId,
|
|
'passedTests': [],
|
|
'notPassedTests': [],
|
|
'passedFlagged': [],
|
|
'notPassedFlagged': []
|
|
};
|
|
// Loop through each test belonging to the capability.
|
|
angular.forEach(ctrl.guidelineData.capabilities[capId].tests,
|
|
function (details, testId) {
|
|
var passed = false;
|
|
|
|
// If the test ID is in the results' test list.
|
|
if (ctrl.resultsData.results.indexOf(testId) > -1) {
|
|
passed = true;
|
|
}
|
|
else if ('aliases' in details) {
|
|
var len = details.aliases.length;
|
|
for (var i = 0; i < len; i++) {
|
|
var alias = details.aliases[i];
|
|
if (ctrl.resultsData.results.indexOf(alias) > -1) {
|
|
passed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add to correct array based on whether the test was
|
|
// passed or not.
|
|
if (passed) {
|
|
cap.passedTests.push(testId);
|
|
if ('flagged' in details) {
|
|
cap.passedFlagged.push(testId);
|
|
}
|
|
}
|
|
else {
|
|
cap.notPassedTests.push(testId);
|
|
if ('flagged' in details) {
|
|
cap.notPassedFlagged.push(testId);
|
|
}
|
|
}
|
|
});
|
|
return cap;
|
|
}
|
|
|
|
/**
|
|
* This will check the schema version of the current capabilities file,
|
|
* and will call the correct method to build an object based on the
|
|
* capability data retrieved from the Refstack API server.
|
|
*/
|
|
function buildCapabilitiesObject() {
|
|
// This is the object template where 'count' is the number of
|
|
// total tests that fall under the given status, and 'passedCount'
|
|
// is the number of tests passed. The 'caps' array will contain
|
|
// objects with details regarding each capability.
|
|
ctrl.caps = {
|
|
'required': {'caps': [], 'count': 0, 'passedCount': 0,
|
|
'flagFailCount': 0, 'flagPassCount': 0},
|
|
'advisory': {'caps': [], 'count': 0, 'passedCount': 0,
|
|
'flagFailCount': 0, 'flagPassCount': 0},
|
|
'deprecated': {'caps': [], 'count': 0, 'passedCount': 0,
|
|
'flagFailCount': 0, 'flagPassCount': 0},
|
|
'removed': {'caps': [], 'count': 0, 'passedCount': 0,
|
|
'flagFailCount': 0, 'flagPassCount': 0}
|
|
};
|
|
|
|
switch (ctrl.schemaVersion) {
|
|
case '1.2':
|
|
var capMethod = 'buildCapabilityV1_2';
|
|
break;
|
|
case '1.3':
|
|
case '1.4':
|
|
case '1.5':
|
|
case '1.6':
|
|
capMethod = 'buildCapabilityV1_3';
|
|
break;
|
|
default:
|
|
ctrl.showError = true;
|
|
ctrl.guidelineData = null;
|
|
ctrl.error = 'The schema version for the guideline ' +
|
|
'file selected (' + ctrl.schemaVersion +
|
|
') is currently not supported.';
|
|
return;
|
|
}
|
|
|
|
// Get test details for each relevant capability and store
|
|
// them in the scope's 'caps' object.
|
|
var targetCaps = ctrl.getTargetCapabilities();
|
|
angular.forEach(targetCaps, function(status, capId) {
|
|
var cap = ctrl[capMethod](capId);
|
|
ctrl.caps[status].count +=
|
|
cap.passedTests.length + cap.notPassedTests.length;
|
|
ctrl.caps[status].passedCount += cap.passedTests.length;
|
|
ctrl.caps[status].flagPassCount += cap.passedFlagged.length;
|
|
ctrl.caps[status].flagFailCount +=
|
|
cap.notPassedFlagged.length;
|
|
ctrl.caps[status].caps.push(cap);
|
|
});
|
|
|
|
ctrl.requiredPassPercent = (ctrl.caps.required.passedCount *
|
|
100 / ctrl.caps.required.count);
|
|
|
|
ctrl.totalRequiredFailCount = ctrl.caps.required.count -
|
|
ctrl.caps.required.passedCount;
|
|
ctrl.totalRequiredFlagCount =
|
|
ctrl.caps.required.flagFailCount +
|
|
ctrl.caps.required.flagPassCount;
|
|
ctrl.totalNonFlagCount = ctrl.caps.required.count -
|
|
ctrl.totalRequiredFlagCount;
|
|
ctrl.nonFlagPassCount = ctrl.totalNonFlagCount -
|
|
(ctrl.totalRequiredFailCount -
|
|
ctrl.caps.required.flagFailCount);
|
|
|
|
ctrl.nonFlagRequiredPassPercent = (ctrl.nonFlagPassCount *
|
|
100 / ctrl.totalNonFlagCount);
|
|
}
|
|
|
|
/**
|
|
* This will check if a given test is flagged.
|
|
* @param {String} test ID of the test to check
|
|
* @param {Object} capObj capability that test is under
|
|
* @returns {Boolean} truthy value if test is flagged
|
|
*/
|
|
function isTestFlagged(test, capObj) {
|
|
if (!capObj) {
|
|
return false;
|
|
}
|
|
return (((ctrl.schemaVersion === '1.2') &&
|
|
(capObj.flagged.indexOf(test) > -1)) ||
|
|
((ctrl.schemaVersion >= '1.3') &&
|
|
(capObj.tests[test].flagged)));
|
|
}
|
|
|
|
/**
|
|
* This will return the reason a test is flagged. An empty string
|
|
* will be returned if the passed in test is not flagged.
|
|
* @param {String} test ID of the test to check
|
|
* @param {String} capObj capability that test is under
|
|
* @returns {String} reason
|
|
*/
|
|
function getFlaggedReason(test, capObj) {
|
|
if ((ctrl.schemaVersion === '1.2') &&
|
|
(ctrl.isTestFlagged(test, capObj))) {
|
|
|
|
// Return a generic message since schema 1.2 does not
|
|
// provide flag reasons.
|
|
return 'DefCore has flagged this test.';
|
|
}
|
|
else if ((ctrl.schemaVersion >= '1.3') &&
|
|
(ctrl.isTestFlagged(test, capObj))) {
|
|
|
|
return capObj.tests[test].flagged.reason;
|
|
}
|
|
else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This will check the if a capability should be shown based on the
|
|
* test filter selected. If a capability does not have any tests
|
|
* belonging under the given filter, it should not be shown.
|
|
* @param {Object} capability Built object for capability
|
|
* @returns {Boolean} true if capability should be shown
|
|
*/
|
|
function isCapabilityShown(capability) {
|
|
return ((ctrl.testStatus === 'total') ||
|
|
(ctrl.testStatus === 'passed' &&
|
|
capability.passedTests.length > 0) ||
|
|
(ctrl.testStatus === 'not passed' &&
|
|
capability.notPassedTests.length > 0) ||
|
|
(ctrl.testStatus === 'flagged' &&
|
|
(capability.passedFlagged.length +
|
|
capability.notPassedFlagged.length > 0)));
|
|
}
|
|
|
|
/**
|
|
* This will check the if a test should be shown based on the test
|
|
* filter selected.
|
|
* @param {String} test ID of the test
|
|
* @param {Object} capability Built object for capability
|
|
* @return {Boolean} true if test should be shown
|
|
*/
|
|
function isTestShown(test, capability) {
|
|
return ((ctrl.testStatus === 'total') ||
|
|
(ctrl.testStatus === 'passed' &&
|
|
capability.passedTests.indexOf(test) > -1) ||
|
|
(ctrl.testStatus === 'not passed' &&
|
|
capability.notPassedTests.indexOf(test) > -1) ||
|
|
(ctrl.testStatus === 'flagged' &&
|
|
(capability.passedFlagged.indexOf(test) > -1 ||
|
|
capability.notPassedFlagged.indexOf(test) > -1)));
|
|
}
|
|
|
|
/**
|
|
* This will give the number of tests belonging under the selected
|
|
* test filter for a given capability.
|
|
* @param {Object} capability Built object for capability
|
|
* @returns {Number} number of tests under filter
|
|
*/
|
|
function getCapabilityTestCount(capability) {
|
|
if (ctrl.testStatus === 'total') {
|
|
return capability.passedTests.length +
|
|
capability.notPassedTests.length;
|
|
}
|
|
else if (ctrl.testStatus === 'passed') {
|
|
return capability.passedTests.length;
|
|
}
|
|
else if (ctrl.testStatus === 'not passed') {
|
|
return capability.notPassedTests.length;
|
|
}
|
|
else if (ctrl.testStatus === 'flagged') {
|
|
return capability.passedFlagged.length +
|
|
capability.notPassedFlagged.length;
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This will give the number of tests belonging under the selected
|
|
* test filter for a given status.
|
|
* @param {String} capability status
|
|
* @returns {Number} number of tests for status under filter
|
|
*/
|
|
function getStatusTestCount(status) {
|
|
if (!ctrl.caps) {
|
|
return -1;
|
|
}
|
|
else if (ctrl.testStatus === 'total') {
|
|
return ctrl.caps[status].count;
|
|
}
|
|
else if (ctrl.testStatus === 'passed') {
|
|
return ctrl.caps[status].passedCount;
|
|
}
|
|
else if (ctrl.testStatus === 'not passed') {
|
|
return ctrl.caps[status].count -
|
|
ctrl.caps[status].passedCount;
|
|
}
|
|
else if (ctrl.testStatus === 'flagged') {
|
|
return ctrl.caps[status].flagFailCount +
|
|
ctrl.caps[status].flagPassCount;
|
|
}
|
|
else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This will open the modal that will show the full list of passed
|
|
* tests for the current results.
|
|
*/
|
|
function openFullTestListModal() {
|
|
$uibModal.open({
|
|
templateUrl: '/components/results-report/partials' +
|
|
'/fullTestListModal.html',
|
|
backdrop: true,
|
|
windowClass: 'modal',
|
|
animation: true,
|
|
controller: 'FullTestListModalController as modal',
|
|
size: 'lg',
|
|
resolve: {
|
|
tests: function () {
|
|
return ctrl.resultsData.results;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
getResults();
|
|
}
|
|
|
|
angular
|
|
.module('refstackApp')
|
|
.controller('FullTestListModalController', FullTestListModalController);
|
|
|
|
FullTestListModalController.$inject = ['$uibModalInstance', 'tests'];
|
|
|
|
/**
|
|
* Full Test List Modal Controller
|
|
* This controller is for the modal that appears if a user wants to see the
|
|
* full list of passed tests on a report page.
|
|
*/
|
|
function FullTestListModalController($uibModalInstance, tests) {
|
|
var ctrl = this;
|
|
|
|
ctrl.tests = tests;
|
|
|
|
/**
|
|
* This function will close/dismiss the modal.
|
|
*/
|
|
ctrl.close = function () {
|
|
$uibModalInstance.dismiss('exit');
|
|
};
|
|
|
|
/**
|
|
* This function will return a string representing the sorted
|
|
* tests list separated by newlines.
|
|
*/
|
|
ctrl.getTestListString = function () {
|
|
return ctrl.tests.sort().join('\n');
|
|
};
|
|
}
|
|
})();
|