Skip to content

Commit c6eaa3d

Browse files
committedSep 6, 2016
[Time Conductor] Adding tests and fixing failing ones. #933
1 parent 4cf6126 commit c6eaa3d

22 files changed

+1333
-129
lines changed
 

Diff for: ‎platform/commonUI/formats/src/UTCTimeFormat.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ define([
5757
* @private
5858
*/
5959
function getScaledFormat (d) {
60-
var m = moment.utc(d);
60+
var momentified = moment.utc(d);
6161
/**
6262
* Uses logic from d3 Time-Scales, v3 of the API. See
6363
* https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
@@ -71,17 +71,17 @@ define([
7171
["HH", function(m) { return m.hours(); }],
7272
["ddd DD", function(m) {
7373
return m.days() &&
74-
m.date() != 1;
74+
m.date() !== 1;
7575
}],
76-
["MMM DD", function(m) { return m.date() != 1; }],
76+
["MMM DD", function(m) { return m.date() !== 1; }],
7777
["MMMM", function(m) {
7878
return m.month();
7979
}],
8080
["YYYY", function() { return true; }]
8181
].filter(function (row){
82-
return row[1](m);
82+
return row[1](momentified);
8383
})[0][0];
84-
};
84+
}
8585

8686
/**
8787
*

Diff for: ‎platform/commonUI/formats/src/UTCTimeFormatSpec.js

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*****************************************************************************
2+
* Open MCT Web, Copyright (c) 2014-2015, United States Government
3+
* as represented by the Administrator of the National Aeronautics and Space
4+
* Administration. All rights reserved.
5+
*
6+
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
* License for the specific language governing permissions and limitations
15+
* under the License.
16+
*
17+
* Open MCT Web includes source code licensed under additional open source
18+
* licenses. See the Open Source Licenses file (LICENSES.md) included with
19+
* this source code distribution or the Licensing information page available
20+
* at runtime from the About dialog for additional information.
21+
*****************************************************************************/
22+
23+
define([
24+
"./UTCTimeFormat",
25+
"moment"
26+
], function (
27+
UTCTimeFormat,
28+
moment
29+
) {
30+
describe("The UTCTimeFormat class", function () {
31+
var format;
32+
var scale;
33+
34+
beforeEach(function () {
35+
format = new UTCTimeFormat();
36+
scale = {min: 0, max: 0};
37+
});
38+
39+
it("Provides an appropriately scaled time format based on the input" +
40+
" time", function () {
41+
var TWO_HUNDRED_MS = 200;
42+
var THREE_SECONDS = 3000;
43+
var FIVE_MINUTES = 5 * 60 * 1000;
44+
var ONE_HOUR_TWENTY_MINS = (1 * 60 * 60 * 1000) + (20 * 60 * 1000);
45+
var TEN_HOURS = (10 * 60 * 60 * 1000);
46+
47+
var JUNE_THIRD = moment.utc("2016-06-03", "YYYY-MM-DD");
48+
var APRIL = moment.utc("2016-04", "YYYY-MM");
49+
var TWENTY_SIXTEEN = moment.utc("2016", "YYYY");
50+
51+
expect(format.format(TWO_HUNDRED_MS, scale)).toBe(".200");
52+
expect(format.format(THREE_SECONDS, scale)).toBe(":03");
53+
expect(format.format(FIVE_MINUTES, scale)).toBe("00:05");
54+
expect(format.format(ONE_HOUR_TWENTY_MINS, scale)).toBe("01:20");
55+
expect(format.format(TEN_HOURS, scale)).toBe("10");
56+
57+
expect(format.format(JUNE_THIRD, scale)).toBe("Fri 03");
58+
expect(format.format(APRIL, scale)).toBe("April");
59+
expect(format.format(TWENTY_SIXTEEN, scale)).toBe("2016");
60+
});
61+
});
62+
});

Diff for: ‎platform/features/conductor-v2/compatibility/src/ConductorRepresenter.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ define(
7878

7979
this.conductor.on("bounds", this.boundsListener);
8080
this.conductor.on("timeSystem", this.timeSystemListener);
81-
this.conductor.on("follow", this.followListener)
81+
this.conductor.on("follow", this.followListener);
8282
}
8383
};
8484

Diff for: ‎platform/features/conductor-v2/compatibility/src/ConductorService.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ define([
6666

6767
ConductorService.prototype.getConductor = function () {
6868
return this.tc;
69-
}
69+
};
7070

7171
return ConductorService;
7272
});

Diff for: ‎platform/features/conductor-v2/compatibility/src/ConductorTelemetryDecorator.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ define(
7979
return function() {
8080
unsubscribeFunc();
8181
conductor.off('bounds', amendRequests);
82-
}
82+
};
8383
};
8484

8585
return ConductorTelemetryDecorator;

