diff --git a/platform/features/conductor/utcTimeSystem/bundle.js b/platform/features/conductor/utcTimeSystem/bundle.js
index 806087e2ea..5db4bd968f 100644
--- a/platform/features/conductor/utcTimeSystem/bundle.js
+++ b/platform/features/conductor/utcTimeSystem/bundle.js
@@ -22,8 +22,7 @@
define([
"./src/UTCTimeSystem",
- 'legacyRegistry',
- 'openmct'
+ "legacyRegistry"
], function (
UTCTimeSystem,
legacyRegistry
diff --git a/platform/features/table/res/templates/telemetry-table.html b/platform/features/table/res/templates/telemetry-table.html
index 24c6a7702f..5bda288f1c 100644
--- a/platform/features/table/res/templates/telemetry-table.html
+++ b/platform/features/table/res/templates/telemetry-table.html
@@ -1,6 +1,5 @@
- Request time: {{tableController.lastRequestTime | date : 'HH:mm:ss:sss'}}
0){
+ if (discarded && discarded.length > 0) {
+ /**
+ * A `discarded` event is thrown when telemetry data fall out of
+ * bounds due to a bounds change event
+ * @type {object[]} discarded the telemetry data
+ * discarded as a result of the bounds change
+ */
this.emit('discarded', discarded);
}
if (added && added.length > 0) {
+ /**
+ * An `added` event is thrown when a bounds change results in
+ * received telemetry falling within the new bounds.
+ * @type {object[]} added the telemetry data that is now within bounds
+ */
this.emit('added', added);
}
this.lastBounds = bounds;
};
- TelemetryCollection.prototype.inBounds = function (element) {
+ /**
+ * Determines is a given telemetry datum is within the bounds currently
+ * defined for this telemetry collection.
+ * @private
+ * @param datum
+ * @returns {boolean}
+ */
+ TelemetryCollection.prototype.inBounds = function (datum) {
var noBoundsDefined = !this.lastBounds || (this.lastBounds.start === undefined && this.lastBounds.end === undefined);
var withinBounds =
- _.get(element, this.sortField) >= this.lastBounds.start &&
- _.get(element, this.sortField) <= this.lastBounds.end;
+ _.get(datum, this.sortField) >= this.lastBounds.start &&
+ _.get(datum, this.sortField) <= this.lastBounds.end;
return noBoundsDefined || withinBounds;
};
/**
+ * Adds an individual item to the collection. Used internally only
* @private
- * @param element
+ * @param item
*/
- TelemetryCollection.prototype.addOne = function (element) {
+ TelemetryCollection.prototype.addOne = function (item) {
var isDuplicate = false;
- var boundsDefined = this.lastBounds && (this.lastBounds.start && this.lastBounds.end);
+ var boundsDefined = this.lastBounds &&
+ (this.lastBounds.start !== undefined && this.lastBounds.end !== undefined);
var array;
+ var boundsLow;
+ var boundsHigh;
+
+ // If collection is not sorted by a time field, we cannot respond to
+ // bounds events, so no bounds checking necessary
+ if (this.sortField === undefined) {
+ this.telemetry.push(item);
+ return true;
+ }
// Insert into either in-bounds array, or the out of bounds high buffer.
// Data in the high buffer will be re-evaluated for possible insertion on next tick
if (boundsDefined) {
- var boundsHigh = _.get(element, this.sortField) > this.lastBounds.end;
- var boundsLow = _.get(element, this.sortField) < this.lastBounds.start;
+ boundsHigh = _.get(item, this.sortField) > this.lastBounds.end;
+ boundsLow = _.get(item, this.sortField) < this.lastBounds.start;
if (!boundsHigh && !boundsLow) {
array = this.telemetry;
@@ -119,26 +166,26 @@ define(
// If out of bounds low, disregard data
if (!boundsLow) {
+
// Going to check for duplicates. Bound the search problem to
- // elements around the given time. Use sortedIndex because it
+ // items around the given time. Use sortedIndex because it
// employs a binary search which is O(log n). Can use binary search
// based on time stamp because the array is guaranteed ordered due
// to sorted insertion.
-
- var startIx = _.sortedIndex(array, element, this.sortField);
+ var startIx = _.sortedIndex(array, item, this.sortField);
if (startIx !== array.length) {
- var endIx = _.sortedLastIndex(array, element, this.sortField);
+ var endIx = _.sortedLastIndex(array, item, this.sortField);
// Create an array of potential dupes, based on having the
// same time stamp
var potentialDupes = array.slice(startIx, endIx + 1);
// Search potential dupes for exact dupe
- isDuplicate = _.findIndex(potentialDupes, _.isEqual.bind(undefined, element)) > -1;
+ isDuplicate = _.findIndex(potentialDupes, _.isEqual.bind(undefined, item)) > -1;
}
if (!isDuplicate) {
- array.splice(startIx, 0, element);
+ array.splice(startIx, 0, item);
//Return true if it was added and in bounds
return array === this.telemetry;
@@ -147,28 +194,60 @@ define(
return false;
};
- TelemetryCollection.prototype.addAll = function (elements) {
- var added = elements.filter(this.addOne);
+ /**
+ * Add an array of objects to this telemetry collection
+ * @fires TelemetryCollection#added
+ * @param {object[]} items
+ */
+ TelemetryCollection.prototype.add = function (items) {
+ var added = items.filter(this.addOne);
this.emit('added', added);
};
- //Todo: addAll for initial historical data
- TelemetryCollection.prototype.add = function (element) {
- if (this.addOne(element)){
- this.emit('added', [element]);
- return true;
- } else {
- return false;
- }
- };
-
+ /**
+ * Clears the contents of the telemetry collection
+ */
TelemetryCollection.prototype.clear = function () {
this.telemetry = [];
};
- TelemetryCollection.prototype.sort = function (sortField){
+ /**
+ * Sorts the telemetry collection based on the provided sort field
+ * specifier.
+ * @example
+ * // First build some mock telemetry for the purpose of an example
+ * let now = Date.now();
+ * let telemetry = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(function (value) {
+ * return {
+ * // define an object property to demonstrate nested paths
+ * timestamp: {
+ * ms: now - value * 1000,
+ * text:
+ * },
+ * value: value
+ * }
+ * });
+ * let collection = new TelemetryCollection();
+ *
+ * collection.add(telemetry);
+ *
+ * // Sort by telemetry value
+ * collection.sort("value");
+ *
+ * // Sort by ms since epoch
+ * collection.sort("timestamp.ms");
+ *
+ * // Sort by formatted date text
+ * collection.sort("timestamp.text");
+ *
+ *
+ * @param {string} sortField An object property path.
+ */
+ TelemetryCollection.prototype.sort = function (sortField) {
this.sortField = sortField;
- this.telemetry = _.sortBy(this.telemetry, this.iteratee);
+ if (sortField !== undefined) {
+ this.telemetry = _.sortBy(this.telemetry, this.iteratee);
+ }
};
return TelemetryCollection;
diff --git a/platform/features/table/src/controllers/MCTTableController.js b/platform/features/table/src/controllers/MCTTableController.js
index af942f8b55..c987a0c3b8 100644
--- a/platform/features/table/src/controllers/MCTTableController.js
+++ b/platform/features/table/src/controllers/MCTTableController.js
@@ -1,7 +1,10 @@
define(
- ['zepto'],
- function ($) {
+ [
+ 'zepto',
+ 'lodash'
+ ],
+ function ($, _) {
/**
* A controller for the MCTTable directive. Populates scope with
@@ -134,7 +137,7 @@ define(
*/
$scope.$watch('defaultSort', function (newColumn, oldColumn) {
if (newColumn !== oldColumn) {
- $scope.toggleSort(newColumn)
+ $scope.toggleSort(newColumn);
}
});
@@ -163,7 +166,7 @@ define(
}
}.bind(this));
- $scope.$on('$destroy', function() {
+ $scope.$on('$destroy', function () {
this.scrollable.off('scroll', this.onScroll);
this.destroyConductorListeners();
@@ -172,7 +175,7 @@ define(
delete this.$scope;
}.bind(this));
- };
+ }
MCTTableController.prototype.destroyConductorListeners = function () {
this.conductor.off('timeSystem', this.changeTimeSystem);
@@ -227,7 +230,7 @@ define(
*/
MCTTableController.prototype.removeRows = function (event, rows) {
var indexInDisplayRows;
- rows.forEach(function (row){
+ rows.forEach(function (row) {
// Do a sequential search here. Only way of finding row is by
// object equality, so array is in effect unsorted.
indexInDisplayRows = this.$scope.displayRows.indexOf(row);
@@ -252,7 +255,7 @@ define(
* @private
*/
MCTTableController.prototype.onScroll = function (event) {
- requestAnimationFrame(function () {
+ this.$window.requestAnimationFrame(function () {
this.setVisibleRows();
this.digest();
@@ -344,18 +347,11 @@ define(
this.$scope.visibleRows[this.$scope.visibleRows.length - 1]
.rowIndex === end) {
return this.digest();
- //return Promise.resolve(); // don't update if no changes are required.
}
}
//Set visible rows from display rows, based on calculated offset.
this.$scope.visibleRows = this.$scope.displayRows.slice(start, end)
.map(function (row, i) {
-/* var formattedRow = JSON.parse(JSON.stringify(row));
- if (self.$scope.formatCell) {
- Object.keys(formattedRow).forEach(function (header) {
- formattedRow[header].text = self.$scope.formatCell(header, row[header].text);
- });
- } */
return {
rowIndex: start + i,
offsetY: ((start + i) * self.$scope.rowHeight) +
@@ -585,19 +581,20 @@ define(
MCTTableController.prototype.digest = function () {
var scope = this.$scope;
var self = this;
- var requestAnimationFrame = this.$window.requestAnimationFrame;
+ var raf = this.$window.requestAnimationFrame;
+ var promise = this.digestPromise;
- if (!this.digestPromise) {
- this.digestPromise = new Promise(function (resolve) {
- requestAnimationFrame(function() {
+ if (!promise) {
+ self.digestPromise = promise = new Promise(function (resolve) {
+ raf(function () {
scope.$digest();
- delete self.digestPromise;
+ self.digestPromise = undefined;
resolve();
});
});
}
- return this.digestPromise;
+ return promise;
};
/**
@@ -640,8 +637,10 @@ define(
}
this.$scope.displayRows = this.filterAndSort(newRows || []);
- this.resize(newRows)
- .then(this.setVisibleRows)
+ return this.resize(newRows)
+ .then(function (rows) {
+ return this.setVisibleRows(rows);
+ }.bind(this))
//Timeout following setVisibleRows to allow digest to
// perform DOM changes, otherwise scrollTo won't work.
.then(function () {
@@ -692,6 +691,7 @@ define(
};
/**
+ * Scroll the view to a given row index
* @param displayRowIndex {number} The index in the displayed rows
* to scroll to.
*/
diff --git a/platform/features/table/src/controllers/TelemetryTableController.js b/platform/features/table/src/controllers/TelemetryTableController.js
index 7e8fe1fe29..5beb49cb7e 100644
--- a/platform/features/table/src/controllers/TelemetryTableController.js
+++ b/platform/features/table/src/controllers/TelemetryTableController.js
@@ -19,6 +19,7 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
+/* global console*/
/**
* This bundle adds a table view for displaying telemetry data.
@@ -28,10 +29,11 @@ define(
[
'../TableConfiguration',
'../../../../../src/api/objects/object-utils',
- '../TelemetryCollection'
+ '../TelemetryCollection',
+ 'lodash'
],
- function (TableConfiguration, objectUtils, TelemetryCollection) {
+ function (TableConfiguration, objectUtils, TelemetryCollection, _) {
/**
* The TableController is responsible for getting data onto the page
@@ -46,6 +48,7 @@ define(
$timeout,
openmct
) {
+
this.$scope = $scope;
this.$timeout = $timeout;
this.openmct = openmct;
@@ -55,14 +58,14 @@ define(
* Initialization block
*/
this.columns = {}; //Range and Domain columns
- this.deregisterListeners = [];
+ this.unobserveObject = undefined;
this.subscriptions = [];
this.timeColumns = [];
$scope.rows = [];
this.table = new TableConfiguration($scope.domainObject,
openmct);
this.lastBounds = this.openmct.conductor.bounds();
- this.requestTime = 0;
+ this.lastRequestTime = 0;
this.telemetry = new TelemetryCollection();
/*
@@ -81,38 +84,45 @@ define(
'changeBounds',
'setScroll',
'addRowsToTable',
- 'removeRowsFromTable',
+ 'removeRowsFromTable'
]);
- this.getData();
- this.registerChangeListeners();
+ // Retrieve data when domain object is available.
+ // Also deferring telemetry request makes testing easier as controller
+ // construction has no unintended consequences.
+ $scope.$watch("domainObject", function () {
+ this.getData();
+ this.registerChangeListeners();
+ }.bind(this));
- this.openmct.conductor.on('follow', this.setScroll);
this.setScroll(this.openmct.conductor.follow());
- this.telemetry.on('added', this.addRowsToTable);
- this.telemetry.on('discarded', this.removeRowsFromTable);
-
this.$scope.$on("$destroy", this.destroy);
}
- TelemetryTableController.prototype.setScroll = function (scroll){
+ /**
+ * @private
+ * @param {boolean} scroll
+ */
+ TelemetryTableController.prototype.setScroll = function (scroll) {
this.$scope.autoScroll = scroll;
};
/**
* Based on the selected time system, find a matching domain column
* to sort by. By default will just match on key.
- * @param timeSystem
+ *
+ * @private
+ * @param {TimeSystem} timeSystem
*/
TelemetryTableController.prototype.sortByTimeSystem = function (timeSystem) {
var scope = this.$scope;
- var sortColumn = undefined;
+ var sortColumn;
scope.defaultSort = undefined;
if (timeSystem) {
this.table.columns.forEach(function (column) {
- if (column.metadata.key === timeSystem.metadata.key) {
+ if (column.getKey() === timeSystem.metadata.key) {
sortColumn = column;
}
});
@@ -124,44 +134,66 @@ define(
};
/**
- * Attach listeners to domain object to respond to changes due to
- * composition, etc.
+ * Attaches listeners that respond to state change in domain object,
+ * conductor, and receipt of telemetry
+ *
* @private
*/
TelemetryTableController.prototype.registerChangeListeners = function () {
- this.deregisterListeners.forEach(function (deregister){
- deregister();
- });
- this.deregisterListeners = [];
+ if (this.unobserveObject) {
+ this.unobserveObject();
+ }
- this.deregisterListeners.push(
- this.openmct.objects.observe(this.newObject, "*",
- function (domainObject){
+ this.unobserveObject = this.openmct.objects.observe(this.newObject, "*",
+ function (domainObject) {
this.newObject = domainObject;
this.getData();
}.bind(this)
- )
- );
+ );
+
this.openmct.conductor.on('timeSystem', this.sortByTimeSystem);
this.openmct.conductor.on('bounds', this.changeBounds);
+ this.openmct.conductor.on('follow', this.setScroll);
+
+ this.telemetry.on('added', this.addRowsToTable);
+ this.telemetry.on('discarded', this.removeRowsFromTable);
};
+ /**
+ * On receipt of new telemetry, informs mct-table directive that new rows
+ * are available and passes populated rows to it
+ *
+ * @private
+ * @param rows
+ */
TelemetryTableController.prototype.addRowsToTable = function (rows) {
this.$scope.$broadcast('add:rows', rows);
};
+ /**
+ * When rows are to be removed, informs mct-table directive. Row removal
+ * happens when rows call outside the bounds of the time conductor
+ *
+ * @private
+ * @param rows
+ */
TelemetryTableController.prototype.removeRowsFromTable = function (rows) {
this.$scope.$broadcast('remove:rows', rows);
};
+ /**
+ * On Time Conductor bounds change, update displayed telemetry. In the
+ * case of a tick, previously visible telemetry that is now out of band
+ * will be removed from the table.
+ * @param {openmct.TimeConductorBounds~TimeConductorBounds} bounds
+ */
TelemetryTableController.prototype.changeBounds = function (bounds) {
- //console.log('bounds.end: ' + bounds.end);
var follow = this.openmct.conductor.follow();
var isTick = follow &&
bounds.start !== this.lastBounds.start &&
bounds.end !== this.lastBounds.end;
- if (isTick){
+ if (isTick) {
this.telemetry.bounds(bounds);
} else {
// Is fixed bounds change
@@ -171,7 +203,7 @@ define(
};
/**
- * Release the current subscription (called when scope is destroyed)
+ * Clean controller, deregistering listeners etc.
*/
TelemetryTableController.prototype.destroy = function () {
@@ -182,11 +214,11 @@ define(
this.subscriptions.forEach(function (subscription) {
subscription();
});
- this.deregisterListeners.forEach(function (deregister){
- deregister();
- });
+
+ if (this.unobserveObject) {
+ this.unobserveObject();
+ }
this.subscriptions = [];
- this.deregisterListeners = [];
if (this.timeoutHandle) {
this.$timeout.cancel(this.timeoutHandle);
@@ -200,9 +232,10 @@ define(
};
/**
+ * For given objects, populate column metadata and table headers.
* @private
- * @param objects
- * @returns {*}
+ * @param {module:openmct.DomainObject[]} objects the domain objects for
+ * which columns should be populated
*/
TelemetryTableController.prototype.loadColumns = function (objects) {
var telemetryApi = this.openmct.telemetry;
@@ -220,25 +253,28 @@ define(
this.filterColumns();
+ // Default to no sort on underlying telemetry collection. Sorting
+ // is necessary to do bounds filtering, but this is only possible
+ // if data matches selected time system
+ this.telemetry.sort(undefined);
+
var timeSystem = this.openmct.conductor.timeSystem();
if (timeSystem) {
this.sortByTimeSystem(timeSystem);
}
- if (!this.telemetry.sortColumn && domainColumns.length > 0) {
- this.telemetry.sort(domainColumns[0].name + '.value');
- }
-
}
return objects;
};
/**
+ * Request telemetry data from an historical store for given objects.
* @private
- * @param objects The domain objects to request telemetry for
- * @returns {*|{configFile}|app|boolean|Route|Object}
+ * @param {object[]} The domain objects to request telemetry for
+ * @returns {Promise} resolved when historical data is available
*/
TelemetryTableController.prototype.getHistoricalData = function (objects) {
+ var self = this;
var openmct = this.openmct;
var bounds = openmct.conductor.bounds();
var scope = this.$scope;
@@ -247,15 +283,22 @@ define(
var requestTime = this.lastRequestTime = Date.now();
var telemetryCollection = this.telemetry;
- return new Promise(function (resolve, reject){
- function finishProcessing(){
- telemetryCollection.addAll(rowData);
+ var promise = new Promise(function (resolve, reject) {
+ /*
+ * On completion of batched processing, set the rows on scope
+ */
+ function finishProcessing() {
+ telemetryCollection.add(rowData);
scope.rows = telemetryCollection.telemetry;
scope.loading = false;
+
resolve(scope.rows);
}
- function processData(historicalData, index, limitEvaluator){
+ /*
+ * Process a batch of historical data
+ */
+ function processData(historicalData, index, limitEvaluator) {
if (index >= historicalData.length) {
processedObjects++;
@@ -263,51 +306,57 @@ define(
finishProcessing();
}
} else {
- rowData = rowData.concat(historicalData.slice(index, index + this.batchSize)
- .map(this.table.getRowValues.bind(this.table, limitEvaluator)));
+ rowData = rowData.concat(historicalData.slice(index, index + self.batchSize)
+ .map(self.table.getRowValues.bind(self.table, limitEvaluator)));
- this.timeoutHandle = this.$timeout(processData.bind(
- this,
- historicalData,
- index + this.batchSize,
- limitEvaluator
- ));
+ /*
+ Use timeout to yield process to other UI activities. On
+ return, process next batch
+ */
+ self.timeoutHandle = self.$timeout(function () {
+ processData(historicalData, index + self.batchSize, limitEvaluator);
+ });
}
}
function makeTableRows(object, historicalData) {
- // Only process one request at a time
- if (requestTime === this.lastRequestTime) {
+ // Only process the most recent request
+ if (requestTime === self.lastRequestTime) {
var limitEvaluator = openmct.telemetry.limitEvaluator(object);
- processData.call(this, historicalData, 0, limitEvaluator);
+ processData(historicalData, 0, limitEvaluator);
} else {
resolve(rowData);
}
}
- function requestData (object) {
+ /*
+ Use the telemetry API to request telemetry for a given object
+ */
+ function requestData(object) {
return openmct.telemetry.request(object, {
start: bounds.start,
end: bounds.end
- }).then(makeTableRows.bind(this, object))
+ }).then(makeTableRows.bind(undefined, object))
.catch(reject);
}
this.$timeout.cancel(this.timeoutHandle);
- if (objects.length > 0){
- objects.forEach(requestData.bind(this));
+ if (objects.length > 0) {
+ objects.forEach(requestData);
} else {
scope.loading = false;
resolve([]);
}
}.bind(this));
+
+ return promise;
};
/**
+ * Subscribe to real-time data for the given objects.
* @private
- * @param objects
- * @returns {*}
+ * @param {object[]} objects The objects to subscribe to.
*/
TelemetryTableController.prototype.subscribeToNewData = function (objects) {
var telemetryApi = this.openmct.telemetry;
@@ -317,6 +366,8 @@ define(
var maxRows = Number.MAX_VALUE;
var limitEvaluator;
var added = false;
+ var scope = this.$scope;
+ var table = this.table;
this.subscriptions.forEach(function (subscription) {
subscription();
@@ -325,20 +376,20 @@ define(
function newData(domainObject, datum) {
limitEvaluator = telemetryApi.limitEvaluator(domainObject);
- added = telemetryCollection.add(this.table.getRowValues(limitEvaluator, datum));
+ added = telemetryCollection.add([table.getRowValues(limitEvaluator, datum)]);
//Inform table that a new row has been added
- if (this.$scope.rows.length > maxRows) {
- this.$scope.$broadcast('remove:rows', this.$scope.rows[0]);
- this.$scope.rows.shift();
+ if (scope.rows.length > maxRows) {
+ scope.$broadcast('remove:rows', scope.rows[0]);
+ scope.rows.shift();
}
- if (!this.$scope.loading && added) {
- this.$scope.$broadcast('add:row',
- this.$scope.rows.length - 1);
+ if (!scope.loading && added) {
+ scope.$broadcast('add:row',
+ scope.rows.length - 1);
}
}
- objects.forEach(function (object){
+ objects.forEach(function (object) {
this.subscriptions.push(
telemetryApi.subscribe(object, newData.bind(this, object), {}));
}.bind(this));
@@ -346,6 +397,12 @@ define(
return objects;
};
+ /**
+ * Request historical data, and subscribe to for real-time data.
+ * @private
+ * @returns {Promise} A promise that is resolved once subscription is
+ * established, and historical telemetry is received and processed.
+ */
TelemetryTableController.prototype.getData = function () {
var telemetryApi = this.openmct.telemetry;
var compositionApi = this.openmct.composition;
@@ -359,41 +416,37 @@ define(
function error(e) {
scope.loading = false;
- console.error(e);
+ console.error(e.stack);
}
- function filterForTelemetry(objects){
+ function filterForTelemetry(objects) {
return objects.filter(telemetryApi.canProvideTelemetry.bind(telemetryApi));
}
function getDomainObjects() {
- return new Promise(function (resolve, reject){
- var objects = [newObject];
- var composition = compositionApi.get(newObject);
+ var objects = [newObject];
+ var composition = compositionApi.get(newObject);
- if (composition) {
- composition
- .load()
- .then(function (children) {
- return objects.concat(children);
- })
- .then(resolve)
- .catch(reject);
- } else {
- return resolve(objects);
- }
- });
+ if (composition) {
+ return composition
+ .load()
+ .then(function (children) {
+ return objects.concat(children);
+ });
+ } else {
+ return Promise.resolve(objects);
+ }
}
scope.headers = [];
scope.rows = [];
- getDomainObjects()
+ return getDomainObjects()
.then(filterForTelemetry)
.then(this.loadColumns)
.then(this.subscribeToNewData)
.then(this.getHistoricalData)
- .catch(error)
+ .catch(error);
};
/**
diff --git a/platform/features/table/test/DomainColumnSpec.js b/platform/features/table/test/DomainColumnSpec.js
deleted file mode 100644
index 3c144b8427..0000000000
--- a/platform/features/table/test/DomainColumnSpec.js
+++ /dev/null
@@ -1,80 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2016, United States Government
- * as represented by the Administrator of the National Aeronautics and Space
- * Administration. All rights reserved.
- *
- * Open MCT is 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.
- *
- * Open MCT includes source code licensed under additional open source
- * licenses. See the Open Source Licenses file (LICENSES.md) included with
- * this source code distribution or the Licensing information page available
- * at runtime from the About dialog for additional information.
- *****************************************************************************/
-
-/**
- * MergeModelsSpec. Created by vwoeltje on 11/6/14.
- */
-define(
- ["../src/DomainColumn"],
- function (DomainColumn) {
-
- var TEST_DOMAIN_VALUE = "some formatted domain value";
-
- describe("A domain column", function () {
- var mockDatum,
- testMetadata,
- mockFormatter,
- column;
-
- beforeEach(function () {
-
- mockFormatter = jasmine.createSpyObj(
- "formatter",
- ["formatDomainValue", "formatRangeValue"]
- );
- testMetadata = {
- key: "testKey",
- name: "Test Name",
- format: "Test Format"
- };
- mockFormatter.formatDomainValue.andReturn(TEST_DOMAIN_VALUE);
-
- column = new DomainColumn(testMetadata, mockFormatter);
- });
-
- it("reports a column header from domain metadata", function () {
- expect(column.getTitle()).toEqual("Test Name");
- });
-
- describe("when given a datum", function () {
- beforeEach(function () {
- mockDatum = {
- testKey: "testKeyValue"
- };
- });
-
- it("looks up data from the given datum", function () {
- expect(column.getValue(undefined, mockDatum))
- .toEqual({ text: TEST_DOMAIN_VALUE });
- });
-
- it("uses formatter to format domain values as requested", function () {
- column.getValue(undefined, mockDatum);
- expect(mockFormatter.formatDomainValue)
- .toHaveBeenCalledWith("testKeyValue", "Test Format");
- });
-
- });
-
- });
- }
-);
diff --git a/platform/features/table/test/NameColumnSpec.js b/platform/features/table/test/NameColumnSpec.js
deleted file mode 100644
index 13e858c2ed..0000000000
--- a/platform/features/table/test/NameColumnSpec.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2016, United States Government
- * as represented by the Administrator of the National Aeronautics and Space
- * Administration. All rights reserved.
- *
- * Open MCT is 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.
- *
- * Open MCT includes source code licensed under additional open source
- * licenses. See the Open Source Licenses file (LICENSES.md) included with
- * this source code distribution or the Licensing information page available
- * at runtime from the About dialog for additional information.
- *****************************************************************************/
-
-/**
- * MergeModelsSpec. Created by vwoeltje on 11/6/14.
- */
-define(
- ["../src/NameColumn"],
- function (NameColumn) {
-
- 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).text)
- .toEqual("Test object name");
- });
-
- });
- }
-);
diff --git a/platform/features/table/test/RangeColumnSpec.js b/platform/features/table/test/RangeColumnSpec.js
deleted file mode 100644
index 473f26ae56..0000000000
--- a/platform/features/table/test/RangeColumnSpec.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2016, United States Government
- * as represented by the Administrator of the National Aeronautics and Space
- * Administration. All rights reserved.
- *
- * Open MCT is 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.
- *
- * Open MCT includes source code licensed under additional open source
- * licenses. See the Open Source Licenses file (LICENSES.md) included with
- * this source code distribution or the Licensing information page available
- * at runtime from the About dialog for additional information.
- *****************************************************************************/
-
-/**
- * MergeModelsSpec. Created by vwoeltje on 11/6/14.
- */
-define(
- ["../src/RangeColumn"],
- function (RangeColumn) {
-
- var TEST_RANGE_VALUE = "some formatted range value";
-
- describe("A range column", function () {
- var testDatum,
- testMetadata,
- mockFormatter,
- mockDomainObject,
- column;
-
- beforeEach(function () {
- testDatum = { testKey: 123, otherKey: 456 };
- mockFormatter = jasmine.createSpyObj(
- "formatter",
- ["formatDomainValue", "formatRangeValue"]
- );
- testMetadata = {
- key: "testKey",
- name: "Test Name"
- };
- mockDomainObject = jasmine.createSpyObj(
- "domainObject",
- ["getModel", "getCapability"]
- );
- mockFormatter.formatRangeValue.andReturn(TEST_RANGE_VALUE);
-
- column = new RangeColumn(testMetadata, mockFormatter);
- });
-
- it("reports a column header from range metadata", function () {
- expect(column.getTitle()).toEqual("Test Name");
- });
-
- it("formats range values as numbers", function () {
- expect(column.getValue(mockDomainObject, testDatum).text)
- .toEqual(TEST_RANGE_VALUE);
-
- // Make sure that service interactions were as expected
- expect(mockFormatter.formatRangeValue)
- .toHaveBeenCalledWith(testDatum.testKey);
- expect(mockFormatter.formatDomainValue)
- .not.toHaveBeenCalled();
- });
- });
- }
-);
diff --git a/platform/features/table/test/TableConfigurationSpec.js b/platform/features/table/test/TableConfigurationSpec.js
index db90cc1c62..af4f2ee2a8 100644
--- a/platform/features/table/test/TableConfigurationSpec.js
+++ b/platform/features/table/test/TableConfigurationSpec.js
@@ -22,13 +22,14 @@
define(
[
- "../src/TableConfiguration",
- "../src/DomainColumn"
+ "../src/TableConfiguration"
],
- function (Table, DomainColumn) {
+ function (Table) {
describe("A table", function () {
var mockDomainObject,
+ mockAPI,
+ mockTelemetryAPI,
mockTelemetryFormatter,
table,
mockModel;
@@ -41,90 +42,63 @@ define(
mockDomainObject.getModel.andReturn(mockModel);
mockTelemetryFormatter = jasmine.createSpyObj('telemetryFormatter',
[
- 'formatDomainValue',
- 'formatRangeValue'
+ 'format'
]);
- mockTelemetryFormatter.formatDomainValue.andCallFake(function (valueIn) {
- return valueIn;
- });
- mockTelemetryFormatter.formatRangeValue.andCallFake(function (valueIn) {
+ mockTelemetryFormatter.format.andCallFake(function (valueIn) {
return valueIn;
});
- table = new Table(mockDomainObject, mockTelemetryFormatter);
- });
+ mockTelemetryAPI = jasmine.createSpyObj('telemetryAPI', [
+ 'getValueFormatter'
+ ]);
+ mockAPI = {
+ telemetry: mockTelemetryAPI
+ };
+ mockTelemetryAPI.getValueFormatter.andReturn(mockTelemetryFormatter);
- it("Add column with no index adds new column to the end", function () {
- var firstColumn = {title: 'First Column'},
- secondColumn = {title: 'Second Column'},
- thirdColumn = {title: 'Third Column'};
-
- table.addColumn(firstColumn);
- table.addColumn(secondColumn);
- table.addColumn(thirdColumn);
-
- expect(table.columns).toBeDefined();
- expect(table.columns.length).toBe(3);
- expect(table.columns[0]).toBe(firstColumn);
- expect(table.columns[1]).toBe(secondColumn);
- expect(table.columns[2]).toBe(thirdColumn);
- });
-
- it("Add column with index adds new column at the specified" +
- " position", function () {
- var firstColumn = {title: 'First Column'},
- secondColumn = {title: 'Second Column'},
- thirdColumn = {title: 'Third Column'};
-
- table.addColumn(firstColumn);
- table.addColumn(thirdColumn);
- table.addColumn(secondColumn, 1);
-
- expect(table.columns).toBeDefined();
- expect(table.columns.length).toBe(3);
- expect(table.columns[0]).toBe(firstColumn);
- expect(table.columns[1]).toBe(secondColumn);
- expect(table.columns[2]).toBe(thirdColumn);
+ table = new Table(mockDomainObject, mockAPI);
});
describe("Building columns from telemetry metadata", function () {
- var metadata = [{
- ranges: [
- {
- name: 'Range 1',
- key: 'range1'
- },
- {
- name: 'Range 2',
- key: 'range2'
+ var metadata = [
+ {
+ name: 'Range 1',
+ key: 'range1',
+ hints: {
+ y: 1
}
- ],
- domains: [
- {
- name: 'Domain 1',
- key: 'domain1',
- format: 'utc'
- },
- {
- name: 'Domain 2',
- key: 'domain2',
- format: 'utc'
+ },
+ {
+ name: 'Range 2',
+ key: 'range2',
+ hints: {
+ y: 2
}
- ]
- }];
+ },
+ {
+ name: 'Domain 1',
+ key: 'domain1',
+ format: 'utc',
+ hints: {
+ x: 1
+ }
+ },
+ {
+ name: 'Domain 2',
+ key: 'domain2',
+ format: 'utc',
+ hints: {
+ x: 2
+ }
+ }
+ ];
beforeEach(function () {
table.populateColumns(metadata);
});
it("populates columns", function () {
- expect(table.columns.length).toBe(5);
- });
-
- it("Build columns populates columns with domains to the left", function () {
- expect(table.columns[1] instanceof DomainColumn).toBeTruthy();
- expect(table.columns[2] instanceof DomainColumn).toBeTruthy();
- expect(table.columns[3] instanceof DomainColumn).toBeFalsy();
+ expect(table.columns.length).toBe(4);
});
it("Produces headers for each column based on title", function () {
@@ -133,7 +107,7 @@ define(
spyOn(firstColumn, 'getTitle');
headers = table.getHeaders();
- expect(headers.length).toBe(5);
+ expect(headers.length).toBe(4);
expect(firstColumn.getTitle).toHaveBeenCalled();
});
@@ -170,23 +144,33 @@ define(
beforeEach(function () {
datum = {
- 'range1': 'range 1 value',
- 'range2': 'range 2 value',
+ 'range1': 10,
+ 'range2': 20,
'domain1': 0,
'domain2': 1
};
- rowValues = table.getRowValues(mockDomainObject, datum);
+ var limitEvaluator = {
+ evaluate: function () {
+ return {
+ "cssClass": "alarm-class"
+ };
+ }
+ };
+ rowValues = table.getRowValues(limitEvaluator, datum);
});
it("Returns a value for every column", function () {
expect(rowValues['Range 1'].text).toBeDefined();
- expect(rowValues['Range 1'].text).toEqual('range 1' +
- ' value');
+ expect(rowValues['Range 1'].text).toEqual(10);
});
- it("Uses the telemetry formatter to appropriately format" +
+ it("Applies appropriate css class if limit violated.", function () {
+ expect(rowValues['Range 1'].cssClass).toEqual("alarm-class");
+ });
+
+ it("Uses telemetry formatter to appropriately format" +
" telemetry values", function () {
- expect(mockTelemetryFormatter.formatRangeValue).toHaveBeenCalled();
+ expect(mockTelemetryFormatter.format).toHaveBeenCalled();
});
});
});
diff --git a/platform/features/table/test/TelemetryCollectionSpec.js b/platform/features/table/test/TelemetryCollectionSpec.js
new file mode 100644
index 0000000000..014e5c684e
--- /dev/null
+++ b/platform/features/table/test/TelemetryCollectionSpec.js
@@ -0,0 +1,191 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2016, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is 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.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+define(
+ [
+ "../src/TelemetryCollection"
+ ],
+ function (TelemetryCollection) {
+
+ describe("A telemetry collection", function () {
+
+ var collection;
+ var telemetryObjects;
+ var ms;
+ var integerTextMap = ["ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE",
+ "SIX", "SEVEN", "EIGHT", "NINE", "TEN", "ELEVEN"];
+
+ beforeEach(function () {
+ telemetryObjects = [0,9,2,4,7,8,5,1,3,6].map(function (number) {
+ ms = number * 1000;
+ return {
+ timestamp: ms,
+ value: {
+ integer: number,
+ text: integerTextMap[number]
+ }
+ };
+ });
+ collection = new TelemetryCollection();
+ });
+
+ it("Sorts inserted telemetry by specified field",
+ function () {
+ collection.sort('value.integer');
+ collection.add(telemetryObjects);
+ expect(collection.telemetry[0].value.integer).toBe(0);
+ expect(collection.telemetry[1].value.integer).toBe(1);
+ expect(collection.telemetry[2].value.integer).toBe(2);
+ expect(collection.telemetry[3].value.integer).toBe(3);
+
+ collection.sort('value.text');
+ expect(collection.telemetry[0].value.text).toBe("EIGHT");
+ expect(collection.telemetry[1].value.text).toBe("FIVE");
+ expect(collection.telemetry[2].value.text).toBe("FOUR");
+ expect(collection.telemetry[3].value.text).toBe("NINE");
+ }
+ );
+
+ describe("on bounds change", function () {
+ var discardedCallback;
+
+ beforeEach(function () {
+ discardedCallback = jasmine.createSpy("discarded");
+ collection.on("discarded", discardedCallback);
+ collection.sort("timestamp");
+ collection.add(telemetryObjects);
+ collection.bounds({start: 5000, end: 8000});
+ });
+
+
+ it("emits an event indicating that telemetry has " +
+ "been discarded", function () {
+ expect(discardedCallback).toHaveBeenCalled();
+ });
+
+ it("discards telemetry data with a time stamp " +
+ "before specified start bound", function () {
+ var discarded = discardedCallback.mostRecentCall.args[0];
+
+ // Expect 5 because as an optimization, the TelemetryCollection
+ // will not consider telemetry values that exceed the upper
+ // bounds. Arbitrary bounds changes in which the end bound is
+ // decreased is assumed to require a new historical query, and
+ // hence re-population of the collection anyway
+ expect(discarded.length).toBe(5);
+ expect(discarded[0].value.integer).toBe(0);
+ expect(discarded[1].value.integer).toBe(1);
+ expect(discarded[4].value.integer).toBe(4);
+ });
+ });
+
+ describe("when adding telemetry to a collection", function () {
+ var addedCallback;
+ beforeEach(function () {
+ collection.sort("timestamp");
+ collection.add(telemetryObjects);
+ addedCallback = jasmine.createSpy("added");
+ collection.on("added", addedCallback);
+ });
+
+ it("emits an event",
+ function () {
+ var addedObject = {
+ timestamp: 10000,
+ value: {
+ integer: 10,
+ text: integerTextMap[10]
+ }
+ };
+ collection.add([addedObject]);
+ expect(addedCallback).toHaveBeenCalledWith([addedObject]);
+ }
+ );
+ it("inserts in the correct order",
+ function () {
+ var addedObjectA = {
+ timestamp: 10000,
+ value: {
+ integer: 10,
+ text: integerTextMap[10]
+ }
+ };
+ var addedObjectB = {
+ timestamp: 11000,
+ value: {
+ integer: 11,
+ text: integerTextMap[11]
+ }
+ };
+ collection.add([addedObjectB, addedObjectA]);
+
+ expect(collection.telemetry[11]).toBe(addedObjectB);
+ }
+ );
+ });
+
+ describe("buffers telemetry", function () {
+ var addedObjectA;
+ var addedObjectB;
+
+ beforeEach(function () {
+ collection.sort("timestamp");
+ collection.add(telemetryObjects);
+
+ addedObjectA = {
+ timestamp: 10000,
+ value: {
+ integer: 10,
+ text: integerTextMap[10]
+ }
+ };
+ addedObjectB = {
+ timestamp: 11000,
+ value: {
+ integer: 11,
+ text: integerTextMap[11]
+ }
+ };
+
+ collection.bounds({start: 0, end: 10000});
+ collection.add([addedObjectA, addedObjectB]);
+ });
+ it("when it falls outside of bounds", function () {
+ expect(collection.highBuffer).toBeDefined();
+ expect(collection.highBuffer.length).toBe(1);
+ expect(collection.highBuffer[0]).toBe(addedObjectB);
+ });
+ it("and adds it to collection when it falls within bounds", function () {
+ expect(collection.telemetry.length).toBe(11);
+ collection.bounds({start: 0, end: 11000});
+ expect(collection.telemetry.length).toBe(12);
+ expect(collection.telemetry[11]).toBe(addedObjectB);
+ });
+ it("and removes it from the buffer when it falls within bounds", function () {
+ expect(collection.highBuffer.length).toBe(1);
+ collection.bounds({start: 0, end: 11000});
+ expect(collection.highBuffer.length).toBe(0);
+ });
+ });
+ });
+ }
+);
diff --git a/platform/features/table/test/controllers/HistoricalTableControllerSpec.js b/platform/features/table/test/controllers/HistoricalTableControllerSpec.js
deleted file mode 100644
index 39f7d1a8f5..0000000000
--- a/platform/features/table/test/controllers/HistoricalTableControllerSpec.js
+++ /dev/null
@@ -1,380 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2016, United States Government
- * as represented by the Administrator of the National Aeronautics and Space
- * Administration. All rights reserved.
- *
- * Open MCT is 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.
- *
- * Open MCT includes source code licensed under additional open source
- * licenses. See the Open Source Licenses file (LICENSES.md) included with
- * this source code distribution or the Licensing information page available
- * at runtime from the About dialog for additional information.
- *****************************************************************************/
-
-define(
- [
- "../../src/controllers/HistoricalTableController"
- ],
- function (TableController) {
-
- describe('The Table Controller', function () {
- var mockScope,
- mockTelemetryHandler,
- mockTelemetryHandle,
- mockTelemetryFormatter,
- mockDomainObject,
- mockTable,
- mockConfiguration,
- mockAngularTimeout,
- mockTimeoutHandle,
- watches,
- mockConductor,
- controller;
-
- function promise(value) {
- return {
- then: function (callback) {
- return promise(callback(value));
- }
- };
- }
-
- function getCallback(target, event) {
- return target.calls.filter(function (call) {
- return call.args[0] === event;
- })[0].args[1];
- }
-
- beforeEach(function () {
- watches = {};
- mockScope = jasmine.createSpyObj('scope', [
- '$on',
- '$watch',
- '$watchCollection'
- ]);
-
- mockScope.$on.andCallFake(function (expression, callback) {
- watches[expression] = callback;
- });
- mockScope.$watch.andCallFake(function (expression, callback) {
- watches[expression] = callback;
- });
- mockScope.$watchCollection.andCallFake(function (expression, callback) {
- watches[expression] = callback;
- });
-
- mockTimeoutHandle = jasmine.createSpy("timeoutHandle");
- mockAngularTimeout = jasmine.createSpy("$timeout");
- mockAngularTimeout.andReturn(mockTimeoutHandle);
- mockAngularTimeout.cancel = jasmine.createSpy("cancelTimeout");
-
- mockConfiguration = {
- 'range1': true,
- 'range2': true,
- 'domain1': true
- };
-
- mockTable = jasmine.createSpyObj('table',
- [
- 'populateColumns',
- 'buildColumnConfiguration',
- 'getRowValues',
- 'saveColumnConfiguration'
- ]
- );
- mockTable.columns = [];
- mockTable.buildColumnConfiguration.andReturn(mockConfiguration);
-
- mockDomainObject = jasmine.createSpyObj('domainObject', [
- 'getCapability',
- 'useCapability',
- 'getModel'
- ]);
- mockDomainObject.getModel.andReturn({});
-
- mockScope.domainObject = mockDomainObject;
-
- mockTelemetryHandle = jasmine.createSpyObj('telemetryHandle', [
- 'request',
- 'promiseTelemetryObjects',
- 'getTelemetryObjects',
- 'getMetadata',
- 'getSeries',
- 'unsubscribe',
- 'makeDatum'
- ]);
- mockTelemetryHandle.promiseTelemetryObjects.andReturn(promise(undefined));
- mockTelemetryHandle.request.andReturn(promise(undefined));
- mockTelemetryHandle.getTelemetryObjects.andReturn([]);
- mockTelemetryHandle.getMetadata.andReturn([]);
-
- mockTelemetryHandler = jasmine.createSpyObj('telemetryHandler', [
- 'handle'
- ]);
- mockTelemetryHandler.handle.andReturn(mockTelemetryHandle);
-
- mockConductor = jasmine.createSpyObj("conductor", [
- "timeSystem",
- "on",
- "off"
- ]);
-
- controller = new TableController(mockScope, mockTelemetryHandler,
- mockTelemetryFormatter, mockAngularTimeout, {conductor: mockConductor});
-
- controller.table = mockTable;
- controller.handle = mockTelemetryHandle;
- });
-
- it('subscribes to telemetry handler for telemetry updates', function () {
- controller.subscribe();
- expect(mockTelemetryHandler.handle).toHaveBeenCalled();
- expect(mockTelemetryHandle.request).toHaveBeenCalled();
- });
-
- it('Unsubscribes from telemetry when scope is destroyed', function () {
- controller.handle = mockTelemetryHandle;
- watches.$destroy();
- expect(mockTelemetryHandle.unsubscribe).toHaveBeenCalled();
- });
-
- describe('makes use of the table', function () {
-
- it('to create column definitions from telemetry' +
- ' metadata', function () {
- controller.setup();
- expect(mockTable.populateColumns).toHaveBeenCalled();
- });
-
- it('to create column configuration, which is written to the' +
- ' object model', function () {
- controller.setup();
- expect(mockTable.buildColumnConfiguration).toHaveBeenCalled();
- });
- });
-
- it('updates the rows on scope when historical telemetry is received', function () {
- var mockSeries = {
- getPointCount: function () {
- return 5;
- },
- getDomainValue: function () {
- return 'Domain Value';
- },
- getRangeValue: function () {
- return 'Range Value';
- }
- },
- mockRow = {'domain': 'Domain Value', 'range': 'Range' +
- ' Value'};
-
- mockTelemetryHandle.makeDatum.andCallFake(function () {
- return mockRow;
- });
- mockTable.getRowValues.andReturn(mockRow);
- mockTelemetryHandle.getTelemetryObjects.andReturn([mockDomainObject]);
- mockTelemetryHandle.getSeries.andReturn(mockSeries);
-
- controller.addHistoricalData(mockDomainObject, mockSeries);
-
- // Angular timeout is called a minumum of twice, regardless
- // of batch size used.
- expect(mockAngularTimeout).toHaveBeenCalled();
- mockAngularTimeout.mostRecentCall.args[0]();
- expect(mockAngularTimeout.calls.length).toEqual(2);
- mockAngularTimeout.mostRecentCall.args[0]();
-
- expect(controller.$scope.rows.length).toBe(5);
- expect(controller.$scope.rows[0]).toBe(mockRow);
- });
-
- it('filters the visible columns based on configuration', function () {
- controller.filterColumns();
- expect(controller.$scope.headers.length).toBe(3);
- expect(controller.$scope.headers[2]).toEqual('domain1');
-
- mockConfiguration.domain1 = false;
- controller.filterColumns();
- expect(controller.$scope.headers.length).toBe(2);
- expect(controller.$scope.headers[2]).toBeUndefined();
- });
-
- describe('creates event listeners', function () {
- beforeEach(function () {
- spyOn(controller, 'subscribe');
- spyOn(controller, 'filterColumns');
- });
-
- it('triggers telemetry subscription update when domain' +
- ' object changes', function () {
- controller.registerChangeListeners();
- //'watches' object is populated by fake scope watch and
- // watchCollection functions defined above
- expect(watches.domainObject).toBeDefined();
- watches.domainObject(mockDomainObject);
- expect(controller.subscribe).toHaveBeenCalled();
- });
-
- it('triggers telemetry subscription update when domain' +
- ' object composition changes', function () {
- controller.registerChangeListeners();
- expect(watches['domainObject.getModel().composition']).toBeDefined();
- watches['domainObject.getModel().composition']([], []);
- expect(controller.subscribe).toHaveBeenCalled();
- });
-
- it('triggers telemetry subscription update when time' +
- ' conductor bounds change', function () {
- controller.registerChangeListeners();
- expect(watches['telemetry:display:bounds']).toBeDefined();
- watches['telemetry:display:bounds']();
- expect(controller.subscribe).toHaveBeenCalled();
- });
-
- it('triggers refiltering of the columns when configuration' +
- ' changes', function () {
- controller.setup();
- expect(watches['domainObject.getModel().configuration.table.columns']).toBeDefined();
- watches['domainObject.getModel().configuration.table.columns']();
- expect(controller.filterColumns).toHaveBeenCalled();
- });
-
- });
- describe('After populating columns', function () {
- var metadata;
- beforeEach(function () {
- metadata = [{domains: [{name: 'time domain 1'}, {name: 'time domain 2'}]}, {domains: [{name: 'time domain 3'}, {name: 'time domain 4'}]}];
- controller.populateColumns(metadata);
- });
-
- it('Automatically identifies time columns', function () {
- expect(controller.timeColumns.length).toBe(4);
- expect(controller.timeColumns[0]).toBe('time domain 1');
- });
-
- it('Automatically sorts by time column that matches current' +
- ' time system', function () {
- var key = 'time_domain_1',
- name = 'time domain 1',
- mockTimeSystem = {
- metadata: {
- key: key
- }
- };
-
- mockTable.columns = [
- {
- domainMetadata: {
- key: key
- },
- getTitle: function () {
- return name;
- }
- },
- {
- domainMetadata: {
- key: 'anotherColumn'
- },
- getTitle: function () {
- return 'some other column';
- }
- },
- {
- domainMetadata: {
- key: 'thirdColumn'
- },
- getTitle: function () {
- return 'a third column';
- }
- }
- ];
-
- expect(mockConductor.on).toHaveBeenCalledWith('timeSystem', jasmine.any(Function));
- getCallback(mockConductor.on, 'timeSystem')(mockTimeSystem);
- expect(controller.$scope.defaultSort).toBe(name);
- });
- });
- describe('Yields thread', function () {
- var mockSeries,
- mockRow;
-
- beforeEach(function () {
- mockSeries = {
- getPointCount: function () {
- return 5;
- },
- getDomainValue: function () {
- return 'Domain Value';
- },
- getRangeValue: function () {
- return 'Range Value';
- }
- };
- mockRow = {'domain': 'Domain Value', 'range': 'Range Value'};
-
- mockTelemetryHandle.makeDatum.andCallFake(function () {
- return mockRow;
- });
- mockTable.getRowValues.andReturn(mockRow);
- mockTelemetryHandle.getTelemetryObjects.andReturn([mockDomainObject]);
- mockTelemetryHandle.getSeries.andReturn(mockSeries);
- });
- it('when row count exceeds batch size', function () {
- controller.batchSize = 3;
- controller.addHistoricalData(mockDomainObject, mockSeries);
-
- //Timeout is called a minimum of two times
- expect(mockAngularTimeout).toHaveBeenCalled();
- mockAngularTimeout.mostRecentCall.args[0]();
-
- expect(mockAngularTimeout.calls.length).toEqual(2);
- mockAngularTimeout.mostRecentCall.args[0]();
-
- //Because it yields, timeout will have been called a
- // third time for the batch.
- expect(mockAngularTimeout.calls.length).toEqual(3);
- mockAngularTimeout.mostRecentCall.args[0]();
-
- expect(controller.$scope.rows.length).toBe(5);
- expect(controller.$scope.rows[0]).toBe(mockRow);
- });
- it('cancelling any outstanding timeouts', function () {
- controller.batchSize = 3;
- controller.addHistoricalData(mockDomainObject, mockSeries);
-
- expect(mockAngularTimeout).toHaveBeenCalled();
- mockAngularTimeout.mostRecentCall.args[0]();
-
- controller.addHistoricalData(mockDomainObject, mockSeries);
-
- expect(mockAngularTimeout.cancel).toHaveBeenCalledWith(mockTimeoutHandle);
- });
- it('cancels timeout on scope destruction', function () {
- controller.batchSize = 3;
- controller.addHistoricalData(mockDomainObject, mockSeries);
-
- //Destroy is used by parent class as well, so multiple
- // calls are made to scope.$on
- var destroyCalls = mockScope.$on.calls.filter(function (call) {
- return call.args[0] === '$destroy';
- });
- //Call destroy function
- expect(destroyCalls.length).toEqual(2);
-
- destroyCalls[0].args[1]();
- expect(mockAngularTimeout.cancel).toHaveBeenCalledWith(mockTimeoutHandle);
-
- });
- });
- });
- }
-);
diff --git a/platform/features/table/test/controllers/MCTTableControllerSpec.js b/platform/features/table/test/controllers/MCTTableControllerSpec.js
index 970ca72047..f57b981c50 100644
--- a/platform/features/table/test/controllers/MCTTableControllerSpec.js
+++ b/platform/features/table/test/controllers/MCTTableControllerSpec.js
@@ -39,21 +39,13 @@ define(
var controller,
mockScope,
watches,
- mockTimeout,
+ mockWindow,
mockElement,
mockExportService,
mockConductor,
mockFormatService,
mockFormat;
- function promise(value) {
- return {
- then: function (callback) {
- return promise(callback(value));
- }
- };
- }
-
function getCallback(target, event) {
return target.calls.filter(function (call) {
return call.args[0] === event;
@@ -66,7 +58,8 @@ define(
mockScope = jasmine.createSpyObj('scope', [
'$watch',
'$on',
- '$watchCollection'
+ '$watchCollection',
+ '$digest'
]);
mockScope.$watchCollection.andCallFake(function (event, callback) {
watches[event] = callback;
@@ -86,8 +79,11 @@ define(
]);
mockScope.displayHeaders = true;
- mockTimeout = jasmine.createSpy('$timeout');
- mockTimeout.andReturn(promise(undefined));
+ mockWindow = jasmine.createSpyObj('$window', ['requestAnimationFrame']);
+ mockWindow.requestAnimationFrame.andCallFake(function (f) {
+ return f();
+ });
+
mockFormat = jasmine.createSpyObj('formatter', [
'parse',
'format'
@@ -99,7 +95,7 @@ define(
controller = new MCTTableController(
mockScope,
- mockTimeout,
+ mockWindow,
mockElement,
mockExportService,
mockFormatService,
@@ -114,12 +110,12 @@ define(
expect(mockScope.$watch).toHaveBeenCalledWith('rows', jasmine.any(Function));
});
- it('destroys listeners on destruction', function () {
- expect(mockScope.$on).toHaveBeenCalledWith('$destroy', controller.destroyConductorListeners);
+ it('unregisters listeners on destruction', function () {
+ expect(mockScope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function));
getCallback(mockScope.$on, '$destroy')();
expect(mockConductor.off).toHaveBeenCalledWith('timeSystem', controller.changeTimeSystem);
- expect(mockConductor.off).toHaveBeenCalledWith('timeOfInterest', controller.setTimeOfInterest);
+ expect(mockConductor.off).toHaveBeenCalledWith('timeOfInterest', controller.changeTimeOfInterest);
expect(mockConductor.off).toHaveBeenCalledWith('bounds', controller.changeBounds);
});
@@ -233,9 +229,20 @@ define(
//Mock setting the rows on scope
var rowsCallback = getCallback(mockScope.$watch, 'rows');
- rowsCallback(rowsAsc);
+ var setRowsPromise = rowsCallback(rowsAsc);
+ var promiseResolved = false;
+ setRowsPromise.then(function () {
+ promiseResolved = true;
+ });
+
+ waitsFor(function () {
+ return promiseResolved;
+ }, "promise to resolve", 100);
+
+ runs(function () {
+ expect(mockScope.toiRowIndex).toBe(2);
+ });
- expect(mockScope.toiRowIndex).toBe(2);
});
});
@@ -287,7 +294,7 @@ define(
});
it('Supports adding rows individually', function () {
- var addRowFunc = getCallback(mockScope.$on, 'add:row'),
+ var addRowFunc = getCallback(mockScope.$on, 'add:rows'),
row4 = {
'col1': {'text': 'row3 col1'},
'col2': {'text': 'ghi'},
@@ -296,15 +303,15 @@ define(
controller.setRows(testRows);
expect(mockScope.displayRows.length).toBe(3);
testRows.push(row4);
- addRowFunc(undefined, 3);
+ addRowFunc(undefined, [row4]);
expect(mockScope.displayRows.length).toBe(4);
});
it('Supports removing rows individually', function () {
- var removeRowFunc = getCallback(mockScope.$on, 'remove:row');
+ var removeRowFunc = getCallback(mockScope.$on, 'remove:rows');
controller.setRows(testRows);
expect(mockScope.displayRows.length).toBe(3);
- removeRowFunc(undefined, 2);
+ removeRowFunc(undefined, [testRows[2]]);
expect(mockScope.displayRows.length).toBe(2);
expect(controller.setVisibleRows).toHaveBeenCalled();
});
@@ -366,7 +373,7 @@ define(
it('Allows sort column to be changed externally by ' +
'setting or changing sortBy attribute', function () {
mockScope.displayRows = testRows;
- var sortByCB = getCallback(mockScope.$watch, 'sortColumn');
+ var sortByCB = getCallback(mockScope.$watch, 'defaultSort');
sortByCB('col2');
expect(mockScope.sortDirection).toEqual('asc');
@@ -381,10 +388,21 @@ define(
it('updates visible rows in scope', function () {
var oldRows;
mockScope.rows = testRows;
- controller.setRows(testRows);
+ var setRowsPromise = controller.setRows(testRows);
+ var promiseResolved = false;
+ setRowsPromise.then(function () {
+ promiseResolved = true;
+ });
oldRows = mockScope.visibleRows;
mockScope.toggleSort('col2');
- expect(mockScope.visibleRows).not.toEqual(oldRows);
+
+ waitsFor(function () {
+ return promiseResolved;
+ }, "promise to resolve", 100);
+
+ runs(function () {
+ expect(mockScope.visibleRows).not.toEqual(oldRows);
+ });
});
it('correctly sorts rows of differing types', function () {
@@ -464,21 +482,10 @@ define(
mockScope.displayRows = controller.sortRows(testRows.slice(0));
- mockScope.rows.push(row4);
- controller.addRows(undefined, mockScope.rows.length - 1);
+ controller.addRows(undefined, [row4, row5, row6, row6]);
expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
-
- mockScope.rows.push(row5);
- controller.addRows(undefined, mockScope.rows.length - 1);
- expect(mockScope.displayRows[4].col2.text).toEqual('aaa');
-
- mockScope.rows.push(row6);
- controller.addRows(undefined, mockScope.rows.length - 1);
- expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
-
- //Add a duplicate row
- mockScope.rows.push(row6);
- controller.addRows(undefined, mockScope.rows.length - 1);
+ expect(mockScope.displayRows[6].col2.text).toEqual('aaa');
+ //Added a duplicate row
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
});
@@ -493,13 +500,11 @@ define(
mockScope.displayRows = controller.sortRows(testRows.slice(0));
mockScope.displayRows = controller.filterRows(testRows);
- mockScope.rows.push(row5);
- controller.addRows(undefined, mockScope.rows.length - 1);
+ controller.addRows(undefined, [row5]);
expect(mockScope.displayRows.length).toBe(2);
expect(mockScope.displayRows[1].col2.text).toEqual('aaa');
- mockScope.rows.push(row6);
- controller.addRows(undefined, mockScope.rows.length - 1);
+ controller.addRows(undefined, [row6]);
expect(mockScope.displayRows.length).toBe(2);
//Row was not added because does not match filter
});
@@ -512,12 +517,10 @@ define(
mockScope.displayRows = testRows.slice(0);
- mockScope.rows.push(row5);
- controller.addRows(undefined, mockScope.rows.length - 1);
+ controller.addRows(undefined, [row5]);
expect(mockScope.displayRows[3].col2.text).toEqual('aaa');
- mockScope.rows.push(row6);
- controller.addRows(undefined, mockScope.rows.length - 1);
+ controller.addRows(undefined, [row6]);
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
});
@@ -535,8 +538,7 @@ define(
mockScope.displayRows = testRows.slice(0);
- mockScope.rows.push(row7);
- controller.addRows(undefined, mockScope.rows.length - 1);
+ controller.addRows(undefined, [row7]);
expect(controller.$scope.sizingRow.col2).toEqual({text: 'some longer string'});
});
diff --git a/platform/features/table/test/controllers/RealtimeTableControllerSpec.js b/platform/features/table/test/controllers/RealtimeTableControllerSpec.js
deleted file mode 100644
index bf29c3d7bd..0000000000
--- a/platform/features/table/test/controllers/RealtimeTableControllerSpec.js
+++ /dev/null
@@ -1,171 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2016, United States Government
- * as represented by the Administrator of the National Aeronautics and Space
- * Administration. All rights reserved.
- *
- * Open MCT is 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.
- *
- * Open MCT includes source code licensed under additional open source
- * licenses. See the Open Source Licenses file (LICENSES.md) included with
- * this source code distribution or the Licensing information page available
- * at runtime from the About dialog for additional information.
- *****************************************************************************/
-
-define(
- [
- "../../src/controllers/RealtimeTableController"
- ],
- function (TableController) {
-
- describe('The real-time table controller', function () {
- var mockScope,
- mockTelemetryHandler,
- mockTelemetryHandle,
- mockTelemetryFormatter,
- mockDomainObject,
- mockTable,
- mockConfiguration,
- watches,
- mockTableRow,
- mockConductor,
- controller;
-
- function promise(value) {
- return {
- then: function (callback) {
- return promise(callback(value));
- }
- };
- }
-
- beforeEach(function () {
- watches = {};
- mockTableRow = {'col1': 'val1', 'col2': 'row2'};
-
- mockScope = jasmine.createSpyObj('scope', [
- '$on',
- '$watch',
- '$watchCollection',
- '$digest',
- '$broadcast'
- ]);
- mockScope.$on.andCallFake(function (expression, callback) {
- watches[expression] = callback;
- });
- mockScope.$watch.andCallFake(function (expression, callback) {
- watches[expression] = callback;
- });
- mockScope.$watchCollection.andCallFake(function (expression, callback) {
- watches[expression] = callback;
- });
-
- mockConfiguration = {
- 'range1': true,
- 'range2': true,
- 'domain1': true
- };
-
- mockTable = jasmine.createSpyObj('table',
- [
- 'populateColumns',
- 'buildColumnConfiguration',
- 'getRowValues',
- 'saveColumnConfiguration'
- ]
- );
- mockTable.columns = [];
- mockTable.buildColumnConfiguration.andReturn(mockConfiguration);
- mockTable.getRowValues.andReturn(mockTableRow);
-
- mockDomainObject = jasmine.createSpyObj('domainObject', [
- 'getCapability',
- 'useCapability',
- 'getModel'
- ]);
- mockDomainObject.getModel.andReturn({});
- mockDomainObject.getCapability.andReturn(
- {
- getMetadata: function () {
- return {ranges: [{format: 'string'}]};
- }
- });
-
- mockScope.domainObject = mockDomainObject;
-
- mockTelemetryHandle = jasmine.createSpyObj('telemetryHandle', [
- 'getMetadata',
- 'unsubscribe',
- 'getDatum',
- 'promiseTelemetryObjects',
- 'getTelemetryObjects',
- 'request',
- 'getMetadata'
- ]);
-
- // Arbitrary array with non-zero length, contents are not
- // used by mocks
- mockTelemetryHandle.getTelemetryObjects.andReturn([{}]);
- mockTelemetryHandle.promiseTelemetryObjects.andReturn(promise(undefined));
- mockTelemetryHandle.getDatum.andReturn({});
- mockTelemetryHandle.request.andReturn(promise(undefined));
- mockTelemetryHandle.getMetadata.andReturn([]);
-
- mockTelemetryHandler = jasmine.createSpyObj('telemetryHandler', [
- 'handle'
- ]);
- mockTelemetryHandler.handle.andReturn(mockTelemetryHandle);
-
- mockConductor = jasmine.createSpyObj('conductor', [
- 'on',
- 'off',
- 'bounds',
- 'timeSystem',
- 'timeOfInterest'
- ]);
-
- controller = new TableController(mockScope, mockTelemetryHandler, mockTelemetryFormatter, {conductor: mockConductor});
- controller.table = mockTable;
- controller.handle = mockTelemetryHandle;
- });
-
- it('registers for streaming telemetry', function () {
- controller.subscribe();
- expect(mockTelemetryHandler.handle).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function), true);
- });
-
- describe('receives new telemetry', function () {
- beforeEach(function () {
- controller.subscribe();
- mockScope.rows = [];
- });
-
- it('updates table with new streaming telemetry', function () {
- mockTelemetryHandler.handle.mostRecentCall.args[1]();
- expect(mockScope.$broadcast).toHaveBeenCalledWith('add:row', 0);
- });
- it('observes the row limit', function () {
- var i = 0;
- controller.maxRows = 10;
-
- //Fill rows array with elements
- for (; i < 10; i++) {
- mockScope.rows.push({row: i});
- }
- mockTelemetryHandler.handle.mostRecentCall.args[1]();
- expect(mockScope.rows.length).toBe(controller.maxRows);
- expect(mockScope.rows[mockScope.rows.length - 1]).toBe(mockTableRow);
- expect(mockScope.rows[0].row).toBe(1);
- });
- });
- });
- }
-);
diff --git a/platform/features/table/test/controllers/TelemetryTableControllerSpec.js b/platform/features/table/test/controllers/TelemetryTableControllerSpec.js
new file mode 100644
index 0000000000..4f403edc32
--- /dev/null
+++ b/platform/features/table/test/controllers/TelemetryTableControllerSpec.js
@@ -0,0 +1,364 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2016, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is 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.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+define(
+ [
+ '../../src/controllers/TelemetryTableController',
+ '../../../../../src/api/objects/object-utils',
+ 'lodash'
+ ],
+ function (TelemetryTableController, objectUtils, _) {
+
+ describe('The TelemetryTableController', function () {
+
+ var controller,
+ mockScope,
+ mockTimeout,
+ mockConductor,
+ mockAPI,
+ mockDomainObject,
+ mockTelemetryAPI,
+ mockObjectAPI,
+ mockCompositionAPI,
+ unobserve,
+ mockBounds;
+
+ function getCallback(target, event) {
+ return target.calls.filter(function (call) {
+ return call.args[0] === event;
+ })[0].args[1];
+ }
+
+ beforeEach(function () {
+ mockBounds = {
+ start: 0,
+ end: 10
+ };
+ mockConductor = jasmine.createSpyObj("conductor", [
+ "bounds",
+ "follow",
+ "on",
+ "off",
+ "timeSystem"
+ ]);
+ mockConductor.bounds.andReturn(mockBounds);
+ mockConductor.follow.andReturn(false);
+
+ mockDomainObject = jasmine.createSpyObj("domainObject", [
+ "getModel",
+ "getId",
+ "useCapability"
+ ]);
+ mockDomainObject.getModel.andReturn({});
+ mockDomainObject.getId.andReturn("mockId");
+ mockDomainObject.useCapability.andReturn(true);
+
+ mockCompositionAPI = jasmine.createSpyObj("compositionAPI", [
+ "get"
+ ]);
+
+ mockObjectAPI = jasmine.createSpyObj("objectAPI", [
+ "observe"
+ ]);
+ unobserve = jasmine.createSpy("unobserve");
+ mockObjectAPI.observe.andReturn(unobserve);
+
+ mockScope = jasmine.createSpyObj("scope", [
+ "$on",
+ "$watch",
+ "$broadcast"
+ ]);
+ mockScope.domainObject = mockDomainObject;
+
+ mockTelemetryAPI = jasmine.createSpyObj("telemetryAPI", [
+ "canProvideTelemetry",
+ "subscribe",
+ "getMetadata",
+ "commonValuesForHints",
+ "request",
+ "limitEvaluator",
+ "getValueFormatter"
+ ]);
+ mockTelemetryAPI.commonValuesForHints.andReturn([]);
+ mockTelemetryAPI.request.andReturn(Promise.resolve([]));
+
+
+ mockTelemetryAPI.canProvideTelemetry.andReturn(false);
+
+ mockTimeout = jasmine.createSpy("timeout");
+ mockTimeout.andReturn(1); // Return something
+ mockTimeout.cancel = jasmine.createSpy("cancel");
+
+ mockAPI = {
+ conductor: mockConductor,
+ objects: mockObjectAPI,
+ telemetry: mockTelemetryAPI,
+ composition: mockCompositionAPI
+ };
+ controller = new TelemetryTableController(mockScope, mockTimeout, mockAPI);
+ });
+
+ describe('listens for', function () {
+ beforeEach(function () {
+ controller.registerChangeListeners();
+ });
+ it('object mutation', function () {
+ var calledObject = mockObjectAPI.observe.mostRecentCall.args[0];
+
+ expect(mockObjectAPI.observe).toHaveBeenCalled();
+ expect(calledObject.identifier.key).toEqual(mockDomainObject.getId());
+ });
+ it('conductor changes', function () {
+ expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", jasmine.any(Function));
+ expect(mockConductor.on).toHaveBeenCalledWith("bounds", jasmine.any(Function));
+ expect(mockConductor.on).toHaveBeenCalledWith("follow", jasmine.any(Function));
+ });
+ });
+
+ describe('deregisters all listeners on scope destruction', function () {
+ var timeSystemListener,
+ boundsListener,
+ followListener;
+
+ beforeEach(function () {
+ controller.registerChangeListeners();
+
+ timeSystemListener = getCallback(mockConductor.on, "timeSystem");
+ boundsListener = getCallback(mockConductor.on, "bounds");
+ followListener = getCallback(mockConductor.on, "follow");
+
+ var destroy = getCallback(mockScope.$on, "$destroy");
+ destroy();
+ });
+
+ it('object mutation', function () {
+ expect(unobserve).toHaveBeenCalled();
+ });
+ it('conductor changes', function () {
+ expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", timeSystemListener);
+ expect(mockConductor.off).toHaveBeenCalledWith("bounds", boundsListener);
+ expect(mockConductor.off).toHaveBeenCalledWith("follow", followListener);
+ });
+ });
+
+ describe ('Subscribes to new data', function () {
+ var mockComposition,
+ mockTelemetryObject,
+ mockChildren,
+ unsubscribe,
+ done;
+
+ beforeEach(function () {
+ mockComposition = jasmine.createSpyObj("composition", [
+ "load"
+ ]);
+
+ mockTelemetryObject = jasmine.createSpyObj("mockTelemetryObject", [
+ "something"
+ ]);
+ mockTelemetryObject.identifier = {
+ key: "mockTelemetryObject"
+ };
+
+ unsubscribe = jasmine.createSpy("unsubscribe");
+ mockTelemetryAPI.subscribe.andReturn(unsubscribe);
+
+ mockChildren = [mockTelemetryObject];
+ mockComposition.load.andReturn(Promise.resolve(mockChildren));
+ mockCompositionAPI.get.andReturn(mockComposition);
+
+ mockTelemetryAPI.canProvideTelemetry.andCallFake(function (obj) {
+ return obj.identifier.key === mockTelemetryObject.identifier.key;
+ });
+
+ done = false;
+ controller.getData().then(function () {
+ done = true;
+ });
+ });
+
+ it('fetches historical data', function () {
+ waitsFor(function () {
+ return done;
+ }, "getData to return", 100);
+
+ runs(function () {
+ expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Object));
+ });
+ });
+
+ it('fetches historical data for the time period specified by the conductor bounds', function () {
+ waitsFor(function () {
+ return done;
+ }, "getData to return", 100);
+
+ runs(function () {
+ expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, mockBounds);
+ });
+ });
+
+ it('subscribes to new data', function () {
+ waitsFor(function () {
+ return done;
+ }, "getData to return", 100);
+
+ runs(function () {
+ expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Function), {});
+ });
+
+ });
+ it('and unsubscribes on view destruction', function () {
+ waitsFor(function () {
+ return done;
+ }, "getData to return", 100);
+
+ runs(function () {
+ var destroy = getCallback(mockScope.$on, "$destroy");
+ destroy();
+
+ expect(unsubscribe).toHaveBeenCalled();
+ });
+ });
+ });
+
+ it('When in real-time mode, enables auto-scroll', function () {
+ controller.registerChangeListeners();
+
+ var followCallback = getCallback(mockConductor.on, "follow");
+ //Confirm pre-condition
+ expect(mockScope.autoScroll).toBeFalsy();
+
+ //Mock setting the conductor to 'follow' mode
+ followCallback(true);
+ expect(mockScope.autoScroll).toBe(true);
+ });
+
+ describe('populates table columns', function () {
+ var domainMetadata;
+ var allMetadata;
+ var mockTimeSystem;
+
+ beforeEach(function () {
+ domainMetadata = [{
+ key: "column1",
+ name: "Column 1",
+ hints: {}
+ }];
+
+ allMetadata = [{
+ key: "column1",
+ name: "Column 1",
+ hints: {}
+ }, {
+ key: "column2",
+ name: "Column 2",
+ hints: {}
+ }, {
+ key: "column3",
+ name: "Column 3",
+ hints: {}
+ }];
+
+ mockTimeSystem = {
+ metadata: {
+ key: "column1"
+ }
+ };
+
+ mockTelemetryAPI.commonValuesForHints.andCallFake(function (metadata, hints) {
+ if (_.eq(hints, ["x"])) {
+ return domainMetadata;
+ } else if (_.eq(hints, [])) {
+ return allMetadata;
+ }
+ });
+
+ controller.loadColumns([mockDomainObject]);
+ });
+
+ it('based on metadata for given objects', function () {
+ expect(mockScope.headers).toBeDefined();
+ expect(mockScope.headers.length).toBeGreaterThan(0);
+ expect(mockScope.headers.indexOf(allMetadata[0].name)).not.toBe(-1);
+ expect(mockScope.headers.indexOf(allMetadata[1].name)).not.toBe(-1);
+ expect(mockScope.headers.indexOf(allMetadata[2].name)).not.toBe(-1);
+ });
+
+ it('and sorts by column matching time system', function () {
+ expect(mockScope.defaultSort).not.toEqual("Column 1");
+ controller.sortByTimeSystem(mockTimeSystem);
+ expect(mockScope.defaultSort).toEqual("Column 1");
+ });
+
+ it('batches processing of rows for performance when receiving historical telemetry', function () {
+ var mockHistoricalData = [
+ {
+ "column1": 1,
+ "column2": 2,
+ "column3": 3
+ },{
+ "column1": 4,
+ "column2": 5,
+ "column3": 6
+ }, {
+ "column1": 7,
+ "column2": 8,
+ "column3": 9
+ }
+ ];
+ controller.batchSize = 2;
+ mockTelemetryAPI.request.andReturn(Promise.resolve(mockHistoricalData));
+ controller.getHistoricalData([mockDomainObject]);
+
+ waitsFor(function () {
+ return !!controller.timeoutHandle;
+ }, "first batch to be processed", 100);
+
+ runs(function () {
+ //Verify that timeout is being used to yield process
+ expect(mockTimeout).toHaveBeenCalled();
+ mockTimeout.mostRecentCall.args[0]();
+ expect(mockTimeout.calls.length).toBe(2);
+ mockTimeout.mostRecentCall.args[0]();
+ expect(mockScope.rows.length).toBe(3);
+ });
+ });
+ });
+
+ it('Removes telemetry rows from table when they fall out of bounds', function () {
+ var discardedRows = [
+ {"column1": "value 1"},
+ {"column2": "value 2"},
+ {"column3": "value 3"}
+ ];
+
+ spyOn(controller.telemetry, "on").andCallThrough();
+
+ controller.registerChangeListeners();
+ expect(controller.telemetry.on).toHaveBeenCalledWith("discarded", jasmine.any(Function));
+ var onDiscard = getCallback(controller.telemetry.on, "discarded");
+ onDiscard(discardedRows);
+ expect(mockScope.$broadcast).toHaveBeenCalledWith("remove:rows", discardedRows);
+ });
+
+ });
+ });