mirror of
https://github.com/nasa/openmct.git
synced 2025-06-17 14:48:13 +00:00
Merged conflicts with wtd534
This commit is contained in:
@ -7,6 +7,8 @@
|
|||||||
"platform/commonUI/dialog",
|
"platform/commonUI/dialog",
|
||||||
"platform/commonUI/general",
|
"platform/commonUI/general",
|
||||||
"platform/telemetry",
|
"platform/telemetry",
|
||||||
|
"platform/features/plot",
|
||||||
|
|
||||||
|
"example/generator",
|
||||||
"example/persistence"
|
"example/persistence"
|
||||||
]
|
]
|
24
platform/features/scrolling/bundle.json
Normal file
24
platform/features/scrolling/bundle.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "Scrolling Lists",
|
||||||
|
"description": "Time-ordered list of latest data.",
|
||||||
|
"extensions": {
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"key": "scrolling",
|
||||||
|
"name": "Scrolling",
|
||||||
|
"description": "Scrolling list of data values.",
|
||||||
|
"templateUrl": "templates/scrolling.html",
|
||||||
|
"needs": [ "telemetry" ],
|
||||||
|
"delegation": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"controllers": [
|
||||||
|
{
|
||||||
|
"key": "ScrollingListController",
|
||||||
|
"implementation": "ScrollingListController.js",
|
||||||
|
"depends": [ "$scope" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
25
platform/features/scrolling/res/templates/scrolling.html
Normal file
25
platform/features/scrolling/res/templates/scrolling.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<div class="w1" ng-controller="TelemetryController as telemetry">
|
||||||
|
<!-- Dynamically set height based on number of rows desired. In current CSS, each row occupies 18px -->
|
||||||
|
<div class="w2"
|
||||||
|
ng-style="{ height: rows.length * 18 + 'px' }"
|
||||||
|
ng-controller="ScrollingListController">
|
||||||
|
<div class="tabular">
|
||||||
|
|
||||||
|
<div class="tr header">
|
||||||
|
<!-- Enable sorting by binding click handlers to the <em> element in each <th>. Note that CSS classes must be added dynamically to the current <th> that is sorting. For example, if sorting by ID: <div class="th sort asc"> or <div class="th sort desc">. -->
|
||||||
|
<div class="th" ng-repeat="header in headers">
|
||||||
|
<em>{{header}}</em>{{header}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tr" ng-repeat="row in rows">
|
||||||
|
<div class="td" ng-repeat="cell in row">
|
||||||
|
{{cell}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
49
platform/features/scrolling/src/DomainColumn.js
Normal file
49
platform/features/scrolling/src/DomainColumn.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*global define,moment*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../../plot/lib/moment.min"],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Date format to use for domain values; in particular,
|
||||||
|
// use day-of-year instead of month/day
|
||||||
|
var DATE_FORMAT = "YYYY-DDD HH:mm:ss";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A column which will report telemetry domain values
|
||||||
|
* (typically, timestamps.) Used by the ScrollingListController.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param domainMetadata an object with the machine- and human-
|
||||||
|
* readable names for this domain (in `key` and `name`
|
||||||
|
* fields, respectively.)
|
||||||
|
*/
|
||||||
|
function DomainColumn(domainMetadata) {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Get the title to display in this column's header.
|
||||||
|
* @returns {string} the title to display
|
||||||
|
*/
|
||||||
|
getTitle: function () {
|
||||||
|
return domainMetadata.name;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get the text to display inside a row under this
|
||||||
|
* column.
|
||||||
|
* @returns {string} the text to display
|
||||||
|
*/
|
||||||
|
getValue: function (domainObject, data, index) {
|
||||||
|
return moment.utc(data.getDomainValue(
|
||||||
|
index,
|
||||||
|
domainMetadata.key
|
||||||
|
)).format(DATE_FORMAT);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return DomainColumn;
|
||||||
|
}
|
||||||
|
);
|
39
platform/features/scrolling/src/NameColumn.js
Normal file
39
platform/features/scrolling/src/NameColumn.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*global define,Promise*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module defining NameColumn. Created by vwoeltje on 11/18/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A column which will report the name of the domain object
|
||||||
|
* which exposed specific telemetry values.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function NameColumn() {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Get the title to display in this column's header.
|
||||||
|
* @returns {string} the title to display
|
||||||
|
*/
|
||||||
|
getTitle: function () {
|
||||||
|
return "Name";
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get the text to display inside a row under this
|
||||||
|
* column. This returns the domain object's name.
|
||||||
|
* @returns {string} the text to display
|
||||||
|
*/
|
||||||
|
getValue: function (domainObject) {
|
||||||
|
return domainObject.getModel().name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return NameColumn;
|
||||||
|
}
|
||||||
|
);
|
43
platform/features/scrolling/src/RangeColumn.js
Normal file
43
platform/features/scrolling/src/RangeColumn.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*global define,Promise*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A column which will report telemetry range values
|
||||||
|
* (typically, measurements.) Used by the ScrollingListController.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param rangeMetadata an object with the machine- and human-
|
||||||
|
* readable names for this range (in `key` and `name`
|
||||||
|
* fields, respectively.)
|
||||||
|
*/
|
||||||
|
function RangeColumn(rangeMetadata) {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Get the title to display in this column's header.
|
||||||
|
* @returns {string} the title to display
|
||||||
|
*/
|
||||||
|
getTitle: function () {
|
||||||
|
return rangeMetadata.name;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get the text to display inside a row under this
|
||||||
|
* column.
|
||||||
|
* @returns {string} the text to display
|
||||||
|
*/
|
||||||
|
getValue: function (domainObject, data, index) {
|
||||||
|
var value = data.getRangeValue(index, rangeMetadata.key);
|
||||||
|
return value && value.toFixed(3);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return RangeColumn;
|
||||||
|
}
|
||||||
|
);
|
113
platform/features/scrolling/src/ScrollingListController.js
Normal file
113
platform/features/scrolling/src/ScrollingListController.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/*global define,Promise*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module defining ListController. Created by vwoeltje on 11/18/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["./NameColumn", "./DomainColumn", "./RangeColumn", "./ScrollingListPopulator"],
|
||||||
|
function (NameColumn, DomainColumn, RangeColumn, ScrollingListPopulator) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var ROW_COUNT = 18;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ScrollingListController is responsible for populating
|
||||||
|
* the contents of the scrolling list view.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function ScrollingListController($scope) {
|
||||||
|
var populator;
|
||||||
|
|
||||||
|
|
||||||
|
// Get a set of populated, ready-to-display rows for the
|
||||||
|
// latest data values.
|
||||||
|
function getRows(telemetry) {
|
||||||
|
var datas = telemetry.getResponse(),
|
||||||
|
objects = telemetry.getTelemetryObjects();
|
||||||
|
|
||||||
|
return populator.getRows(datas, objects, ROW_COUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the contents
|
||||||
|
function updateRows() {
|
||||||
|
var telemetry = $scope.telemetry;
|
||||||
|
$scope.rows = telemetry ? getRows(telemetry) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up columns based on telemetry metadata. This will
|
||||||
|
// include one column for each domain and range type, as
|
||||||
|
// well as a column for the domain object name.
|
||||||
|
function setupColumns(telemetry) {
|
||||||
|
var domainKeys = {},
|
||||||
|
rangeKeys = {},
|
||||||
|
columns = [],
|
||||||
|
metadata;
|
||||||
|
|
||||||
|
// Add a domain to the set of columns, if a domain
|
||||||
|
// with the same key has not yet been inclued.
|
||||||
|
function addDomain(domain) {
|
||||||
|
var key = domain.key;
|
||||||
|
if (key && !domainKeys[key]) {
|
||||||
|
domainKeys[key] = true;
|
||||||
|
columns.push(new DomainColumn(domain));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a range to the set of columns, if a range
|
||||||
|
// with the same key has not yet been inclued.
|
||||||
|
function addRange(range) {
|
||||||
|
var key = range.key;
|
||||||
|
if (key && !rangeKeys[key]) {
|
||||||
|
rangeKeys[key] = true;
|
||||||
|
columns.push(new RangeColumn(range));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We cannot proceed if the telemetry controller
|
||||||
|
// is not available; clear all rows/columns.
|
||||||
|
if (!telemetry) {
|
||||||
|
columns = [];
|
||||||
|
$scope.rows = [];
|
||||||
|
$scope.headers = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
columns = [ new NameColumn() ];
|
||||||
|
|
||||||
|
// Add domain, range columns
|
||||||
|
metadata = telemetry.getMetadata();
|
||||||
|
(metadata || []).forEach(function (metadata) {
|
||||||
|
(metadata.domains || []).forEach(addDomain);
|
||||||
|
});
|
||||||
|
(metadata || []).forEach(function (metadata) {
|
||||||
|
(metadata.ranges || []).forEach(addRange);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add default domain, range columns if none
|
||||||
|
// were described in metadata.
|
||||||
|
if (Object.keys(domainKeys).length < 1) {
|
||||||
|
columns.push(new DomainColumn({ name: "Time" }));
|
||||||
|
}
|
||||||
|
if (Object.keys(rangeKeys).length < 1) {
|
||||||
|
columns.push(new RangeColumn({ name: "Value" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have all columns now; use them to initializer
|
||||||
|
// the populator, which will use them to generate
|
||||||
|
// actual rows and headers.
|
||||||
|
populator = new ScrollingListPopulator(columns);
|
||||||
|
|
||||||
|
// Initialize headers
|
||||||
|
$scope.headers = populator.getHeaders();
|
||||||
|
|
||||||
|
// Fill in the contents of the rows.
|
||||||
|
updateRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.$on("telemetryUpdate", updateRows);
|
||||||
|
$scope.$watch("telemetry", setupColumns);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ScrollingListController;
|
||||||
|
}
|
||||||
|
);
|
115
platform/features/scrolling/src/ScrollingListPopulator.js
Normal file
115
platform/features/scrolling/src/ScrollingListPopulator.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function ScrollingListPopulator(columns) {
|
||||||
|
/**
|
||||||
|
* Look up the most recent values from a set of data objects.
|
||||||
|
* Returns an array of objects in the order in which data
|
||||||
|
* should be displayed; each element is an object with
|
||||||
|
* two properties:
|
||||||
|
*
|
||||||
|
* * objectIndex: The index of the domain object associated
|
||||||
|
* with the data point to be displayed in that
|
||||||
|
* row.
|
||||||
|
* * pointIndex: The index of the data point itself, within
|
||||||
|
* its data set.
|
||||||
|
*
|
||||||
|
* @param {Array<Telemetry>} datas an array of the most recent
|
||||||
|
* data objects; expected to be in the same order
|
||||||
|
* as the domain objects provided at constructor
|
||||||
|
* @param {Array<ScrollingColumn}
|
||||||
|
*/
|
||||||
|
function getLatestDataValues(datas, count) {
|
||||||
|
var latest = [],
|
||||||
|
candidate,
|
||||||
|
candidateTime,
|
||||||
|
used = datas.map(function () { return 0; });
|
||||||
|
|
||||||
|
// This algorithm is O(nk) for n rows and k telemetry elements;
|
||||||
|
// one O(k) linear search for a max is made for each of n rows.
|
||||||
|
// This could be done in O(n lg k + k lg k), using a priority
|
||||||
|
// queue (where priority is max-finding) containing k initial
|
||||||
|
// values. For n rows, pop the max from the queue and replenish
|
||||||
|
// the queue with a value from the data at the same
|
||||||
|
// objectIndex, if available.
|
||||||
|
// But k is small, so this might not give an observable
|
||||||
|
// improvement in performance.
|
||||||
|
|
||||||
|
// Find the most recent unused data point (this will be used
|
||||||
|
// in a loop to find and the N most recent data points)
|
||||||
|
function findCandidate(data, i) {
|
||||||
|
var nextTime,
|
||||||
|
pointCount = data.getPointCount(),
|
||||||
|
pointIndex = pointCount - used[i] - 1;
|
||||||
|
if (data && pointIndex >= 0) {
|
||||||
|
nextTime = data.getDomainValue(pointIndex);
|
||||||
|
if (nextTime > candidateTime) {
|
||||||
|
candidateTime = nextTime;
|
||||||
|
candidate = {
|
||||||
|
objectIndex: i,
|
||||||
|
pointIndex: pointIndex
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble a list of the most recent data points
|
||||||
|
while (latest.length < count) {
|
||||||
|
// Reset variables pre-search
|
||||||
|
candidateTime = Number.NEGATIVE_INFINITY;
|
||||||
|
candidate = undefined;
|
||||||
|
|
||||||
|
// Linear search for most recent
|
||||||
|
datas.forEach(findCandidate);
|
||||||
|
|
||||||
|
if (candidate) {
|
||||||
|
// Record this data point - it is the most recent
|
||||||
|
latest.push(candidate);
|
||||||
|
|
||||||
|
// Track the data points used so we can look farther back
|
||||||
|
// in the data set on the next iteration
|
||||||
|
used[candidate.objectIndex] = used[candidate.objectIndex] + 1;
|
||||||
|
} else {
|
||||||
|
// Ran out of candidates; not enough data points
|
||||||
|
// available to fill all rows.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return latest;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
getHeaders: function () {
|
||||||
|
return columns.map(function (column) {
|
||||||
|
return column.getTitle();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getRows: function (datas, objects, count) {
|
||||||
|
var values = getLatestDataValues(datas, count);
|
||||||
|
|
||||||
|
// Each value will become a row, which will contain
|
||||||
|
// some value in each column (rendering by the
|
||||||
|
// column object itself)
|
||||||
|
return values.map(function (value) {
|
||||||
|
return columns.map(function (column) {
|
||||||
|
return column.getValue(
|
||||||
|
objects[value.objectIndex],
|
||||||
|
datas[value.objectIndex],
|
||||||
|
value.pointIndex
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ScrollingListPopulator;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
46
platform/features/scrolling/test/DomainColumnSpec.js
Normal file
46
platform/features/scrolling/test/DomainColumnSpec.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../src/DomainColumn"],
|
||||||
|
function (DomainColumn) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("A domain column", function () {
|
||||||
|
var mockDataSet,
|
||||||
|
testMetadata,
|
||||||
|
column;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockDataSet = jasmine.createSpyObj(
|
||||||
|
"data",
|
||||||
|
[ "getDomainValue" ]
|
||||||
|
);
|
||||||
|
testMetadata = {
|
||||||
|
key: "testKey",
|
||||||
|
name: "Test Name"
|
||||||
|
};
|
||||||
|
column = new DomainColumn(testMetadata);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reports a column header from domain metadata", function () {
|
||||||
|
expect(column.getTitle()).toEqual("Test Name");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("looks up data from a data set", function () {
|
||||||
|
column.getValue(undefined, mockDataSet, 42);
|
||||||
|
expect(mockDataSet.getDomainValue)
|
||||||
|
.toHaveBeenCalledWith(42, "testKey");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formats domain values as time", function () {
|
||||||
|
mockDataSet.getDomainValue.andReturn(402513731000);
|
||||||
|
expect(column.getValue(undefined, mockDataSet, 42))
|
||||||
|
.toEqual("1982-276 17:22:11");
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
37
platform/features/scrolling/test/NameColumnSpec.js
Normal file
37
platform/features/scrolling/test/NameColumnSpec.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../src/NameColumn"],
|
||||||
|
function (NameColumn) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("A name column", function () {
|
||||||
|
var mockDomainObject,
|
||||||
|
column;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
|
"domainObject",
|
||||||
|
[ "getModel" ]
|
||||||
|
);
|
||||||
|
mockDomainObject.getModel.andReturn({
|
||||||
|
name: "Test object name"
|
||||||
|
});
|
||||||
|
column = new NameColumn();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reports a column header", function () {
|
||||||
|
expect(column.getTitle()).toEqual("Name");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("looks up name from an object's model", function () {
|
||||||
|
expect(column.getValue(mockDomainObject))
|
||||||
|
.toEqual("Test object name");
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
45
platform/features/scrolling/test/RangeColumnSpec.js
Normal file
45
platform/features/scrolling/test/RangeColumnSpec.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../src/RangeColumn"],
|
||||||
|
function (RangeColumn) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("A range column", function () {
|
||||||
|
var mockDataSet,
|
||||||
|
testMetadata,
|
||||||
|
column;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockDataSet = jasmine.createSpyObj(
|
||||||
|
"data",
|
||||||
|
[ "getRangeValue" ]
|
||||||
|
);
|
||||||
|
testMetadata = {
|
||||||
|
key: "testKey",
|
||||||
|
name: "Test Name"
|
||||||
|
};
|
||||||
|
column = new RangeColumn(testMetadata);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reports a column header from range metadata", function () {
|
||||||
|
expect(column.getTitle()).toEqual("Test Name");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("looks up data from a data set", function () {
|
||||||
|
column.getValue(undefined, mockDataSet, 42);
|
||||||
|
expect(mockDataSet.getRangeValue)
|
||||||
|
.toHaveBeenCalledWith(42, "testKey");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formats range values as time", function () {
|
||||||
|
mockDataSet.getRangeValue.andReturn(123.45678);
|
||||||
|
expect(column.getValue(undefined, mockDataSet, 42))
|
||||||
|
.toEqual("123.457");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,89 @@
|
|||||||
|
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../src/ScrollingListController"],
|
||||||
|
function (ScrollingListController) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("The scrolling list controller", function () {
|
||||||
|
var mockScope,
|
||||||
|
mockTelemetry,
|
||||||
|
testMetadata,
|
||||||
|
controller;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockScope = jasmine.createSpyObj(
|
||||||
|
"$scope",
|
||||||
|
[ "$on", "$watch" ]
|
||||||
|
);
|
||||||
|
mockTelemetry = jasmine.createSpyObj(
|
||||||
|
"telemetryController",
|
||||||
|
[ "getResponse", "getMetadata", "getTelemetryObjects" ]
|
||||||
|
);
|
||||||
|
testMetadata = [
|
||||||
|
{
|
||||||
|
domains: [
|
||||||
|
{ key: "d0", name: "D0" },
|
||||||
|
{ key: "d1", name: "D1" }
|
||||||
|
],
|
||||||
|
ranges: [
|
||||||
|
{ key: "r0", name: "R0" },
|
||||||
|
{ key: "r1", name: "R1" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
domains: [
|
||||||
|
{ key: "d0", name: "D0" },
|
||||||
|
{ key: "d2", name: "D2" }
|
||||||
|
],
|
||||||
|
ranges: [
|
||||||
|
{ key: "r0", name: "R0" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
mockTelemetry.getMetadata.andReturn(testMetadata);
|
||||||
|
mockTelemetry.getResponse.andReturn([]);
|
||||||
|
mockTelemetry.getTelemetryObjects.andReturn([]);
|
||||||
|
mockScope.telemetry = mockTelemetry;
|
||||||
|
controller = new ScrollingListController(mockScope);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("listens for telemetry data updates", function () {
|
||||||
|
expect(mockScope.$on).toHaveBeenCalledWith(
|
||||||
|
"telemetryUpdate",
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("watches for telemetry controller changes", function () {
|
||||||
|
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||||
|
"telemetry",
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides a column for each name and each unique domain, range", function () {
|
||||||
|
// Should have six columns based on metadata above,
|
||||||
|
// (name, d0, d1, d2, r0, r1)
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockTelemetry);
|
||||||
|
expect(mockScope.headers).toEqual(["Name", "D0", "D1", "D2", "R0", "R1"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not throw if telemetry controller is undefined", function () {
|
||||||
|
// Just a general robustness check
|
||||||
|
mockScope.telemetry = undefined;
|
||||||
|
expect(mockScope.$watch.mostRecentCall.args[1])
|
||||||
|
.not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides default columns if domain/range metadata is unavailable", function () {
|
||||||
|
mockTelemetry.getMetadata.andReturn([]);
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockTelemetry);
|
||||||
|
expect(mockScope.headers).toEqual(["Name", "Time", "Value"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,84 @@
|
|||||||
|
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../src/ScrollingListPopulator"],
|
||||||
|
function (ScrollingListPopulator) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("The scrolling list populator", function () {
|
||||||
|
var mockColumns,
|
||||||
|
mockDatas,
|
||||||
|
mockDomainObjects,
|
||||||
|
populator;
|
||||||
|
|
||||||
|
function makeMockColumn(name, index) {
|
||||||
|
var mockColumn = jasmine.createSpyObj(
|
||||||
|
"column" + index,
|
||||||
|
[ "getTitle", "getValue" ]
|
||||||
|
);
|
||||||
|
mockColumn.getTitle.andReturn(name);
|
||||||
|
mockColumn.getValue.andCallFake(function (obj, data, i) {
|
||||||
|
return data.getDomainValue(i);
|
||||||
|
});
|
||||||
|
return mockColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeMockData(bias, index) {
|
||||||
|
var mockData = jasmine.createSpyObj(
|
||||||
|
"data" + index,
|
||||||
|
[ "getDomainValue", "getPointCount" ]
|
||||||
|
);
|
||||||
|
mockData.getPointCount.andReturn(1000);
|
||||||
|
mockData.getDomainValue.andCallFake(function (i) {
|
||||||
|
return i + bias;
|
||||||
|
});
|
||||||
|
return mockData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeMockDomainObject(name, index) {
|
||||||
|
var mockDomainObject = jasmine.createSpyObj(
|
||||||
|
"domainObject" + index,
|
||||||
|
[ "getId", "getModel" ]
|
||||||
|
);
|
||||||
|
return mockDomainObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockColumns = ["A", "B", "C", "D"].map(makeMockColumn);
|
||||||
|
mockDatas = [ 10, 0, 3 ].map(makeMockData);
|
||||||
|
mockDomainObjects = ["A", "B", "C"].map(makeMockDomainObject);
|
||||||
|
populator = new ScrollingListPopulator(mockColumns);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns column headers", function () {
|
||||||
|
expect(populator.getHeaders()).toEqual(["A", "B", "C", "D"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides rows on request, with all columns in each row", function () {
|
||||||
|
var rows = populator.getRows(mockDatas, mockDomainObjects, 84);
|
||||||
|
expect(rows.length).toEqual(84);
|
||||||
|
rows.forEach(function (row) {
|
||||||
|
expect(row.length).toEqual(4); // number of columns
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns rows in reverse domain order", function () {
|
||||||
|
var rows = populator.getRows(mockDatas, mockDomainObjects, 84),
|
||||||
|
previous = Number.POSITIVE_INFINITY;
|
||||||
|
|
||||||
|
// Should always be most-recent-first; since the mockColumn
|
||||||
|
// returns the domain value, column contents should be
|
||||||
|
// non-increasing.
|
||||||
|
rows.forEach(function (row) {
|
||||||
|
expect(row[0]).not.toBeGreaterThan(previous);
|
||||||
|
previous = row[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
7
platform/features/scrolling/test/suite.json
Normal file
7
platform/features/scrolling/test/suite.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
"DomainColumn",
|
||||||
|
"NameColumn",
|
||||||
|
"RangeColumn",
|
||||||
|
"ScrollingListController",
|
||||||
|
"ScrollingListPopulator"
|
||||||
|
]
|
Reference in New Issue
Block a user