Diff for: ‎platform/features/conductor-v2/conductor/bundle.js

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ define([
6464
"implementation": TimeConductorController,
6565
"depends": [
6666
"$scope",
67+
"$window",
6768
"timeConductor",
6869
"timeConductorViewService",
6970
"timeSystems[]"

Diff for: ‎platform/features/conductor-v2/conductor/src/TimeConductorSpec.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,21 @@ define(['./TimeConductor'], function (TimeConductor) {
5050

5151
it("Allows setting of valid bounds", function () {
5252
bounds = {start: 0, end: 1};
53-
expect(tc.bounds()).not.toBe(bounds);
53+
expect(tc.bounds()).not.toEqual(bounds);
5454
expect(tc.bounds.bind(tc, bounds)).not.toThrow();
55-
expect(tc.bounds()).toBe(bounds);
55+
expect(tc.bounds()).toEqual(bounds);
5656
});
5757

5858
it("Disallows setting of invalid bounds", function () {
5959
bounds = {start: 1, end: 0};
60-
expect(tc.bounds()).not.toBe(bounds);
60+
expect(tc.bounds()).not.toEqual(bounds);
6161
expect(tc.bounds.bind(tc, bounds)).toThrow();
62-
expect(tc.bounds()).not.toBe(bounds);
62+
expect(tc.bounds()).not.toEqual(bounds);
6363

6464
bounds = {start: 1};
65-
expect(tc.bounds()).not.toBe(bounds);
65+
expect(tc.bounds()).not.toEqual(bounds);
6666
expect(tc.bounds.bind(tc, bounds)).toThrow();
67-
expect(tc.bounds()).not.toBe(bounds);
67+
expect(tc.bounds()).not.toEqual(bounds);
6868
});
6969

7070
it("Allows setting of time system with bounds", function () {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*****************************************************************************
2+
* Open MCT Web, Copyright (c) 2014-2015, United States Government
3+
* as represented by the Administrator of the National Aeronautics and Space
4+
* Administration. All rights reserved.
5+
*
6+
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
* License for the specific language governing permissions and limitations
15+
* under the License.
16+
*
17+
* Open MCT Web includes source code licensed under additional open source
18+
* licenses. See the Open Source Licenses file (LICENSES.md) included with
19+
* this source code distribution or the Licensing information page available
20+
* at runtime from the About dialog for additional information.
21+
*****************************************************************************/
22+
23+
define(["./LocalClock"], function (LocalClock) {
24+
describe("The LocalClock class", function () {
25+
var clock,
26+
mockTimeout,
27+
timeoutHandle = {};
28+
29+
beforeEach(function () {
30+
mockTimeout = jasmine.createSpy("timeout");
31+
mockTimeout.andReturn(timeoutHandle);
32+
mockTimeout.cancel = jasmine.createSpy("cancel");
33+
34+
clock = new LocalClock(mockTimeout, 0);
35+
clock.start();
36+
});
37+
38+
it("calls listeners on tick with current time", function () {
39+
var mockListener = jasmine.createSpy("listener");
40+
clock.listen(mockListener);
41+
clock.tick();
42+
expect(mockListener).toHaveBeenCalledWith(jasmine.any(Number));
43+
});
44+
45+
it("stops ticking when stop is called", function () {
46+
clock.stop();
47+
expect(mockTimeout.cancel).toHaveBeenCalledWith(timeoutHandle);
48+
});
49+
});
50+
});

Diff for: ‎platform/features/conductor-v2/conductor/src/ui/MctConductorAxis.js

+89-75
Original file line numberDiff line numberDiff line change
@@ -22,99 +22,113 @@
2222

2323
define(
2424
[
25-
"zepto",
2625
"d3"
2726
],
28-
function ($, d3) {
27+
function (d3) {
28+
var PADDING = 1;
2929

3030
/**
3131
* The mct-conductor-axis renders a horizontal axis with regular
3232
* labelled 'ticks'. It requires 'start' and 'end' integer values to
3333
* be specified as attributes.
3434
*/
3535
function MCTConductorAxis(conductor, formatService) {
36-
function link(scope, element, attrs, ngModelController) {
37-
var target = element[0].firstChild,
38-
height = target.offsetHeight,
39-
padding = 1;
40-
41-
var vis = d3.select(target)
42-
.append('svg:svg')
43-
.attr('width', '100%')
44-
.attr('height', height);
45-
var xAxis = d3.axisTop();
46-
var xScale = d3.scaleUtc();
47-
48-
// draw x axis with labels and move to the bottom of the chart area
49-
var axisElement = vis.append("g")
50-
.attr("transform", "translate(0," + (height - padding) + ")");
51-
52-
function setScale() {
53-
var width = target.offsetWidth;
54-
var timeSystem = conductor.timeSystem();
55-
var bounds = conductor.bounds();
56-
57-
if (timeSystem.isUTCBased()) {
58-
xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
59-
} else {
60-
xScale.domain([bounds.start, bounds.end]);
61-
}
36+
// Dependencies
37+
this.d3 = d3;
38+
this.conductor = conductor;
39+
this.formatService = formatService;
40+
41+
// Runtime properties (set by 'link' function)
42+
this.target = undefined;
43+
this.xScale = undefined;
44+
this.xAxis = undefined;
45+
this.axisElement = undefined;
46+
this.setScale = this.setScale.bind(this);
47+
this.changeTimeSystem = this.changeTimeSystem.bind(this);
48+
49+
// Angular Directive interface
50+
this.link = this.link.bind(this);
51+
this.restrict = "E";
52+
this.template =
53+
"<div class=\"l-axis-holder\" mct-resize=\"resize()\"></div>";
54+
this.priority = 1000;
55+
}
56+
57+
MCTConductorAxis.prototype.setScale = function () {
58+
var width = this.target.offsetWidth;
59+
var timeSystem = this.conductor.timeSystem();
60+
var bounds = this.conductor.bounds();
61+
62+
if (timeSystem.isUTCBased()) {
63+
this.xScale = this.xScale || this.d3.scaleUtc();
64+
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
65+
} else {
66+
this.xScale = this.xScale || this.d3.scaleLinear();
67+
this.xScale.domain([bounds.start, bounds.end]);
68+
}
6269

63-
xScale.range([padding, width - padding * 2]);
64-
axisElement.call(xAxis);
70+
this.xScale.range([PADDING, width - PADDING * 2]);
71+
this.axisElement.call(this.xAxis);
72+
};
73+
74+
MCTConductorAxis.prototype.changeTimeSystem = function (timeSystem) {
75+
var key = timeSystem.formats()[0];
76+
if (key !== undefined) {
77+
var format = this.formatService.getFormat(key);
78+
var bounds = this.conductor.bounds();
79+
80+
if (timeSystem.isUTCBased()) {
81+
this.xScale = this.d3.scaleUtc();
82+
} else {
83+
this.xScale = this.d3.scaleLinear();
6584
}
6685

67-
function changeTimeSystem(timeSystem) {
68-
var key = timeSystem.formats()[0];
69-
if (key !== undefined) {
70-
var format = formatService.getFormat(key);
71-
var b = conductor.bounds();
72-
73-
if (timeSystem.isUTCBased()) {
74-
xScale = d3.scaleUtc();
75-
} else {
76-
xScale = d3.scaleLinear();
77-
}
78-
79-
xAxis.scale(xScale);
80-
//Define a custom format function
81-
xAxis.tickFormat(function (tickValue) {
82-
// Normalize date representations to numbers
83-
if (tickValue instanceof Date){
84-
tickValue = tickValue.getTime();
85-
}
86-
return format.format(tickValue, {
87-
min: b.start,
88-
max: b.end
89-
});
90-
});
91-
axisElement.call(xAxis);
86+
this.xAxis.scale(this.xScale);
87+
//Define a custom format function
88+
this.xAxis.tickFormat(function (tickValue) {
89+
// Normalize date representations to numbers
90+
if (tickValue instanceof Date){
91+
tickValue = tickValue.getTime();
9292
}
93-
}
93+
return format.format(tickValue, {
94+
min: bounds.start,
95+
max: bounds.end
96+
});
97+
});
98+
this.axisElement.call(this.xAxis);
99+
}
100+
};
94101

95-
scope.resize = setScale;
102+
MCTConductorAxis.prototype.link = function (scope, element) {
103+
var conductor = this.conductor;
104+
this.target = element[0].firstChild;
105+
var height = this.target.offsetHeight;
106+
var vis = this.d3.select(this.target)
107+
.append('svg:svg')
108+
.attr('width', '100%')
109+
.attr('height', height);
96110

97-
conductor.on('timeSystem', changeTimeSystem);
111+
this.xAxis = this.d3.axisTop();
98112

99-
//On conductor bounds changes, redraw ticks
100-
conductor.on('bounds', function (bounds) {
101-
setScale();
102-
});
113+
// draw x axis with labels and move to the bottom of the chart area
114+
this.axisElement = vis.append("g")
115+
.attr("transform", "translate(0," + (height - PADDING) + ")");
103116

104-
if (conductor.timeSystem() !== undefined) {
105-
changeTimeSystem(conductor.timeSystem());
106-
setScale();
107-
}
108-
}
117+
scope.resize = this.setScale;
109118

110-
return {
111-
restrict: "E",
112-
template: "<div class=\"l-axis-holder\" mct-resize=\"resize()\"></div>",
113-
priority: 1000,
114-
link: link
115-
};
116-
}
119+
conductor.on('timeSystem', this.changeTimeSystem);
120+
121+
//On conductor bounds changes, redraw ticks
122+
conductor.on('bounds', this.setScale);
123+
124+
if (conductor.timeSystem() !== undefined) {
125+
this.changeTimeSystem(conductor.timeSystem());
126+
this.setScale();
127+
}
128+
};
117129

118-
return MCTConductorAxis;
130+
return function(conductor, formatService) {
131+
return new MCTConductorAxis(conductor, formatService);
132+
};
119133
}
120134
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*****************************************************************************
2+
* Open MCT Web, Copyright (c) 2014-2015, United States Government
3+
* as represented by the Administrator of the National Aeronautics and Space
4+
* Administration. All rights reserved.
5+
*
6+
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
* License for the specific language governing permissions and limitations
15+
* under the License.
16+
*
17+
* Open MCT Web includes source code licensed under additional open source
18+
* licenses. See the Open Source Licenses file (LICENSES.md) included with
19+
* this source code distribution or the Licensing information page available
20+
* at runtime from the About dialog for additional information.
21+
*****************************************************************************/
22+
23+
define(['./MctConductorAxis'], function (MctConductorAxis) {
24+
describe("The MctConductorAxis directive", function () {
25+
var directive,
26+
mockConductor,
27+
mockFormatService,
28+
mockScope,
29+
mockElement,
30+
mockTarget,
31+
mockBounds,
32+
d3;
33+
34+
beforeEach(function () {
35+
mockScope = {};
36+
37+
//Add some HTML elements
38+
mockTarget = {
39+
offsetWidth: 0,
40+
offsetHeight: 0
41+
};
42+
mockElement = {
43+
firstChild: mockTarget
44+
};
45+
mockBounds = {
46+
start: 100,
47+
end: 200
48+
};
49+
mockConductor = jasmine.createSpyObj("conductor", [
50+
"timeSystem",
51+
"bounds",
52+
"on"
53+
]);
54+
mockConductor.bounds.andReturn(mockBounds);
55+
56+
mockFormatService = jasmine.createSpyObj("formatService", [
57+
"getFormat"
58+
]);
59+
60+
var d3Functions = [
61+
"scale",
62+
"scaleUtc",
63+
"scaleLinear",
64+
"select",
65+
"append",
66+
"attr",
67+
"axisTop",
68+
"call",
69+
"tickFormat",
70+
"domain",
71+
"range"
72+
];
73+
d3 = jasmine.createSpyObj("d3", d3Functions);
74+
d3Functions.forEach(function(func) {
75+
d3[func].andReturn(d3);
76+
});
77+
78+
directive = new MctConductorAxis(mockConductor, mockFormatService);
79+
directive.d3 = d3;
80+
directive.link(mockScope, [mockElement]);
81+
});
82+
83+
it("listens for changes to time system and bounds", function () {
84+
expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", directive.changeTimeSystem);
85+
expect(mockConductor.on).toHaveBeenCalledWith("bounds", directive.setScale);
86+
});
87+
88+
describe("when the time system changes", function () {
89+
var mockTimeSystem;
90+
var mockFormat;
91+
92+
beforeEach(function() {
93+
mockTimeSystem = jasmine.createSpyObj("timeSystem", [
94+
"formats",
95+
"isUTCBased"
96+
]);
97+
mockFormat = jasmine.createSpyObj("format", [
98+
"format"
99+
]);
100+
101+
mockTimeSystem.formats.andReturn(["mockFormat"]);
102+
mockFormatService.getFormat.andReturn(mockFormat);
103+
});
104+
105+
it("uses a UTC scale for UTC time systems", function () {
106+
mockTimeSystem.isUTCBased.andReturn(true);
107+
directive.changeTimeSystem(mockTimeSystem);
108+
expect(d3.scaleUtc).toHaveBeenCalled();
109+
expect(d3.scaleLinear).not.toHaveBeenCalled();
110+
});
111+
112+
it("uses a linear scale for non-UTC time systems", function () {
113+
mockTimeSystem.isUTCBased.andReturn(false);
114+
directive.changeTimeSystem(mockTimeSystem);
115+
expect(d3.scaleLinear).toHaveBeenCalled();
116+
expect(d3.scaleUtc).not.toHaveBeenCalled();
117+
});
118+
119+
it("sets axis domain to time conductor bounds", function () {
120+
mockTimeSystem.isUTCBased.andReturn(false);
121+
mockConductor.timeSystem.andReturn(mockTimeSystem);
122+
123+
directive.setScale();
124+
expect(d3.domain).toHaveBeenCalledWith([mockBounds.start, mockBounds.end]);
125+
});
126+
127+
it("uses the format specified by the time system to format tick" +
128+
" labels", function () {
129+
directive.changeTimeSystem(mockTimeSystem);
130+
expect(d3.tickFormat).toHaveBeenCalled();
131+
d3.tickFormat.mostRecentCall.args[0]();
132+
expect(mockFormat.format).toHaveBeenCalled();
133+
});
134+
});
135+
});
136+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*****************************************************************************
2+
* Open MCT Web, Copyright (c) 2014-2015, United States Government
3+
* as represented by the Administrator of the National Aeronautics and Space
4+
* Administration. All rights reserved.
5+
*
6+
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
* License for the specific language governing permissions and limitations
15+
* under the License.
16+
*
17+
* Open MCT Web includes source code licensed under additional open source
18+
* licenses. See the Open Source Licenses file (LICENSES.md) included with
19+
* this source code distribution or the Licensing information page available
20+
* at runtime from the About dialog for additional information.
21+
*****************************************************************************/
22+
23+
define(['./NumberFormat'], function (NumberFormat) {
24+
describe("The NumberFormat class", function () {
25+
var format;
26+
beforeEach(function () {
27+
format = new NumberFormat();
28+
});
29+
30+
it("The format function takes a string and produces a number", function () {
31+
var text = format.format(1);
32+
expect(text).toBe("1");
33+
expect(typeof(text)).toBe("string");
34+
});
35+
36+
it("The parse function takes a string and produces a number", function () {
37+
var number = format.parse("1");
38+
expect(number).toBe(1);
39+
expect(typeof(number)).toBe("number");
40+
});
41+
42+
it("validates that the input is a number", function () {
43+
expect(format.validate("1")).toBe(true);
44+
expect(format.validate(1)).toBe(true);
45+
expect(format.validate("1.1")).toBe(true);
46+
expect(format.validate("abc")).toBe(false);
47+
});
48+
});
49+
});

Diff for: ‎platform/features/conductor-v2/conductor/src/ui/TimeConductorController.js

+19-17
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ define(
2626
],
2727
function (TimeConductorValidation) {
2828

29-
function TimeConductorController($scope, timeConductor, conductorViewService, timeSystems) {
29+
function TimeConductorController($scope, $window, timeConductor, conductorViewService, timeSystems) {
3030

3131
var self = this;
3232

@@ -38,6 +38,7 @@ define(
3838
});
3939

4040
this.$scope = $scope;
41+
this.$window = $window;
4142
this.conductorViewService = conductorViewService;
4243
this.conductor = timeConductor;
4344
this.modes = conductorViewService.availableModes();
@@ -99,7 +100,6 @@ define(
99100

100101
// Watch scope for selection of mode or time system by user
101102
this.$scope.$watch('modeModel.selectedKey', this.setMode);
102-
this.$scope.$watch('timeSystem', this.changeTimeSystem);
103103
};
104104

105105
/**
@@ -113,7 +113,7 @@ define(
113113
this.$scope.boundsModel.end = bounds.end;
114114
if (!this.pendingUpdate) {
115115
this.pendingUpdate = true;
116-
requestAnimationFrame(function () {
116+
this.$window.requestAnimationFrame(function () {
117117
this.pendingUpdate = false;
118118
this.$scope.$digest();
119119
}.bind(this));
@@ -136,16 +136,8 @@ define(
136136
* @private
137137
*/
138138
TimeConductorController.prototype.setFormFromDeltas = function (deltas) {
139-
/*
140-
* If the selected mode defines deltas, set them in the form
141-
*/
142-
if (deltas !== undefined) {
143-
this.$scope.boundsModel.startDelta = deltas.start;
144-
this.$scope.boundsModel.endDelta = deltas.end;
145-
} else {
146-
this.$scope.boundsModel.startDelta = 0;
147-
this.$scope.boundsModel.endDelta = 0;
148-
}
139+
this.$scope.boundsModel.startDelta = deltas.start;
140+
this.$scope.boundsModel.endDelta = deltas.end;
149141
};
150142

151143
/**
@@ -180,7 +172,7 @@ define(
180172
var deltas = {
181173
start: boundsFormModel.startDelta,
182174
end: boundsFormModel.endDelta
183-
}
175+
};
184176
if (this.validation.validateStartDelta(deltas.start) && this.validation.validateEndDelta(deltas.end)) {
185177
//Sychronize deltas between form and mode
186178
this.conductorViewService.deltas(deltas);
@@ -204,20 +196,24 @@ define(
204196
};
205197

206198
/**
199+
* Respond to time system selection from UI
200+
*
207201
* Allows time system to be changed by key. This supports selection
208202
* from the menu. Resolves a TimeSystem object and then invokes
209203
* TimeConductorController#setTimeSystem
210204
* @param key
211205
* @see TimeConductorController#setTimeSystem
212206
*/
213207
TimeConductorController.prototype.selectTimeSystemByKey = function(key){
214-
var selected = this.timeSystems.find(function (timeSystem){
208+
var selected = this.timeSystems.filter(function (timeSystem){
215209
return timeSystem.metadata.key === key;
216-
});
210+
})[0];
217211
this.conductor.timeSystem(selected, selected.defaults().bounds);
218212
};
219213

220214
/**
215+
* Handles time system change from time conductor
216+
*
221217
* Sets the selected time system. Will populate form with the default
222218
* bounds and deltas defined in the selected time system.
223219
*
@@ -226,7 +222,13 @@ define(
226222
*/
227223
TimeConductorController.prototype.changeTimeSystem = function (newTimeSystem) {
228224
if (newTimeSystem && (newTimeSystem !== this.$scope.timeSystemModel.selected)) {
229-
this.setFormFromDeltas((newTimeSystem.defaults() || {}).deltas);
225+
if (newTimeSystem.defaults()){
226+
var deltas = newTimeSystem.defaults().deltas || {start: 0, end: 0};
227+
var bounds = newTimeSystem.defaults().bounds || {start: 0, end: 0};
228+
229+
this.setFormFromDeltas(deltas);
230+
this.setFormFromBounds(bounds);
231+
}
230232
this.setFormFromTimeSystem(newTimeSystem);
231233
}
232234
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
/*****************************************************************************
2+
* Open MCT Web, Copyright (c) 2014-2015, United States Government
3+
* as represented by the Administrator of the National Aeronautics and Space
4+
* Administration. All rights reserved.
5+
*
6+
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
* License for the specific language governing permissions and limitations
15+
* under the License.
16+
*
17+
* Open MCT Web includes source code licensed under additional open source
18+
* licenses. See the Open Source Licenses file (LICENSES.md) included with
19+
* this source code distribution or the Licensing information page available
20+
* at runtime from the About dialog for additional information.
21+
*****************************************************************************/
22+
23+
define(['./TimeConductorController'], function (TimeConductorController) {
24+
describe("The time conductor controller", function () {
25+
var mockScope;
26+
var mockWindow;
27+
var mockTimeConductor;
28+
var mockConductorViewService;
29+
var mockTimeSystems;
30+
var controller;
31+
32+
beforeEach(function () {
33+
mockScope = jasmine.createSpyObj("$scope", ["$watch"]);
34+
mockWindow = jasmine.createSpyObj("$window", ["requestAnimationFrame"]);
35+
mockTimeConductor = jasmine.createSpyObj(
36+
"TimeConductor",
37+
[
38+
"bounds",
39+
"timeSystem",
40+
"on"
41+
]
42+
);
43+
mockTimeConductor.bounds.andReturn({start: undefined, end: undefined});
44+
45+
mockConductorViewService = jasmine.createSpyObj(
46+
"ConductorViewService",
47+
[
48+
"availableModes",
49+
"mode",
50+
"availableTimeSystems",
51+
"deltas"
52+
]
53+
);
54+
mockConductorViewService.availableModes.andReturn([]);
55+
mockConductorViewService.availableTimeSystems.andReturn([]);
56+
57+
mockTimeSystems = [];
58+
});
59+
60+
function getListener(name) {
61+
return mockTimeConductor.on.calls.filter(function (call){
62+
return call.args[0] === name;
63+
})[0].args[1];
64+
}
65+
66+
describe("", function (){
67+
beforeEach(function() {
68+
controller = new TimeConductorController(
69+
mockScope,
70+
mockWindow,
71+
mockTimeConductor,
72+
mockConductorViewService,
73+
mockTimeSystems
74+
);
75+
});
76+
77+
});
78+
79+
describe("when time conductor state changes", function () {
80+
var mockFormat;
81+
var mockDeltaFormat;
82+
var defaultBounds;
83+
var defaultDeltas;
84+
var mockDefaults;
85+
var timeSystem;
86+
var tsListener;
87+
88+
beforeEach(function () {
89+
mockFormat = {};
90+
mockDeltaFormat = {};
91+
defaultBounds = {
92+
start: 2,
93+
end: 3
94+
};
95+
defaultDeltas = {
96+
start: 10,
97+
end: 20
98+
};
99+
mockDefaults = {
100+
deltas: defaultDeltas,
101+
bounds: defaultBounds
102+
};
103+
timeSystem = {
104+
formats: function () {
105+
return [mockFormat];
106+
},
107+
deltaFormat: function () {
108+
return mockDeltaFormat;
109+
},
110+
defaults: function () {
111+
return mockDefaults;
112+
}
113+
};
114+
115+
controller = new TimeConductorController(
116+
mockScope,
117+
mockWindow,
118+
mockTimeConductor,
119+
mockConductorViewService,
120+
mockTimeSystems
121+
);
122+
123+
tsListener = getListener("timeSystem");
124+
});
125+
126+
it("listens for changes to conductor state", function () {
127+
expect(mockTimeConductor.on).toHaveBeenCalledWith("timeSystem", jasmine.any(Function));
128+
expect(mockTimeConductor.on).toHaveBeenCalledWith("bounds", jasmine.any(Function));
129+
expect(mockTimeConductor.on).toHaveBeenCalledWith("follow", jasmine.any(Function));
130+
});
131+
132+
it("when time system changes, sets time system on scope", function () {
133+
expect(tsListener).toBeDefined();
134+
tsListener(timeSystem);
135+
136+
expect(mockScope.timeSystemModel).toBeDefined();
137+
expect(mockScope.timeSystemModel.selected).toBe(timeSystem);
138+
expect(mockScope.timeSystemModel.format).toBe(mockFormat);
139+
expect(mockScope.timeSystemModel.deltaFormat).toBe(mockDeltaFormat);
140+
});
141+
142+
it("when time system changes, sets defaults on scope", function () {
143+
expect(tsListener).toBeDefined();
144+
tsListener(timeSystem);
145+
146+
expect(mockScope.boundsModel.start).toEqual(defaultBounds.start);
147+
expect(mockScope.boundsModel.end).toEqual(defaultBounds.end);
148+
149+
expect(mockScope.boundsModel.startDelta).toEqual(defaultDeltas.start);
150+
expect(mockScope.boundsModel.endDelta).toEqual(defaultDeltas.end);
151+
});
152+
153+
it("when bounds change, sets them on scope", function () {
154+
var bounds = {
155+
start: 1,
156+
end: 2
157+
};
158+
159+
var boundsListener = getListener("bounds");
160+
expect(boundsListener).toBeDefined();
161+
boundsListener(bounds);
162+
163+
expect(mockScope.boundsModel).toBeDefined();
164+
expect(mockScope.boundsModel.start).toEqual(bounds.start);
165+
expect(mockScope.boundsModel.end).toEqual(bounds.end);
166+
});
167+
168+
it("responds to a change in 'follow' state of the time conductor", function () {
169+
var followListener = getListener("follow");
170+
expect(followListener).toBeDefined();
171+
172+
followListener(true);
173+
expect(mockScope.followMode).toEqual(true);
174+
175+
followListener(false);
176+
expect(mockScope.followMode).toEqual(false);
177+
});
178+
});
179+
180+
describe("when user makes changes from UI", function () {
181+
var mode = "realtime";
182+
var ts1Metadata;
183+
var ts2Metadata;
184+
var ts3Metadata;
185+
var mockTimeSystemConstructors;
186+
187+
beforeEach(function () {
188+
mode = "realtime";
189+
ts1Metadata = {
190+
'key': 'ts1',
191+
'name': 'Time System One',
192+
'cssClass': 'cssClassOne'
193+
};
194+
ts2Metadata = {
195+
'key': 'ts2',
196+
'name': 'Time System Two',
197+
'cssClass': 'cssClassTwo'
198+
};
199+
ts3Metadata = {
200+
'key': 'ts3',
201+
'name': 'Time System Three',
202+
'cssClass': 'cssClassThree'
203+
};
204+
mockTimeSystems = [
205+
{
206+
metadata: ts1Metadata
207+
},
208+
{
209+
metadata: ts2Metadata
210+
},
211+
{
212+
metadata: ts3Metadata
213+
}
214+
];
215+
216+
//Wrap in mock constructors
217+
mockTimeSystemConstructors = mockTimeSystems.map(function (mockTimeSystem) {
218+
return function () {
219+
return mockTimeSystem;
220+
};
221+
});
222+
});
223+
224+
it("sets the mode on scope", function () {
225+
controller = new TimeConductorController(
226+
mockScope,
227+
mockWindow,
228+
mockTimeConductor,
229+
mockConductorViewService,
230+
mockTimeSystemConstructors
231+
);
232+
233+
mockConductorViewService.availableTimeSystems.andReturn(mockTimeSystems);
234+
controller.setMode(mode);
235+
236+
expect(mockScope.modeModel.selectedKey).toEqual(mode);
237+
});
238+
239+
it("sets available time systems on scope when mode changes", function () {
240+
controller = new TimeConductorController(
241+
mockScope,
242+
mockWindow,
243+
mockTimeConductor,
244+
mockConductorViewService,
245+
mockTimeSystemConstructors
246+
);
247+
248+
mockConductorViewService.availableTimeSystems.andReturn(mockTimeSystems);
249+
controller.setMode(mode);
250+
251+
expect(mockScope.timeSystemModel.options.length).toEqual(3);
252+
expect(mockScope.timeSystemModel.options[0]).toEqual(ts1Metadata);
253+
expect(mockScope.timeSystemModel.options[1]).toEqual(ts2Metadata);
254+
expect(mockScope.timeSystemModel.options[2]).toEqual(ts3Metadata);
255+
});
256+
257+
it("sets bounds on the time conductor", function () {
258+
var formModel = {
259+
start: 1,
260+
end: 10
261+
};
262+
263+
264+
controller = new TimeConductorController(
265+
mockScope,
266+
mockWindow,
267+
mockTimeConductor,
268+
mockConductorViewService,
269+
mockTimeSystemConstructors
270+
);
271+
272+
controller.updateBoundsFromForm(formModel);
273+
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(formModel);
274+
});
275+
276+
it("applies deltas when they change in form", function () {
277+
var deltas = {
278+
start: 1000,
279+
end: 2000
280+
};
281+
var formModel = {
282+
startDelta: deltas.start,
283+
endDelta: deltas.end
284+
};
285+
286+
controller = new TimeConductorController(
287+
mockScope,
288+
mockWindow,
289+
mockTimeConductor,
290+
mockConductorViewService,
291+
mockTimeSystemConstructors
292+
);
293+
294+
controller.updateDeltasFromForm(formModel);
295+
expect(mockConductorViewService.deltas).toHaveBeenCalledWith(deltas);
296+
});
297+
298+
it("sets the time system on the time conductor", function () {
299+
var defaultBounds = {
300+
start: 5,
301+
end: 6
302+
};
303+
var timeSystem = {
304+
metadata: {
305+
key: 'testTimeSystem'
306+
},
307+
defaults: function() {
308+
return {
309+
bounds: defaultBounds
310+
};
311+
}
312+
};
313+
314+
mockTimeSystems = [
315+
// Wrap as constructor function
316+
function() {
317+
return timeSystem;
318+
}
319+
];
320+
321+
controller = new TimeConductorController(
322+
mockScope,
323+
mockWindow,
324+
mockTimeConductor,
325+
mockConductorViewService,
326+
mockTimeSystems
327+
);
328+
329+
controller.selectTimeSystemByKey('testTimeSystem');
330+
expect(mockTimeConductor.timeSystem).toHaveBeenCalledWith(timeSystem, defaultBounds);
331+
});
332+
});
333+
334+
});
335+
});

Diff for: ‎platform/features/conductor-v2/conductor/src/ui/TimeConductorMode.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ define(
3939
this._tickSourceUnlisten = undefined;
4040
this._timeSystems = timeSystems;
4141
this._availableTickSources = undefined;
42-
4342
this.changeTimeSystem = this.changeTimeSystem.bind(this);
43+
this.tick = this.tick.bind(this);
4444

4545
//Set the time system initially
4646
if (conductor.timeSystem()) {
@@ -81,6 +81,7 @@ define(
8181
end: 0
8282
}
8383
};
84+
8485
this.conductor.bounds(defaults.bounds);
8586
this.deltas(defaults.deltas);
8687

@@ -130,7 +131,7 @@ define(
130131
}
131132
this._tickSource = tickSource;
132133
if (tickSource) {
133-
this._tickSourceUnlisten = tickSource.listen(this.tick.bind(this));
134+
this._tickSourceUnlisten = tickSource.listen(this.tick);
134135
//Now following a tick source
135136
this.conductor.follow(true);
136137
} else {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*****************************************************************************
2+
* Open MCT Web, Copyright (c) 2014-2015, United States Government
3+
* as represented by the Administrator of the National Aeronautics and Space
4+
* Administration. All rights reserved.
5+
*
6+
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
* License for the specific language governing permissions and limitations
15+
* under the License.
16+
*
17+
* Open MCT Web includes source code licensed under additional open source
18+
* licenses. See the Open Source Licenses file (LICENSES.md) included with
19+
* this source code distribution or the Licensing information page available
20+
* at runtime from the About dialog for additional information.
21+
*****************************************************************************/
22+
23+
define(['./TimeConductorMode'], function (TimeConductorMode) {
24+
describe("The Time Conductor Mode", function () {
25+
var mockTimeConductor,
26+
fixedModeMetaData,
27+
mockTimeSystems,
28+
fixedTimeSystem,
29+
30+
realtimeModeMetaData,
31+
realtimeTimeSystem,
32+
mockTickSource,
33+
34+
mockBounds,
35+
mode;
36+
37+
beforeEach(function () {
38+
fixedModeMetaData = {
39+
key: "fixed"
40+
};
41+
realtimeModeMetaData = {
42+
key: "realtime"
43+
};
44+
mockBounds = {
45+
start: 0,
46+
end: 1
47+
};
48+
49+
fixedTimeSystem = jasmine.createSpyObj("timeSystem", [
50+
"defaults",
51+
"tickSources"
52+
]);
53+
fixedTimeSystem.tickSources.andReturn([]);
54+
55+
mockTickSource = jasmine.createSpyObj("tickSource", [
56+
"listen"
57+
]);
58+
mockTickSource.metadata = {
59+
mode: "realtime"
60+
};
61+
realtimeTimeSystem = jasmine.createSpyObj("realtimeTimeSystem", [
62+
"defaults",
63+
"tickSources"
64+
]);
65+
realtimeTimeSystem.tickSources.andReturn([mockTickSource]);
66+
67+
//Do not return any time systems initially for a default
68+
// construction configuration that works without any additional work
69+
mockTimeSystems = [];
70+
71+
mockTimeConductor = jasmine.createSpyObj("timeConductor", [
72+
"bounds",
73+
"timeSystem",
74+
"on",
75+
"off",
76+
"follow"
77+
]);
78+
mockTimeConductor.bounds.andReturn(mockBounds);
79+
});
80+
81+
it("Reacts to changes in conductor time system", function () {
82+
mode = new TimeConductorMode(fixedModeMetaData, mockTimeConductor, mockTimeSystems);
83+
expect(mockTimeConductor.on).toHaveBeenCalledWith("timeSystem", mode.changeTimeSystem);
84+
});
85+
86+
it("Stops listening to time system changes on destroy", function () {
87+
mode = new TimeConductorMode(fixedModeMetaData, mockTimeConductor, mockTimeSystems);
88+
mode.destroy();
89+
expect(mockTimeConductor.off).toHaveBeenCalledWith("timeSystem", mode.changeTimeSystem);
90+
});
91+
92+
it("Filters available time systems to those with tick sources that" +
93+
" support this mode", function () {
94+
mockTimeSystems = [fixedTimeSystem, realtimeTimeSystem];
95+
mode = new TimeConductorMode(realtimeModeMetaData, mockTimeConductor, mockTimeSystems);
96+
97+
var availableTimeSystems = mode.availableTimeSystems();
98+
expect(availableTimeSystems.length).toBe(1);
99+
expect(availableTimeSystems.indexOf(fixedTimeSystem)).toBe(-1);
100+
expect(availableTimeSystems.indexOf(realtimeTimeSystem)).toBe(0);
101+
});
102+
103+
describe("Changing the time system", function () {
104+
var defaults;
105+
106+
beforeEach(function () {
107+
defaults = {
108+
bounds: {
109+
start: 1,
110+
end: 2
111+
},
112+
deltas: {
113+
start: 3,
114+
end: 4
115+
}
116+
};
117+
118+
fixedTimeSystem.defaults.andReturn(defaults);
119+
120+
});
121+
it ("sets defaults from new time system", function() {
122+
mode = new TimeConductorMode(fixedModeMetaData, mockTimeConductor, mockTimeSystems);
123+
spyOn(mode, "deltas");
124+
mode.deltas.andCallThrough();
125+
126+
mode.changeTimeSystem(fixedTimeSystem);
127+
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(defaults.bounds);
128+
expect(mode.deltas).toHaveBeenCalledWith(defaults.deltas);
129+
});
130+
it ("If a tick source is available, sets the tick source", function() {
131+
mode = new TimeConductorMode(realtimeModeMetaData, mockTimeConductor, mockTimeSystems);
132+
mode.changeTimeSystem(realtimeTimeSystem);
133+
134+
var currentTickSource = mode.tickSource();
135+
expect(currentTickSource).toBe(mockTickSource);
136+
});
137+
});
138+
139+
describe("Setting a tick source", function () {
140+
var mockUnlistener;
141+
142+
beforeEach(function() {
143+
mockUnlistener = jasmine.createSpy("unlistener");
144+
mockTickSource.listen.andReturn(mockUnlistener);
145+
146+
mode = new TimeConductorMode(realtimeModeMetaData, mockTimeConductor, mockTimeSystems);
147+
mode.tickSource(mockTickSource);
148+
});
149+
150+
it ("Unlistens from old tick source", function() {
151+
mode.tickSource(mockTickSource);
152+
expect(mockUnlistener).toHaveBeenCalled();
153+
});
154+
155+
it ("Listens to new tick source", function() {
156+
expect(mockTickSource.listen).toHaveBeenCalledWith(mode.tick);
157+
});
158+
159+
it ("Sets 'follow' state on time conductor", function() {
160+
expect(mockTimeConductor.follow).toHaveBeenCalledWith(true);
161+
});
162+
163+
it ("on destroy, unlistens from tick source", function() {
164+
mode.destroy();
165+
expect(mockUnlistener).toHaveBeenCalled();
166+
});
167+
});
168+
169+
describe("setting deltas", function () {
170+
beforeEach(function() {
171+
mode = new TimeConductorMode(realtimeModeMetaData, mockTimeConductor, mockTimeSystems);
172+
});
173+
it ("sets the bounds on the time conductor based on new delta" +
174+
" values", function() {
175+
var deltas = {
176+
start: 20,
177+
end: 10
178+
};
179+
180+
mode.deltas(deltas);
181+
182+
expect(mockTimeConductor.bounds).toHaveBeenCalledWith({
183+
start: mockBounds.end - deltas.start,
184+
end: mockBounds.end + deltas.end
185+
});
186+
});
187+
});
188+
189+
describe("ticking", function () {
190+
beforeEach(function() {
191+
mode = new TimeConductorMode(realtimeModeMetaData, mockTimeConductor, mockTimeSystems);
192+
});
193+
it ("sets bounds based on current delta values", function() {
194+
var deltas = {
195+
start: 20,
196+
end: 10
197+
};
198+
var time = 100;
199+
200+
mode.deltas(deltas);
201+
mode.tick(time);
202+
203+
expect(mockTimeConductor.bounds).toHaveBeenCalledWith({
204+
start: time - deltas.start,
205+
end:time + deltas.end
206+
});
207+
});
208+
});
209+
});
210+
});

Diff for: ‎platform/features/conductor-v2/conductor/src/ui/TimeConductorValidation.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ define(
3131
*/
3232
function TimeConductorValidation(conductor) {
3333
var self = this;
34-
this.conductor = conductor
34+
this.conductor = conductor;
3535

3636
/*
3737
* Bind all class functions to 'this'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*****************************************************************************
2+
* Open MCT Web, Copyright (c) 2014-2015, United States Government
3+
* as represented by the Administrator of the National Aeronautics and Space
4+
* Administration. All rights reserved.
5+
*
6+
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
* License for the specific language governing permissions and limitations
15+
* under the License.
16+
*
17+
* Open MCT Web includes source code licensed under additional open source
18+
* licenses. See the Open Source Licenses file (LICENSES.md) included with
19+
* this source code distribution or the Licensing information page available
20+
* at runtime from the About dialog for additional information.
21+
*****************************************************************************/
22+
23+
define(['./TimeConductorValidation'], function (TimeConductorValidation) {
24+
describe("The Time Conductor Validation class", function () {
25+
var timeConductorValidation,
26+
mockTimeConductor;
27+
28+
beforeEach(function () {
29+
mockTimeConductor = jasmine.createSpyObj("timeConductor", [
30+
"validateBounds",
31+
"bounds"
32+
]);
33+
timeConductorValidation = new TimeConductorValidation(mockTimeConductor);
34+
});
35+
36+
describe("Validates start and end values using Time Conductor", function () {
37+
beforeEach(function() {
38+
var mockBounds = {
39+
start: 10,
40+
end: 20
41+
};
42+
43+
mockTimeConductor.bounds.andReturn(mockBounds);
44+
45+
});
46+
it("Validates start values using Time Conductor", function () {
47+
var startValue = 30;
48+
timeConductorValidation.validateStart(startValue);
49+
expect(mockTimeConductor.validateBounds).toHaveBeenCalled();
50+
});
51+
it("Validates end values using Time Conductor", function () {
52+
var endValue = 40;
53+
timeConductorValidation.validateEnd(endValue);
54+
expect(mockTimeConductor.validateBounds).toHaveBeenCalled();
55+
});
56+
});
57+
58+
it("Validates that start delta is valid number > 0", function () {
59+
expect(timeConductorValidation.validateStartDelta(-1)).toBe(false);
60+
expect(timeConductorValidation.validateStartDelta("abc")).toBe(false);
61+
expect(timeConductorValidation.validateStartDelta("1")).toBe(true);
62+
expect(timeConductorValidation.validateStartDelta(1)).toBe(true);
63+
});
64+
65+
it("Validates that end delta is valid number >= 0", function () {
66+
expect(timeConductorValidation.validateEndDelta(-1)).toBe(false);
67+
expect(timeConductorValidation.validateEndDelta("abc")).toBe(false);
68+
expect(timeConductorValidation.validateEndDelta("1")).toBe(true);
69+
expect(timeConductorValidation.validateEndDelta(0)).toBe(true);
70+
expect(timeConductorValidation.validateEndDelta(1)).toBe(true);
71+
});
72+
});
73+
});

Diff for: ‎platform/features/conductor-v2/conductor/src/ui/TimeConductorViewService.js

+22-15
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ define(
3636
* @constructor
3737
*/
3838
function TimeConductorViewService(conductor, timeSystems) {
39-
this._timeSystems = timeSystems = timeSystems.map(
39+
this._timeSystems = timeSystems.map(
4040
function (timeSystemConstructor) {
4141
return timeSystemConstructor();
4242
});
@@ -63,34 +63,38 @@ define(
6363
}
6464
};
6565

66-
function timeSystemsForMode(sourceType) {
67-
return timeSystems.filter(function (timeSystem){
68-
return timeSystem.tickSources().some(function (tickSource){
69-
return tickSource.metadata.mode === sourceType;
70-
});
66+
function hasTickSource(sourceType, timeSystem) {
67+
return timeSystem.tickSources().some(function (tickSource){
68+
return tickSource.metadata.mode === sourceType;
7169
});
7270
}
7371

72+
var timeSystemsForMode = function (sourceType) {
73+
return this._timeSystems.filter(hasTickSource.bind(this, sourceType));
74+
}.bind(this);
75+
7476
//Only show 'real-time mode' if appropriate time systems available
7577
if (timeSystemsForMode('realtime').length > 0 ) {
76-
this._availableModes['realtime'] = {
78+
var realtimeMode = {
7779
key: 'realtime',
7880
cssclass: 'icon-clock',
7981
label: 'Real-time',
8082
name: 'Real-time Mode',
8183
description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.'
8284
};
85+
this._availableModes[realtimeMode.key] = realtimeMode;
8386
}
8487

8588
//Only show 'LAD mode' if appropriate time systems available
8689
if (timeSystemsForMode('LAD').length > 0) {
87-
this._availableModes['latest'] = {
90+
var ladMode = {
8891
key: 'LAD',
8992
cssclass: 'icon-database',
9093
label: 'LAD',
9194
name: 'LAD Mode',
9295
description: 'Latest Available Data mode monitors real-time streaming data as it comes in. The Time Conductor and displays will only advance when data becomes available.'
9396
};
97+
this._availableModes[ladMode.key] = ladMode;
9498
}
9599
}
96100

@@ -114,6 +118,12 @@ define(
114118
*
115119
*/
116120
TimeConductorViewService.prototype.mode = function (newModeKey) {
121+
function contains(timeSystems, ts) {
122+
return timeSystems.filter(function (t) {
123+
return t.metadata.key === ts.metadata.key;
124+
}).length > 0;
125+
}
126+
117127
if (arguments.length === 1) {
118128
var timeSystem = this._conductor.timeSystem();
119129
var modes = this.availableModes();
@@ -122,13 +132,10 @@ define(
122132
if (this._mode) {
123133
this._mode.destroy();
124134
}
125-
126-
function contains(timeSystems, timeSystem) {
127-
return timeSystems.find(function (t) {
128-
return t.metadata.key === timeSystem.metadata.key;
129-
}) !== undefined;
130-
}
131135
this._mode = new TimeConductorMode(modeMetaData, this._conductor, this._timeSystems);
136+
137+
// If no time system set on time conductor, or the currently selected time system is not available in
138+
// the new mode, default to first available time system
132139
if (!timeSystem || !contains(this._mode.availableTimeSystems(), timeSystem)) {
133140
timeSystem = this._mode.availableTimeSystems()[0];
134141
this._conductor.timeSystem(timeSystem, timeSystem.defaults().bounds);
@@ -169,7 +176,7 @@ define(
169176
*/
170177
TimeConductorViewService.prototype.deltas = function () {
171178
//Deltas stored on mode. Use .apply to preserve arguments
172-
return this._mode.deltas.apply(this._mode, arguments)
179+
return this._mode.deltas.apply(this._mode, arguments);
173180
};
174181

175182
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*****************************************************************************
2+
* Open MCT Web, Copyright (c) 2014-2015, United States Government
3+
* as represented by the Administrator of the National Aeronautics and Space
4+
* Administration. All rights reserved.
5+
*
6+
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
* License for the specific language governing permissions and limitations
15+
* under the License.
16+
*
17+
* Open MCT Web includes source code licensed under additional open source
18+
* licenses. See the Open Source Licenses file (LICENSES.md) included with
19+
* this source code distribution or the Licensing information page available
20+
* at runtime from the About dialog for additional information.
21+
*****************************************************************************/
22+
23+
define(['./TimeConductorViewService'], function (TimeConductorViewService) {
24+
describe("The Time Conductor view service", function () {
25+
var mockTimeConductor;
26+
var basicTimeSystem;
27+
var tickingTimeSystem;
28+
var viewService;
29+
var tickingTimeSystemDefaults;
30+
31+
function mockConstructor(object) {
32+
return function () {
33+
return object;
34+
};
35+
}
36+
37+
beforeEach(function () {
38+
mockTimeConductor = jasmine.createSpyObj("timeConductor", [
39+
"timeSystem",
40+
"bounds",
41+
"follow",
42+
"on",
43+
"off"
44+
]);
45+
46+
basicTimeSystem = jasmine.createSpyObj("basicTimeSystem", [
47+
"tickSources",
48+
"defaults"
49+
]);
50+
basicTimeSystem.metadata = {
51+
key: "basic"
52+
};
53+
basicTimeSystem.tickSources.andReturn([]);
54+
basicTimeSystem.defaults.andReturn({
55+
bounds: {
56+
start: 0,
57+
end: 1
58+
},
59+
deltas: {
60+
start: 0,
61+
end: 0
62+
}
63+
});
64+
//Initialize conductor
65+
mockTimeConductor.timeSystem.andReturn(basicTimeSystem);
66+
mockTimeConductor.bounds.andReturn({start: 0, end: 1});
67+
68+
tickingTimeSystem = jasmine.createSpyObj("tickingTimeSystem", [
69+
"tickSources",
70+
"defaults"
71+
]);
72+
tickingTimeSystem.metadata = {
73+
key: "ticking"
74+
};
75+
tickingTimeSystemDefaults = {
76+
bounds: {
77+
start: 100,
78+
end: 200
79+
},
80+
deltas: {
81+
start: 1000,
82+
end: 500
83+
}
84+
};
85+
tickingTimeSystem.defaults.andReturn(tickingTimeSystemDefaults);
86+
});
87+
88+
it("At a minimum supports fixed mode", function () {
89+
var mockTimeSystems = [mockConstructor(basicTimeSystem)];
90+
viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
91+
92+
var availableModes = viewService.availableModes();
93+
expect(availableModes.fixed).toBeDefined();
94+
});
95+
96+
it("Supports realtime mode if appropriate tick source(s) availables", function () {
97+
var mockTimeSystems = [mockConstructor(tickingTimeSystem)];
98+
var mockRealtimeTickSource = {
99+
metadata: {
100+
mode: 'realtime'
101+
}
102+
};
103+
tickingTimeSystem.tickSources.andReturn([mockRealtimeTickSource]);
104+
105+
viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
106+
107+
var availableModes = viewService.availableModes();
108+
expect(availableModes.realtime).toBeDefined();
109+
});
110+
111+
it("Supports LAD mode if appropriate tick source(s) available", function () {
112+
var mockTimeSystems = [mockConstructor(tickingTimeSystem)];
113+
var mockLADTickSource = {
114+
metadata: {
115+
mode: 'LAD'
116+
}
117+
};
118+
tickingTimeSystem.tickSources.andReturn([mockLADTickSource]);
119+
120+
viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
121+
122+
var availableModes = viewService.availableModes();
123+
expect(availableModes.LAD).toBeDefined();
124+
});
125+
126+
describe("when mode is changed", function () {
127+
128+
it("destroys previous mode", function () {
129+
var mockTimeSystems = [mockConstructor(basicTimeSystem)];
130+
131+
var oldMode = jasmine.createSpyObj("conductorMode", [
132+
"destroy"
133+
]);
134+
135+
viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
136+
viewService._mode = oldMode;
137+
viewService.mode('fixed');
138+
expect(oldMode.destroy).toHaveBeenCalled();
139+
});
140+
141+
describe("the time system", function () {
142+
it("is retained if available in new mode", function () {
143+
var mockTimeSystems = [mockConstructor(basicTimeSystem), mockConstructor(tickingTimeSystem)];
144+
var mockRealtimeTickSource = {
145+
metadata: {
146+
mode: 'realtime'
147+
},
148+
listen: function() {}
149+
};
150+
tickingTimeSystem.tickSources.andReturn([mockRealtimeTickSource]);
151+
152+
viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
153+
154+
//Set time system to one known to support realtime mode
155+
mockTimeConductor.timeSystem.andReturn(tickingTimeSystem);
156+
157+
//Select realtime mode
158+
mockTimeConductor.timeSystem.reset();
159+
viewService.mode('realtime');
160+
expect(mockTimeConductor.timeSystem).not.toHaveBeenCalledWith(tickingTimeSystem, tickingTimeSystemDefaults.bounds);
161+
});
162+
it("is defaulted if selected time system not available in new mode", function () {
163+
var mockTimeSystems = [mockConstructor(basicTimeSystem), mockConstructor(tickingTimeSystem)];
164+
var mockRealtimeTickSource = {
165+
metadata: {
166+
mode: 'realtime'
167+
},
168+
listen: function() {}
169+
};
170+
tickingTimeSystem.tickSources.andReturn([mockRealtimeTickSource]);
171+
172+
viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
173+
174+
//Set time system to one known to not support realtime mode
175+
mockTimeConductor.timeSystem.andReturn(basicTimeSystem);
176+
177+
//Select realtime mode
178+
mockTimeConductor.timeSystem.reset();
179+
viewService.mode('realtime');
180+
expect(mockTimeConductor.timeSystem).toHaveBeenCalledWith(tickingTimeSystem, tickingTimeSystemDefaults.bounds);
181+
});
182+
});
183+
});
184+
});
185+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*****************************************************************************
2+
* Open MCT Web, Copyright (c) 2014-2015, United States Government
3+
* as represented by the Administrator of the National Aeronautics and Space
4+
* Administration. All rights reserved.
5+
*
6+
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
* License for the specific language governing permissions and limitations
15+
* under the License.
16+
*
17+
* Open MCT Web includes source code licensed under additional open source
18+
* licenses. See the Open Source Licenses file (LICENSES.md) included with
19+
* this source code distribution or the Licensing information page available
20+
* at runtime from the About dialog for additional information.
21+
*****************************************************************************/
22+
23+
define(['./UTCTimeSystem'], function (UTCTimeSystem) {
24+
describe("The UTCTimeSystem class", function () {
25+
var timeSystem,
26+
mockTimeout;
27+
28+
beforeEach(function () {
29+
mockTimeout = jasmine.createSpy("timeout");
30+
timeSystem = new UTCTimeSystem(mockTimeout);
31+
});
32+
33+
it("defines at least one format", function () {
34+
expect(timeSystem.formats().length).toBeGreaterThan(0);
35+
});
36+
37+
it("defines a tick source", function () {
38+
var tickSources = timeSystem.tickSources();
39+
expect(tickSources.length).toBeGreaterThan(0);
40+
});
41+
42+
it("defines some defaults", function () {
43+
expect(timeSystem.defaults()).toBeDefined();
44+
});
45+
});
46+
});

Diff for: ‎platform/features/plot/src/PlotController.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ define(
239239
setBasePanZoom(bounds);
240240
requery();
241241
}
242-
self.updateStatus($scope.domainObject, follow);
242+
self.setUnsynchedStatus($scope.domainObject, follow && self.isZoomed());
243243
}
244244

245245
this.modeOptions = new PlotModeOptions([], subPlotFactory);
@@ -370,9 +370,9 @@ define(
370370
return this.pending;
371371
};
372372

373-
PlotController.prototype.updateStatus = function (domainObject, follow) {
373+
PlotController.prototype.setUnsynchedStatus = function (domainObject, status) {
374374
if (domainObject.hasCapability('status')) {
375-
domainObject.getCapability('status').set('timeconductor-unsynced', follow && this.isZoomed());
375+
domainObject.getCapability('status').set('timeconductor-unsynced', status);
376376
}
377377
};
378378

Diff for: ‎platform/features/plot/test/PlotControllerSpec.js

+35-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ define(
3535
mockHandle,
3636
mockDomainObject,
3737
mockSeries,
38+
mockStatusCapability,
3839
controller;
3940

4041
function bind(method, thisObj) {
@@ -71,7 +72,7 @@ define(
7172
);
7273
mockDomainObject = jasmine.createSpyObj(
7374
"domainObject",
74-
["getId", "getModel", "getCapability"]
75+
["getId", "getModel", "getCapability", "hasCapability"]
7576
);
7677
mockHandler = jasmine.createSpyObj(
7778
"telemetrySubscriber",
@@ -95,6 +96,11 @@ define(
9596
['getPointCount', 'getDomainValue', 'getRangeValue']
9697
);
9798

99+
mockStatusCapability = jasmine.createSpyObj(
100+
"statusCapability",
101+
["set"]
102+
);
103+
98104
mockHandler.handle.andReturn(mockHandle);
99105
mockThrottle.andCallFake(function (fn) {
100106
return fn;
@@ -230,6 +236,34 @@ define(
230236
expect(bind(controller.unzoom, controller)).not.toThrow();
231237
});
232238

239+
it("sets status when plot becomes detached from time conductor", function () {
240+
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
241+
242+
function boundsEvent(){
243+
fireEvent("telemetry:display:bounds", [
244+
{},
245+
{ start: 10, end: 100 },
246+
true
247+
]);
248+
}
249+
250+
mockDomainObject.hasCapability.andCallFake(function (name) {
251+
return name === "status";
252+
});
253+
mockDomainObject.getCapability.andReturn(mockStatusCapability);
254+
spyOn(controller, "isZoomed");
255+
256+
//Mock zoomed in state
257+
controller.isZoomed.andReturn(true);
258+
boundsEvent();
259+
expect(mockStatusCapability.set).toHaveBeenCalledWith("timeconductor-unsynced", true);
260+
261+
//"Reset" zoom
262+
controller.isZoomed.andReturn(false);
263+
boundsEvent();
264+
expect(mockStatusCapability.set).toHaveBeenCalledWith("timeconductor-unsynced", false);
265+
});
266+
233267
it("indicates if a request is pending", function () {
234268
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
235269
expect(controller.isRequestPending()).toBeTruthy();
@@ -286,7 +320,6 @@ define(
286320
expect(mockHandle.request.calls.length).toEqual(2);
287321
});
288322

289-
290323
it("maintains externally-provided domain axis bounds after data is received", function () {
291324
mockSeries.getPointCount.andReturn(3);
292325
mockSeries.getRangeValue.andReturn(42);

0 commit comments

Comments
 (0)
Please sign in to comment.