Merge remote-tracking branch 'github/master' into open450b
Merge latest from master branch to reconcile conflicts for https://github.com/nasa/openmctweb/pull/469 Conflicts: platform/commonUI/general/bundle.json
This commit is contained in:
@@ -37,7 +37,6 @@ define([
|
||||
"./src/controllers/ViewSwitcherController",
|
||||
"./src/controllers/BottomBarController",
|
||||
"./src/controllers/GetterSetterController",
|
||||
"./src/controllers/SplitPaneController",
|
||||
"./src/controllers/SelectorController",
|
||||
"./src/controllers/ObjectInspectorController",
|
||||
"./src/controllers/BannerController",
|
||||
@@ -66,7 +65,6 @@ define([
|
||||
ViewSwitcherController,
|
||||
BottomBarController,
|
||||
GetterSetterController,
|
||||
SplitPaneController,
|
||||
SelectorController,
|
||||
ObjectInspectorController,
|
||||
BannerController,
|
||||
@@ -241,10 +239,6 @@ define([
|
||||
"$scope"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "SplitPaneController",
|
||||
"implementation": SplitPaneController
|
||||
},
|
||||
{
|
||||
"key": "SelectorController",
|
||||
"implementation": SelectorController,
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<!--
|
||||
Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
Open MCT Web 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 Web 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.
|
||||
-->
|
||||
<span ng-controller="SplitPaneController as splitter">
|
||||
<div class="splitter" ng-style="splitter.style()"
|
||||
mct-drag="splitter.move(delta.x)">
|
||||
</div>
|
||||
<div class='split-pane-component items pane' style="right:0;"
|
||||
ng-style="splitter.style()"
|
||||
ng-transclude>
|
||||
</div>
|
||||
</span>
|
||||
@@ -1,89 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
var DEFAULT_MAXIMUM = 1000,
|
||||
DEFAULT_MINIMUM = 120;
|
||||
|
||||
/**
|
||||
* Controller for the splitter in Browse mode. Current implementation
|
||||
* uses many hard-coded constants; this could be generalized.
|
||||
* @memberof platform/commonUI/general
|
||||
* @constructor
|
||||
*/
|
||||
function SplitPaneController() {
|
||||
this.current = 200;
|
||||
this.start = 200;
|
||||
this.assigned = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current position of the splitter, in pixels
|
||||
* from the left edge.
|
||||
* @returns {number} position of the splitter, in pixels
|
||||
*/
|
||||
SplitPaneController.prototype.state = function (defaultState) {
|
||||
// Set the state to the desired default, if we don't have a
|
||||
// "real" current state yet.
|
||||
if (arguments.length > 0 && !this.assigned) {
|
||||
this.current = defaultState;
|
||||
this.assigned = true;
|
||||
}
|
||||
return this.current;
|
||||
};
|
||||
|
||||
/**
|
||||
* Begin moving the splitter; this will note the splitter's
|
||||
* current position, which is necessary for correct
|
||||
* interpretation of deltas provided by mct-drag.
|
||||
*/
|
||||
SplitPaneController.prototype.startMove = function () {
|
||||
this.start = this.current;
|
||||
};
|
||||
|
||||
/**
|
||||
* Move the splitter a number of pixels to the right
|
||||
* (negative numbers move the splitter to the left.)
|
||||
* This movement is relative to the position of the
|
||||
* splitter when startMove was last invoked.
|
||||
* @param {number} delta number of pixels to move
|
||||
*/
|
||||
SplitPaneController.prototype.move = function (delta, minimum, maximum) {
|
||||
// Ensure defaults for minimum/maximum
|
||||
maximum = isNaN(maximum) ? DEFAULT_MAXIMUM : maximum;
|
||||
minimum = isNaN(minimum) ? DEFAULT_MINIMUM : minimum;
|
||||
|
||||
// Update current splitter state
|
||||
this.current = Math.min(
|
||||
maximum,
|
||||
Math.max(minimum, this.start + delta)
|
||||
);
|
||||
};
|
||||
|
||||
return SplitPaneController;
|
||||
}
|
||||
);
|
||||
@@ -22,8 +22,8 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../../src/controllers/DateTimePickerController"],
|
||||
function (DateTimePickerController) {
|
||||
["../../src/controllers/DateTimePickerController", "moment"],
|
||||
function (DateTimePickerController, moment) {
|
||||
"use strict";
|
||||
|
||||
describe("The DateTimePickerController", function () {
|
||||
@@ -39,6 +39,14 @@ define(
|
||||
});
|
||||
}
|
||||
|
||||
function fireWatchCollection(expr, value) {
|
||||
mockScope.$watchCollection.calls.forEach(function (call) {
|
||||
if (call.args[0] === expr) {
|
||||
call.args[1](value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
"$scope",
|
||||
@@ -57,6 +65,131 @@ define(
|
||||
);
|
||||
});
|
||||
|
||||
it("updates value in model when values in scope change", function () {
|
||||
mockScope.date = {
|
||||
year: 1998,
|
||||
month: 0,
|
||||
day: 6
|
||||
};
|
||||
mockScope.time = {
|
||||
hours: 12,
|
||||
minutes: 34,
|
||||
seconds: 56
|
||||
};
|
||||
fireWatchCollection("date", mockScope.date);
|
||||
expect(mockScope.ngModel[mockScope.field])
|
||||
.toEqual(moment.utc("1998-01-06 12:34:56").valueOf());
|
||||
});
|
||||
|
||||
describe("once initialized with model state", function () {
|
||||
var testTime = moment.utc("1998-01-06 12:34:56").valueOf();
|
||||
|
||||
beforeEach(function () {
|
||||
fireWatch("ngModel[field]", testTime);
|
||||
});
|
||||
|
||||
it("exposes date/time values in scope", function () {
|
||||
expect(mockScope.date.year).toEqual(1998);
|
||||
expect(mockScope.date.month).toEqual(0); // Months are zero-indexed
|
||||
expect(mockScope.date.day).toEqual(6);
|
||||
expect(mockScope.time.hours).toEqual(12);
|
||||
expect(mockScope.time.minutes).toEqual(34);
|
||||
expect(mockScope.time.seconds).toEqual(56);
|
||||
});
|
||||
|
||||
it("provides names for time properties", function () {
|
||||
Object.keys(mockScope.time).forEach(function (key) {
|
||||
expect(mockScope.nameFor(key))
|
||||
.toEqual(jasmine.any(String));
|
||||
});
|
||||
});
|
||||
|
||||
it("provides options for time properties", function () {
|
||||
Object.keys(mockScope.time).forEach(function (key) {
|
||||
expect(mockScope.optionsFor(key))
|
||||
.toEqual(jasmine.any(Array));
|
||||
});
|
||||
});
|
||||
|
||||
it("exposes times to populate calendar as a table", function () {
|
||||
// Verify that data structure is as expected by template
|
||||
expect(mockScope.table).toEqual(jasmine.any(Array));
|
||||
expect(mockScope.table[0]).toEqual(jasmine.any(Array));
|
||||
expect(mockScope.table[0][0]).toEqual({
|
||||
year: jasmine.any(Number),
|
||||
month: jasmine.any(Number),
|
||||
day: jasmine.any(Number),
|
||||
dayOfYear: jasmine.any(Number)
|
||||
});
|
||||
});
|
||||
|
||||
it("contains the current date in its initial table", function () {
|
||||
var matchingCell;
|
||||
// Should be able to find the selected date
|
||||
mockScope.table.forEach(function (row) {
|
||||
row.forEach(function (cell) {
|
||||
if (cell.dayOfYear === 6) {
|
||||
matchingCell = cell;
|
||||
}
|
||||
});
|
||||
});
|
||||
expect(matchingCell).toEqual({
|
||||
year: 1998,
|
||||
month: 0,
|
||||
day: 6,
|
||||
dayOfYear: 6
|
||||
});
|
||||
});
|
||||
|
||||
it("allows the displayed month to be advanced", function () {
|
||||
// Around the edges of the displayed calendar we
|
||||
// may be in previous or subsequent month, so
|
||||
// test around the middle.
|
||||
var i, originalMonth = mockScope.table[2][0].month;
|
||||
|
||||
function mod12(month) {
|
||||
return ((month % 12) + 12) % 12;
|
||||
}
|
||||
|
||||
for (i = 1; i <= 12; i += 1) {
|
||||
mockScope.changeMonth(1);
|
||||
expect(mockScope.table[2][0].month)
|
||||
.toEqual(mod12(originalMonth + i));
|
||||
}
|
||||
|
||||
for (i = 11; i >= -12; i -= 1) {
|
||||
mockScope.changeMonth(-1);
|
||||
expect(mockScope.table[2][0].month)
|
||||
.toEqual(mod12(originalMonth + i));
|
||||
}
|
||||
});
|
||||
|
||||
it("allows checking if a cell is in the current month", function () {
|
||||
expect(mockScope.isInCurrentMonth(mockScope.table[2][0]))
|
||||
.toBe(true);
|
||||
});
|
||||
|
||||
it("allows cells to be selected", function () {
|
||||
mockScope.select(mockScope.table[2][0]);
|
||||
expect(mockScope.isSelected(mockScope.table[2][0]))
|
||||
.toBe(true);
|
||||
mockScope.select(mockScope.table[2][1]);
|
||||
expect(mockScope.isSelected(mockScope.table[2][0]))
|
||||
.toBe(false);
|
||||
expect(mockScope.isSelected(mockScope.table[2][1]))
|
||||
.toBe(true);
|
||||
});
|
||||
|
||||
it("allows cells to be compared", function () {
|
||||
var table = mockScope.table;
|
||||
expect(mockScope.dateEquals(table[2][0], table[2][1]))
|
||||
.toBe(false);
|
||||
expect(mockScope.dateEquals(table[2][1], table[2][1]))
|
||||
.toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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.
|
||||
*****************************************************************************/
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../../src/controllers/SplitPaneController"],
|
||||
function (SplitPaneController) {
|
||||
"use strict";
|
||||
|
||||
describe("The split pane controller", function () {
|
||||
var controller;
|
||||
|
||||
beforeEach(function () {
|
||||
controller = new SplitPaneController();
|
||||
});
|
||||
|
||||
it("has an initial position", function () {
|
||||
expect(controller.state() > 0).toBeTruthy();
|
||||
});
|
||||
|
||||
it("can be moved", function () {
|
||||
var initialState = controller.state();
|
||||
controller.startMove();
|
||||
controller.move(50);
|
||||
expect(controller.state()).toEqual(initialState + 50);
|
||||
});
|
||||
|
||||
it("clamps its position", function () {
|
||||
var initialState = controller.state();
|
||||
controller.startMove();
|
||||
// Move some really extreme number
|
||||
controller.move(-100000);
|
||||
// Shouldn't have moved below 0...
|
||||
expect(controller.state() > 0).toBeTruthy();
|
||||
// ...but should have moved left somewhere
|
||||
expect(controller.state() < initialState).toBeTruthy();
|
||||
|
||||
// Then do the same to the right
|
||||
controller.move(100000);
|
||||
// Shouldn't have moved below 0...
|
||||
expect(controller.state() < 100000).toBeTruthy();
|
||||
// ...but should have moved left somewhere
|
||||
expect(controller.state() > initialState).toBeTruthy();
|
||||
});
|
||||
|
||||
it("accepts a default state", function () {
|
||||
// Should use default state the first time...
|
||||
expect(controller.state(12321)).toEqual(12321);
|
||||
// ...but not after it's been initialized
|
||||
expect(controller.state(42)).toEqual(12321);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -34,14 +34,14 @@ define(
|
||||
mockElement,
|
||||
testAttrs,
|
||||
mockBody,
|
||||
mockParentEl,
|
||||
mockPlainEl,
|
||||
testRect,
|
||||
mctClickElsewhere;
|
||||
|
||||
function testEvent(x, y) {
|
||||
return {
|
||||
pageX: x,
|
||||
pageY: y,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
preventDefault: jasmine.createSpy("preventDefault")
|
||||
};
|
||||
}
|
||||
@@ -55,8 +55,8 @@ define(
|
||||
jasmine.createSpyObj("element", JQLITE_METHODS);
|
||||
mockBody =
|
||||
jasmine.createSpyObj("body", JQLITE_METHODS);
|
||||
mockParentEl =
|
||||
jasmine.createSpyObj("parent", ["getBoundingClientRect"]);
|
||||
mockPlainEl =
|
||||
jasmine.createSpyObj("htmlElement", ["getBoundingClientRect"]);
|
||||
|
||||
testAttrs = {
|
||||
mctClickElsewhere: "some Angular expression"
|
||||
@@ -67,6 +67,8 @@ define(
|
||||
width: 60,
|
||||
height: 75
|
||||
};
|
||||
mockElement[0] = mockPlainEl;
|
||||
mockPlainEl.getBoundingClientRect.andReturn(testRect);
|
||||
|
||||
mockDocument.find.andReturn(mockBody);
|
||||
|
||||
@@ -78,6 +80,49 @@ define(
|
||||
expect(mctClickElsewhere.restrict).toEqual("A");
|
||||
});
|
||||
|
||||
it("detaches listeners when destroyed", function () {
|
||||
expect(mockBody.off).not.toHaveBeenCalled();
|
||||
mockScope.$on.calls.forEach(function (call) {
|
||||
if (call.args[0] === '$destroy') {
|
||||
call.args[1]();
|
||||
}
|
||||
});
|
||||
expect(mockBody.off).toHaveBeenCalled();
|
||||
expect(mockBody.off.mostRecentCall.args)
|
||||
.toEqual(mockBody.on.mostRecentCall.args);
|
||||
});
|
||||
|
||||
it("listens for mousedown on the document's body", function () {
|
||||
expect(mockBody.on)
|
||||
.toHaveBeenCalledWith('mousedown', jasmine.any(Function));
|
||||
});
|
||||
|
||||
describe("when a click occurs outside the element's bounds", function () {
|
||||
beforeEach(function () {
|
||||
mockBody.on.mostRecentCall.args[1](testEvent(
|
||||
testRect.left + testRect.width + 10,
|
||||
testRect.top + testRect.height + 10
|
||||
));
|
||||
});
|
||||
|
||||
it("triggers an evaluation of its related Angular expression", function () {
|
||||
expect(mockScope.$eval)
|
||||
.toHaveBeenCalledWith(testAttrs.mctClickElsewhere);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when a click occurs within the element's bounds", function () {
|
||||
beforeEach(function () {
|
||||
mockBody.on.mostRecentCall.args[1](testEvent(
|
||||
testRect.left + testRect.width / 2,
|
||||
testRect.top + testRect.height / 2
|
||||
));
|
||||
});
|
||||
|
||||
it("triggers no evaluation", function () {
|
||||
expect(mockScope.$eval).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,13 +30,16 @@ define(
|
||||
'on',
|
||||
'addClass',
|
||||
'children',
|
||||
'eq'
|
||||
'eq',
|
||||
'toggleClass',
|
||||
'css'
|
||||
];
|
||||
|
||||
describe("The mct-split-pane directive", function () {
|
||||
var mockParse,
|
||||
mockLog,
|
||||
mockInterval,
|
||||
mockParsed,
|
||||
mctSplitPane;
|
||||
|
||||
beforeEach(function () {
|
||||
@@ -45,6 +48,11 @@ define(
|
||||
jasmine.createSpyObj('$log', ['warn', 'info', 'debug']);
|
||||
mockInterval = jasmine.createSpy('$interval');
|
||||
mockInterval.cancel = jasmine.createSpy('mockCancel');
|
||||
mockParsed = jasmine.createSpy('parsed');
|
||||
mockParsed.assign = jasmine.createSpy('assign');
|
||||
|
||||
mockParse.andReturn(mockParsed);
|
||||
|
||||
mctSplitPane = new MCTSplitPane(
|
||||
mockParse,
|
||||
mockLog,
|
||||
@@ -61,8 +69,19 @@ define(
|
||||
mockElement,
|
||||
testAttrs,
|
||||
mockChildren,
|
||||
mockFirstPane,
|
||||
mockSplitter,
|
||||
mockSecondPane,
|
||||
controller;
|
||||
|
||||
function fireOn(eventType) {
|
||||
mockScope.$on.calls.forEach(function (call) {
|
||||
if (call.args[0] === eventType) {
|
||||
call.args[1]();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope =
|
||||
jasmine.createSpyObj('$scope', ['$apply', '$watch', '$on']);
|
||||
@@ -71,10 +90,33 @@ define(
|
||||
testAttrs = {};
|
||||
mockChildren =
|
||||
jasmine.createSpyObj('children', JQLITE_METHODS);
|
||||
mockFirstPane =
|
||||
jasmine.createSpyObj('firstPane', JQLITE_METHODS);
|
||||
mockSplitter =
|
||||
jasmine.createSpyObj('splitter', JQLITE_METHODS);
|
||||
mockSecondPane =
|
||||
jasmine.createSpyObj('secondPane', JQLITE_METHODS);
|
||||
|
||||
mockElement.children.andReturn(mockChildren);
|
||||
mockChildren.eq.andReturn(mockChildren);
|
||||
mockChildren[0] = {};
|
||||
mockElement[0] = {
|
||||
offsetWidth: 12321,
|
||||
offsetHeight: 45654
|
||||
};
|
||||
mockChildren.eq.andCallFake(function (i) {
|
||||
return [mockFirstPane, mockSplitter, mockSecondPane][i];
|
||||
});
|
||||
mockFirstPane[0] = { offsetWidth: 123, offsetHeight: 456 };
|
||||
mockSplitter[0] = {
|
||||
nodeName: 'mct-splitter',
|
||||
offsetWidth: 10,
|
||||
offsetHeight: 456
|
||||
};
|
||||
mockSecondPane[0] = { offsetWidth: 10, offsetHeight: 456 };
|
||||
|
||||
mockChildren[0] = mockFirstPane[0];
|
||||
mockChildren[1] = mockSplitter[0];
|
||||
mockChildren[3] = mockSecondPane[0];
|
||||
mockChildren.length = 3;
|
||||
|
||||
controller = mctSplitPane.controller[3](
|
||||
mockScope,
|
||||
@@ -87,6 +129,77 @@ define(
|
||||
expect(mockInterval.mostRecentCall.args[3]).toBe(false);
|
||||
});
|
||||
|
||||
it("exposes its splitter's initial position", function () {
|
||||
expect(controller.position()).toEqual(
|
||||
mockFirstPane[0].offsetWidth + mockSplitter[0].offsetWidth
|
||||
);
|
||||
});
|
||||
|
||||
it("exposes the current anchoring mode", function () {
|
||||
expect(controller.anchor()).toEqual({
|
||||
edge : 'left',
|
||||
opposite : 'right',
|
||||
dimension : 'width',
|
||||
orientation : 'vertical'
|
||||
});
|
||||
});
|
||||
|
||||
it("allows classes to be toggled on contained elements", function () {
|
||||
controller.toggleClass('resizing');
|
||||
expect(mockChildren.toggleClass)
|
||||
.toHaveBeenCalledWith('resizing');
|
||||
});
|
||||
|
||||
it("allows positions to be set", function () {
|
||||
var testValue = mockChildren[0].offsetWidth + 50;
|
||||
controller.position(testValue);
|
||||
expect(mockFirstPane.css).toHaveBeenCalledWith(
|
||||
'width',
|
||||
(testValue - mockSplitter[0].offsetWidth) + 'px'
|
||||
);
|
||||
});
|
||||
|
||||
it("issues no warnings under nominal usage", function () {
|
||||
expect(mockLog.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("warns if no mct-splitter is present", function () {
|
||||
mockSplitter[0].nodeName = "not-mct-splitter";
|
||||
controller = mctSplitPane.controller[3](
|
||||
mockScope,
|
||||
mockElement,
|
||||
testAttrs
|
||||
);
|
||||
expect(mockLog.warn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("warns if an unknown anchor key is given", function () {
|
||||
testAttrs.anchor = "middle";
|
||||
controller = mctSplitPane.controller[3](
|
||||
mockScope,
|
||||
mockElement,
|
||||
testAttrs
|
||||
);
|
||||
expect(mockLog.warn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates positions on a timer", function () {
|
||||
mockFirstPane[0].offsetWidth += 100;
|
||||
// Should not reflect the change yet
|
||||
expect(controller.position()).not.toEqual(
|
||||
mockFirstPane[0].offsetWidth + mockSplitter[0].offsetWidth
|
||||
);
|
||||
mockInterval.mostRecentCall.args[0]();
|
||||
expect(controller.position()).toEqual(
|
||||
mockFirstPane[0].offsetWidth + mockSplitter[0].offsetWidth
|
||||
);
|
||||
});
|
||||
|
||||
it("cancels the active interval when scope is destroyed", function () {
|
||||
expect(mockInterval.cancel).not.toHaveBeenCalled();
|
||||
fireOn('$destroy');
|
||||
expect(mockInterval.cancel).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
113
platform/commonUI/general/test/directives/MCTSplitterSpec.js
Normal file
113
platform/commonUI/general/test/directives/MCTSplitterSpec.js
Normal file
@@ -0,0 +1,113 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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.
|
||||
*****************************************************************************/
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../../src/directives/MCTSplitter"],
|
||||
function (MCTSplitter) {
|
||||
'use strict';
|
||||
|
||||
describe("The mct-splitter directive", function () {
|
||||
var mctSplitter;
|
||||
|
||||
beforeEach(function () {
|
||||
mctSplitter = new MCTSplitter();
|
||||
});
|
||||
|
||||
it("is applicable to elements", function () {
|
||||
expect(mctSplitter.restrict).toEqual("E");
|
||||
});
|
||||
|
||||
it("depends on the mct-split-pane controller", function () {
|
||||
expect(mctSplitter.require).toEqual("^mctSplitPane");
|
||||
});
|
||||
|
||||
describe("when linked", function () {
|
||||
var mockScope,
|
||||
mockElement,
|
||||
testAttrs,
|
||||
mockSplitPane;
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
'$scope',
|
||||
[ '$on', '$watch' ]
|
||||
);
|
||||
mockElement = jasmine.createSpyObj(
|
||||
'element',
|
||||
[ 'addClass' ]
|
||||
);
|
||||
testAttrs = {};
|
||||
mockSplitPane = jasmine.createSpyObj(
|
||||
'mctSplitPane',
|
||||
[ 'position', 'toggleClass', 'anchor' ]
|
||||
);
|
||||
|
||||
mctSplitter.link(
|
||||
mockScope,
|
||||
mockElement,
|
||||
testAttrs,
|
||||
mockSplitPane
|
||||
);
|
||||
});
|
||||
|
||||
it("adds a splitter class", function () {
|
||||
expect(mockElement.addClass)
|
||||
.toHaveBeenCalledWith('splitter');
|
||||
});
|
||||
|
||||
describe("and then manipulated", function () {
|
||||
var testPosition;
|
||||
|
||||
beforeEach(function () {
|
||||
testPosition = 12321;
|
||||
mockSplitPane.position.andReturn(testPosition);
|
||||
mockSplitPane.anchor.andReturn({
|
||||
orientation: 'vertical',
|
||||
reversed: false
|
||||
});
|
||||
mockScope.splitter.startMove();
|
||||
});
|
||||
|
||||
it("adds a 'resizing' class", function () {
|
||||
expect(mockSplitPane.toggleClass)
|
||||
.toHaveBeenCalledWith('resizing');
|
||||
});
|
||||
|
||||
it("repositions during drag", function () {
|
||||
mockScope.splitter.move([ 10, 0 ]);
|
||||
expect(mockSplitPane.position)
|
||||
.toHaveBeenCalledWith(testPosition + 10);
|
||||
});
|
||||
|
||||
it("removes the 'resizing' class when finished", function () {
|
||||
mockSplitPane.toggleClass.reset();
|
||||
mockScope.splitter.endMove();
|
||||
expect(mockSplitPane.toggleClass)
|
||||
.toHaveBeenCalledWith('resizing');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -8,7 +8,6 @@
|
||||
"controllers/GetterSetterController",
|
||||
"controllers/ObjectInspectorController",
|
||||
"controllers/SelectorController",
|
||||
"controllers/SplitPaneController",
|
||||
"controllers/TimeRangeController",
|
||||
"controllers/ToggleController",
|
||||
"controllers/TreeNodeController",
|
||||
@@ -20,6 +19,7 @@
|
||||
"directives/MCTResize",
|
||||
"directives/MCTScroll",
|
||||
"directives/MCTSplitPane",
|
||||
"directives/MCTSplitter",
|
||||
"services/Popup",
|
||||
"services/PopupService",
|
||||
"services/UrlService",
|
||||
|
||||
@@ -35,10 +35,21 @@ define(
|
||||
* Both "Start" and "Restart" share this implementation, but
|
||||
* control their visibility with different `appliesTo` behavior.
|
||||
*
|
||||
* @implements Action
|
||||
* @implements {Action}
|
||||
* @memberof platform/features/clock
|
||||
* @constructor
|
||||
* @param {Function} now a function which returns the current
|
||||
* time (typically wrapping `Date.now`)
|
||||
* @param {ActionContext} context the context for this action
|
||||
*/
|
||||
function AbstractStartTimerAction(now, context) {
|
||||
var domainObject = context.domainObject;
|
||||
this.domainObject = context.domainObject;
|
||||
this.now = now;
|
||||
}
|
||||
|
||||
AbstractStartTimerAction.prototype.perform = function () {
|
||||
var domainObject = this.domainObject,
|
||||
now = this.now;
|
||||
|
||||
function doPersist() {
|
||||
var persistence = domainObject.getCapability('persistence');
|
||||
@@ -49,13 +60,9 @@ define(
|
||||
model.timestamp = now();
|
||||
}
|
||||
|
||||
return {
|
||||
perform: function () {
|
||||
return domainObject.useCapability('mutation', setTimestamp)
|
||||
.then(doPersist);
|
||||
}
|
||||
};
|
||||
}
|
||||
return domainObject.useCapability('mutation', setTimestamp)
|
||||
.then(doPersist);
|
||||
};
|
||||
|
||||
return AbstractStartTimerAction;
|
||||
}
|
||||
|
||||
@@ -31,12 +31,22 @@ define(
|
||||
*
|
||||
* Behaves the same as (and delegates functionality to)
|
||||
* the "Start" action.
|
||||
* @implements Action
|
||||
*
|
||||
* @extends {platform/features/clock.AbstractTimerAction}
|
||||
* @implements {Action}
|
||||
* @memberof platform/features/clock
|
||||
* @constructor
|
||||
* @param {Function} now a function which returns the current
|
||||
* time (typically wrapping `Date.now`)
|
||||
* @param {ActionContext} context the context for this action
|
||||
*/
|
||||
function RestartTimerAction(now, context) {
|
||||
return new AbstractStartTimerAction(now, context);
|
||||
AbstractStartTimerAction.apply(this, [ now, context ]);
|
||||
}
|
||||
|
||||
RestartTimerAction.prototype =
|
||||
Object.create(AbstractStartTimerAction.prototype);
|
||||
|
||||
RestartTimerAction.appliesTo = function (context) {
|
||||
var model =
|
||||
(context.domainObject && context.domainObject.getModel())
|
||||
|
||||
@@ -32,12 +32,21 @@ define(
|
||||
* Sets the reference timestamp in a timer to the current
|
||||
* time, such that it begins counting up.
|
||||
*
|
||||
* @implements Action
|
||||
* @extends {platform/features/clock.AbstractTimerAction}
|
||||
* @implements {Action}
|
||||
* @memberof platform/features/clock
|
||||
* @constructor
|
||||
* @param {Function} now a function which returns the current
|
||||
* time (typically wrapping `Date.now`)
|
||||
* @param {ActionContext} context the context for this action
|
||||
*/
|
||||
function StartTimerAction(now, context) {
|
||||
return new AbstractStartTimerAction(now, context);
|
||||
AbstractStartTimerAction.apply(this, [ now, context ]);
|
||||
}
|
||||
|
||||
StartTimerAction.prototype =
|
||||
Object.create(AbstractStartTimerAction.prototype);
|
||||
|
||||
StartTimerAction.appliesTo = function (context) {
|
||||
var model =
|
||||
(context.domainObject && context.domainObject.getModel())
|
||||
|
||||
@@ -30,19 +30,21 @@ define(
|
||||
* Controller for views of a Clock domain object.
|
||||
*
|
||||
* @constructor
|
||||
* @memberof platform/features/clock
|
||||
* @param {angular.Scope} $scope the Angular scope
|
||||
* @param {platform/features/clock.TickerService} tickerService
|
||||
* a service used to align behavior with clock ticks
|
||||
*/
|
||||
function ClockController($scope, tickerService) {
|
||||
var text,
|
||||
ampm,
|
||||
use24,
|
||||
lastTimestamp,
|
||||
var lastTimestamp,
|
||||
unlisten,
|
||||
timeFormat;
|
||||
timeFormat,
|
||||
self = this;
|
||||
|
||||
function update() {
|
||||
var m = moment.utc(lastTimestamp);
|
||||
text = timeFormat && m.format(timeFormat);
|
||||
ampm = m.format("A"); // Just the AM or PM part
|
||||
self.textValue = timeFormat && m.format(timeFormat);
|
||||
self.ampmValue = m.format("A"); // Just the AM or PM part
|
||||
}
|
||||
|
||||
function tick(timestamp) {
|
||||
@@ -56,8 +58,8 @@ define(
|
||||
if (clockFormat !== undefined) {
|
||||
baseFormat = clockFormat[0];
|
||||
|
||||
use24 = clockFormat[1] === 'clock24';
|
||||
timeFormat = use24 ?
|
||||
self.use24 = clockFormat[1] === 'clock24';
|
||||
timeFormat = self.use24 ?
|
||||
baseFormat.replace('hh', "HH") : baseFormat;
|
||||
|
||||
update();
|
||||
@@ -69,32 +71,32 @@ define(
|
||||
// Listen for clock ticks ... and stop listening on destroy
|
||||
unlisten = tickerService.listen(tick);
|
||||
$scope.$on('$destroy', unlisten);
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get the clock's time zone, as displayable text.
|
||||
* @returns {string}
|
||||
*/
|
||||
zone: function () {
|
||||
return "UTC";
|
||||
},
|
||||
/**
|
||||
* Get the current time, as displayable text.
|
||||
* @returns {string}
|
||||
*/
|
||||
text: function () {
|
||||
return text;
|
||||
},
|
||||
/**
|
||||
* Get the text to display to qualify a time as AM or PM.
|
||||
* @returns {string}
|
||||
*/
|
||||
ampm: function () {
|
||||
return use24 ? '' : ampm;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the clock's time zone, as displayable text.
|
||||
* @returns {string}
|
||||
*/
|
||||
ClockController.prototype.zone = function () {
|
||||
return "UTC";
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current time, as displayable text.
|
||||
* @returns {string}
|
||||
*/
|
||||
ClockController.prototype.text = function () {
|
||||
return this.textValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text to display to qualify a time as AM or PM.
|
||||
* @returns {string}
|
||||
*/
|
||||
ClockController.prototype.ampm = function () {
|
||||
return this.use24 ? '' : this.ampmValue;
|
||||
};
|
||||
|
||||
return ClockController;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -31,6 +31,12 @@ define(
|
||||
*
|
||||
* This is a short-term workaround to assure Timer views stay
|
||||
* up-to-date; should be replaced by a global auto-refresh.
|
||||
*
|
||||
* @constructor
|
||||
* @memberof platform/features/clock
|
||||
* @param {angular.Scope} $scope the Angular scope
|
||||
* @param {platform/features/clock.TickerService} tickerService
|
||||
* a service used to align behavior with clock ticks
|
||||
*/
|
||||
function RefreshingController($scope, tickerService) {
|
||||
var unlisten;
|
||||
|
||||
@@ -33,26 +33,30 @@ define(
|
||||
* Controller for views of a Timer domain object.
|
||||
*
|
||||
* @constructor
|
||||
* @memberof platform/features/clock
|
||||
* @param {angular.Scope} $scope the Angular scope
|
||||
* @param $window Angular-provided window object
|
||||
* @param {Function} now a function which returns the current
|
||||
* time (typically wrapping `Date.now`)
|
||||
*/
|
||||
function TimerController($scope, $window, now) {
|
||||
var timerObject,
|
||||
relevantAction,
|
||||
sign = '',
|
||||
text = '',
|
||||
formatter,
|
||||
active = true,
|
||||
relativeTimestamp,
|
||||
lastTimestamp;
|
||||
lastTimestamp,
|
||||
self = this;
|
||||
|
||||
function update() {
|
||||
var timeDelta = lastTimestamp - relativeTimestamp;
|
||||
|
||||
if (formatter && !isNaN(timeDelta)) {
|
||||
text = formatter(timeDelta);
|
||||
sign = timeDelta < 0 ? "-" : timeDelta >= 1000 ? "+" : "";
|
||||
self.textValue = formatter(timeDelta);
|
||||
self.signValue = timeDelta < 0 ? "-" :
|
||||
timeDelta >= 1000 ? "+" : "";
|
||||
} else {
|
||||
text = "";
|
||||
sign = "";
|
||||
self.textValue = "";
|
||||
self.signValue = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +79,7 @@ define(
|
||||
updateFormat(formatKey);
|
||||
updateTimestamp(timestamp);
|
||||
|
||||
relevantAction = actionCapability &&
|
||||
self.relevantAction = actionCapability &&
|
||||
actionCapability.getActions(actionKey)[0];
|
||||
|
||||
update();
|
||||
@@ -92,13 +96,14 @@ define(
|
||||
}
|
||||
|
||||
function tick() {
|
||||
var lastSign = sign, lastText = text;
|
||||
var lastSign = self.signValue,
|
||||
lastText = self.textValue;
|
||||
lastTimestamp = now();
|
||||
update();
|
||||
// We're running in an animation frame, not in a digest cycle.
|
||||
// We need to trigger a digest cycle if our displayable data
|
||||
// changes.
|
||||
if (lastSign !== sign || lastText !== text) {
|
||||
if (lastSign !== self.signValue || lastText !== self.textValue) {
|
||||
$scope.$apply();
|
||||
}
|
||||
if (active) {
|
||||
@@ -117,51 +122,59 @@ define(
|
||||
active = false;
|
||||
});
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get the glyph to display for the start/restart button.
|
||||
* @returns {string} glyph to display
|
||||
*/
|
||||
buttonGlyph: function () {
|
||||
return relevantAction ?
|
||||
relevantAction.getMetadata().glyph : "";
|
||||
},
|
||||
/**
|
||||
* Get the text to show for the start/restart button
|
||||
* (e.g. in a tooltip)
|
||||
* @returns {string} name of the action
|
||||
*/
|
||||
buttonText: function () {
|
||||
return relevantAction ?
|
||||
relevantAction.getMetadata().name : "";
|
||||
},
|
||||
/**
|
||||
* Perform the action associated with the start/restart button.
|
||||
*/
|
||||
clickButton: function () {
|
||||
if (relevantAction) {
|
||||
relevantAction.perform();
|
||||
updateObject($scope.domainObject);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Get the sign (+ or -) of the current timer value, as
|
||||
* displayable text.
|
||||
* @returns {string} sign of the current timer value
|
||||
*/
|
||||
sign: function () {
|
||||
return sign;
|
||||
},
|
||||
/**
|
||||
* Get the text to display for the current timer value.
|
||||
* @returns {string} current timer value
|
||||
*/
|
||||
text: function () {
|
||||
return text;
|
||||
}
|
||||
};
|
||||
this.$scope = $scope;
|
||||
this.signValue = '';
|
||||
this.textValue = '';
|
||||
this.updateObject = updateObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the glyph to display for the start/restart button.
|
||||
* @returns {string} glyph to display
|
||||
*/
|
||||
TimerController.prototype.buttonGlyph = function () {
|
||||
return this.relevantAction ?
|
||||
this.relevantAction.getMetadata().glyph : "";
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text to show for the start/restart button
|
||||
* (e.g. in a tooltip)
|
||||
* @returns {string} name of the action
|
||||
*/
|
||||
TimerController.prototype.buttonText = function () {
|
||||
return this.relevantAction ?
|
||||
this.relevantAction.getMetadata().name : "";
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Perform the action associated with the start/restart button.
|
||||
*/
|
||||
TimerController.prototype.clickButton = function () {
|
||||
if (this.relevantAction) {
|
||||
this.relevantAction.perform();
|
||||
this.updateObject(this.$scope.domainObject);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the sign (+ or -) of the current timer value, as
|
||||
* displayable text.
|
||||
* @returns {string} sign of the current timer value
|
||||
*/
|
||||
TimerController.prototype.sign = function () {
|
||||
return this.signValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text to display for the current timer value.
|
||||
* @returns {string} current timer value
|
||||
*/
|
||||
TimerController.prototype.text = function () {
|
||||
return this.textValue;
|
||||
};
|
||||
|
||||
return TimerController;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -37,45 +37,39 @@ define(
|
||||
* supports `TimerController`.
|
||||
*
|
||||
* @constructor
|
||||
* @memberof platform/features/clock
|
||||
*/
|
||||
function TimerFormatter() {
|
||||
|
||||
// Round this timestamp down to the second boundary
|
||||
// (e.g. 1124ms goes down to 1000ms, -2400ms goes down to -3000ms)
|
||||
function toWholeSeconds(duration) {
|
||||
return Math.abs(Math.floor(duration / 1000) * 1000);
|
||||
}
|
||||
|
||||
// Short-form format, e.g. 02:22:11
|
||||
function short(duration) {
|
||||
return moment.duration(toWholeSeconds(duration), 'ms')
|
||||
.format(SHORT_FORMAT, { trim: false });
|
||||
}
|
||||
|
||||
// Long-form format, e.g. 3d 02:22:11
|
||||
function long(duration) {
|
||||
return moment.duration(toWholeSeconds(duration), 'ms')
|
||||
.format(LONG_FORMAT, { trim: false });
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Format a duration for display, using the short form.
|
||||
* (e.g. 03:33:11)
|
||||
* @param {number} duration the duration, in milliseconds
|
||||
* @param {boolean} sign true if positive
|
||||
*/
|
||||
short: short,
|
||||
/**
|
||||
* Format a duration for display, using the long form.
|
||||
* (e.g. 0d 03:33:11)
|
||||
* @param {number} duration the duration, in milliseconds
|
||||
* @param {boolean} sign true if positive
|
||||
*/
|
||||
long: long
|
||||
};
|
||||
}
|
||||
|
||||
// Round this timestamp down to the second boundary
|
||||
// (e.g. 1124ms goes down to 1000ms, -2400ms goes down to -3000ms)
|
||||
function toWholeSeconds(duration) {
|
||||
return Math.abs(Math.floor(duration / 1000) * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a duration for display, using the short form.
|
||||
* (e.g. 03:33:11)
|
||||
* @param {number} duration the duration, in milliseconds
|
||||
* @param {boolean} sign true if positive
|
||||
*/
|
||||
TimerFormatter.prototype.short = function (duration) {
|
||||
return moment.duration(toWholeSeconds(duration), 'ms')
|
||||
.format(SHORT_FORMAT, { trim: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* Format a duration for display, using the long form.
|
||||
* (e.g. 0d 03:33:11)
|
||||
* @param {number} duration the duration, in milliseconds
|
||||
* @param {boolean} sign true if positive
|
||||
*/
|
||||
TimerFormatter.prototype.long = function (duration) {
|
||||
return moment.duration(toWholeSeconds(duration), 'ms')
|
||||
.format(LONG_FORMAT, { trim: false });
|
||||
};
|
||||
|
||||
return TimerFormatter;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -28,32 +28,40 @@ define(
|
||||
|
||||
/**
|
||||
* Indicator that displays the current UTC time in the status area.
|
||||
* @implements Indicator
|
||||
* @implements {Indicator}
|
||||
* @memberof platform/features/clock
|
||||
* @param {platform/features/clock.TickerService} tickerService
|
||||
* a service used to align behavior with clock ticks
|
||||
* @param {string} indicatorFormat format string for timestamps
|
||||
* shown in this indicator
|
||||
*/
|
||||
function ClockIndicator(tickerService, CLOCK_INDICATOR_FORMAT) {
|
||||
var text = "";
|
||||
function ClockIndicator(tickerService, indicatorFormat) {
|
||||
var self = this;
|
||||
|
||||
this.text = "";
|
||||
|
||||
tickerService.listen(function (timestamp) {
|
||||
text = moment.utc(timestamp).format(CLOCK_INDICATOR_FORMAT) + " UTC";
|
||||
self.text = moment.utc(timestamp)
|
||||
.format(indicatorFormat) + " UTC";
|
||||
});
|
||||
|
||||
return {
|
||||
getGlyph: function () {
|
||||
return "C";
|
||||
},
|
||||
getGlyphClass: function () {
|
||||
return "no-icon no-collapse float-right subtle";
|
||||
},
|
||||
getText: function () {
|
||||
return text;
|
||||
},
|
||||
getDescription: function () {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
ClockIndicator.prototype.getGlyph = function () {
|
||||
return "C";
|
||||
};
|
||||
|
||||
ClockIndicator.prototype.getGlyphClass = function () {
|
||||
return "no-icon no-collapse float-right subtle";
|
||||
};
|
||||
|
||||
ClockIndicator.prototype.getText = function () {
|
||||
return this.text;
|
||||
};
|
||||
|
||||
ClockIndicator.prototype.getDescription = function () {
|
||||
return "";
|
||||
};
|
||||
|
||||
return ClockIndicator;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -30,23 +30,23 @@ define(
|
||||
* Calls functions every second, as close to the actual second
|
||||
* tick as is feasible.
|
||||
* @constructor
|
||||
* @memberof platform/features/clock
|
||||
* @param $timeout Angular's $timeout
|
||||
* @param {Function} now function to provide the current time in ms
|
||||
*/
|
||||
function TickerService($timeout, now) {
|
||||
var callbacks = [],
|
||||
last = now() - 1000;
|
||||
var self = this;
|
||||
|
||||
function tick() {
|
||||
var timestamp = now(),
|
||||
millis = timestamp % 1000;
|
||||
|
||||
// Only update callbacks if a second has actually passed.
|
||||
if (timestamp >= last + 1000) {
|
||||
callbacks.forEach(function (callback) {
|
||||
if (timestamp >= self.last + 1000) {
|
||||
self.callbacks.forEach(function (callback) {
|
||||
callback(timestamp);
|
||||
});
|
||||
last = timestamp - millis;
|
||||
self.last = timestamp - millis;
|
||||
}
|
||||
|
||||
// Try to update at exactly the next second
|
||||
@@ -55,35 +55,35 @@ define(
|
||||
|
||||
tick();
|
||||
|
||||
return {
|
||||
/**
|
||||
* Listen for clock ticks. The provided callback will
|
||||
* be invoked with the current timestamp (in milliseconds
|
||||
* since Jan 1 1970) at regular intervals, as near to the
|
||||
* second boundary as possible.
|
||||
*
|
||||
* @method listen
|
||||
* @name TickerService#listen
|
||||
* @param {Function} callback callback to invoke
|
||||
* @returns {Function} a function to unregister this listener
|
||||
*/
|
||||
listen: function (callback) {
|
||||
callbacks.push(callback);
|
||||
|
||||
// Provide immediate feedback
|
||||
callback(last);
|
||||
|
||||
// Provide a deregistration function
|
||||
return function () {
|
||||
callbacks = callbacks.filter(function (cb) {
|
||||
return cb !== callback;
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
this.callbacks = [];
|
||||
this.last = now() - 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for clock ticks. The provided callback will
|
||||
* be invoked with the current timestamp (in milliseconds
|
||||
* since Jan 1 1970) at regular intervals, as near to the
|
||||
* second boundary as possible.
|
||||
*
|
||||
* @param {Function} callback callback to invoke
|
||||
* @returns {Function} a function to unregister this listener
|
||||
*/
|
||||
TickerService.prototype.listen = function (callback) {
|
||||
var self = this;
|
||||
|
||||
self.callbacks.push(callback);
|
||||
|
||||
// Provide immediate feedback
|
||||
callback(this.last);
|
||||
|
||||
// Provide a deregistration function
|
||||
return function () {
|
||||
self.callbacks = self.callbacks.filter(function (cb) {
|
||||
return cb !== callback;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
return TickerService;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -424,6 +424,58 @@ define(
|
||||
expect(controller.selected().style).not.toEqual(oldStyle);
|
||||
});
|
||||
|
||||
describe("on display bounds changes", function () {
|
||||
var testBounds;
|
||||
|
||||
beforeEach(function () {
|
||||
testBounds = { start: 123, end: 321 };
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
mockScope.model = testModel;
|
||||
findWatch("domainObject")(mockDomainObject);
|
||||
findWatch("model.modified")(testModel.modified);
|
||||
findWatch("model.composition")(mockScope.model.composition);
|
||||
findOn('telemetry:display:bounds')({}, testBounds);
|
||||
});
|
||||
|
||||
it("issues new requests", function () {
|
||||
expect(mockHandle.request).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("requests only a single point", function () {
|
||||
expect(mockHandle.request.mostRecentCall.args[0].size)
|
||||
.toEqual(1);
|
||||
});
|
||||
|
||||
describe("and after data has been received", function () {
|
||||
var mockSeries,
|
||||
testValue;
|
||||
|
||||
beforeEach(function () {
|
||||
testValue = 12321;
|
||||
|
||||
mockSeries = jasmine.createSpyObj('series', [
|
||||
'getPointCount',
|
||||
'getDomainValue',
|
||||
'getRangeValue'
|
||||
]);
|
||||
mockSeries.getPointCount.andReturn(1);
|
||||
mockSeries.getRangeValue.andReturn(testValue);
|
||||
|
||||
// Fire the callback associated with the request
|
||||
mockHandle.request.mostRecentCall.args[1](
|
||||
mockHandle.getTelemetryObjects()[0],
|
||||
mockSeries
|
||||
);
|
||||
});
|
||||
|
||||
it("updates displayed values", function () {
|
||||
expect(controller.getElements()[0].value)
|
||||
.toEqual("Formatted " + testValue);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it("reflects limit status", function () {
|
||||
var elements;
|
||||
|
||||
@@ -459,6 +511,7 @@ define(
|
||||
expect(elements[1].cssClass).toEqual("alarm-b");
|
||||
expect(elements[2].cssClass).toEqual("alarm-c");
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -286,6 +286,34 @@ define(
|
||||
expect(mockHandle.request.calls.length).toEqual(2);
|
||||
});
|
||||
|
||||
|
||||
it("maintains externally-provided domain axis bounds after data is received", function () {
|
||||
mockSeries.getPointCount.andReturn(3);
|
||||
mockSeries.getRangeValue.andReturn(42);
|
||||
mockSeries.getDomainValue.andCallFake(function (i) {
|
||||
return 2500 + i * 2500;
|
||||
});
|
||||
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
fireEvent("telemetry:display:bounds", [
|
||||
{},
|
||||
{start: 0, end: 10000}
|
||||
]);
|
||||
mockHandle.request.mostRecentCall.args[1](
|
||||
mockDomainObject,
|
||||
mockSeries
|
||||
);
|
||||
|
||||
// Pan-zoom state should reflect bounds set externally;
|
||||
// domain axis should not have shrunk to fit data.
|
||||
expect(
|
||||
controller.getSubPlots()[0].panZoomStack.getOrigin()[0]
|
||||
).toEqual(0);
|
||||
expect(
|
||||
controller.getSubPlots()[0].panZoomStack.getDimensions()[0]
|
||||
).toEqual(10000);
|
||||
});
|
||||
|
||||
it("provides classes for legends based on limit state", function () {
|
||||
var mockTelemetryObjects = mockHandle.getTelemetryObjects();
|
||||
|
||||
|
||||
@@ -32,16 +32,14 @@ define(
|
||||
var TEST_RANGE_VALUE = "some formatted range value";
|
||||
|
||||
describe("A range column", function () {
|
||||
var mockDataSet,
|
||||
var testDatum,
|
||||
testMetadata,
|
||||
mockFormatter,
|
||||
mockDomainObject,
|
||||
column;
|
||||
|
||||
beforeEach(function () {
|
||||
mockDataSet = jasmine.createSpyObj(
|
||||
"data",
|
||||
[ "getRangeValue" ]
|
||||
);
|
||||
testDatum = { testKey: 123, otherKey: 456 };
|
||||
mockFormatter = jasmine.createSpyObj(
|
||||
"formatter",
|
||||
[ "formatDomainValue", "formatRangeValue" ]
|
||||
@@ -50,6 +48,10 @@ define(
|
||||
key: "testKey",
|
||||
name: "Test Name"
|
||||
};
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
[ "getModel", "getCapability" ]
|
||||
);
|
||||
mockFormatter.formatRangeValue.andReturn(TEST_RANGE_VALUE);
|
||||
|
||||
column = new RangeColumn(testMetadata, mockFormatter);
|
||||
@@ -59,20 +61,13 @@ define(
|
||||
expect(column.getTitle()).toEqual("Test Name");
|
||||
});
|
||||
|
||||
xit("looks up data from a data set", function () {
|
||||
column.getValue(undefined, mockDataSet, 42);
|
||||
expect(mockDataSet.getRangeValue)
|
||||
.toHaveBeenCalledWith(42, "testKey");
|
||||
});
|
||||
|
||||
xit("formats range values as numbers", function () {
|
||||
mockDataSet.getRangeValue.andReturn(123.45678);
|
||||
expect(column.getValue(undefined, mockDataSet, 42).text)
|
||||
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(123.45678);
|
||||
.toHaveBeenCalledWith(testDatum.testKey);
|
||||
expect(mockFormatter.formatDomainValue)
|
||||
.not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -47,7 +47,13 @@ define(
|
||||
mockQ = jasmine.createSpyObj('$q', ['when', 'all']);
|
||||
mockSubscription = jasmine.createSpyObj(
|
||||
'subscription',
|
||||
['unsubscribe', 'getTelemetryObjects', 'promiseTelemetryObjects']
|
||||
[
|
||||
'makeDatum',
|
||||
'getDatum',
|
||||
'unsubscribe',
|
||||
'getTelemetryObjects',
|
||||
'promiseTelemetryObjects'
|
||||
]
|
||||
);
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
'domainObject',
|
||||
@@ -112,6 +118,20 @@ define(
|
||||
expect(handle.getSeries(mockDomainObject))
|
||||
.toEqual(mockSeries);
|
||||
});
|
||||
|
||||
it("provides access to the datum objects by index", function () {
|
||||
var testDatum = { a: 1, b: 2 }, testIndex = 42;
|
||||
mockSubscription.makeDatum.andReturn(testDatum);
|
||||
handle.request({});
|
||||
expect(handle.getDatum(mockDomainObject, testIndex))
|
||||
.toEqual(testDatum);
|
||||
expect(mockSubscription.makeDatum)
|
||||
.toHaveBeenCalledWith(
|
||||
mockDomainObject,
|
||||
mockSeries,
|
||||
testIndex
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user