diff --git a/platform/commonUI/general/res/sass/features/_time-display.scss b/platform/commonUI/general/res/sass/features/_time-display.scss index 94f64f9bb7..8c019860ad 100644 --- a/platform/commonUI/general/res/sass/features/_time-display.scss +++ b/platform/commonUI/general/res/sass/features/_time-display.scss @@ -1,33 +1,36 @@ .l-time-display { $transTime: 200ms; + $controlSize: 14px; + $control1ControlW: $controlSize + $interiorMargin; + $control2ControlW: $control1ControlW * 2; line-height: 140%; &:hover { - .l-btn.control { + .l-btn.controls { opacity: 1; } } &.l-timer { - .l-value:before, - .control { - font-size: 0.8em; - } - .l-value:before { // Direction +/- element + font-size: $controlSize; margin-right: $interiorMarginSm; + } - .control { + .controls { @include trans-prop-nice((width, opacity), $transTime); + font-size: $controlSize; line-height: inherit; margin-right: 0; opacity: 0; width: 0; + .flex-elem { + margin-right: $interiorMargin; + } } - &:hover .control { - margin-right: $interiorMargin; + &:hover .controls { opacity: 1; - width: 1em; + width: $control2ControlW; } } @@ -35,4 +38,34 @@ color: pullForward($colorBodyFg, 50%); font-weight: 400; } + + // States + &.s-state-stopped, + &.s-state-paused { + .l-value { + opacity: 0.4; + } + } + + &.s-state-started { + .l-value { + opacity: 1; + } + } + + &.s-state-stopped { + // Hide Stop button, 1controlW + .t-btn-stop { + display: none; + } + &:hover .controls { width: $control1ControlW; } + + } + + &.s-state-paused { + // Paused, do something visual + .l-value { + &:before { @extend .pulse; } + } + } } diff --git a/platform/features/clock/bundle.js b/platform/features/clock/bundle.js index c7b941c1d9..022f81f809 100644 --- a/platform/features/clock/bundle.js +++ b/platform/features/clock/bundle.js @@ -28,6 +28,8 @@ define([ "./src/controllers/RefreshingController", "./src/actions/StartTimerAction", "./src/actions/RestartTimerAction", + "./src/actions/StopTimerAction", + "./src/actions/PauseTimerAction", "text!./res/templates/clock.html", "text!./res/templates/timer.html", 'legacyRegistry' @@ -39,6 +41,8 @@ define([ RefreshingController, StartTimerAction, RestartTimerAction, + StopTimerAction, + PauseTimerAction, clockTemplate, timerTemplate, legacyRegistry @@ -139,6 +143,17 @@ define([ "cssClass": "icon-play", "priority": "preferred" }, + { + "key": "timer.pause", + "implementation": PauseTimerAction, + "depends": [ + "now" + ], + "category": "contextual", + "name": "Pause", + "cssClass": "icon-pause", + "priority": "preferred" + }, { "key": "timer.restart", "implementation": RestartTimerAction, @@ -149,6 +164,17 @@ define([ "name": "Restart at 0", "cssClass": "icon-refresh", "priority": "preferred" + }, + { + "key": "timer.stop", + "implementation": StopTimerAction, + "depends": [ + "now" + ], + "category": "contextual", + "name": "Stop", + "cssClass": "icon-box", + "priority": "preferred" } ], "types": [ diff --git a/platform/features/clock/res/templates/timer.html b/platform/features/clock/res/templates/timer.html index 0db8dbb4aa..d48a9691b3 100644 --- a/platform/features/clock/res/templates/timer.html +++ b/platform/features/clock/res/templates/timer.html @@ -19,11 +19,16 @@ this source code distribution or the Licensing information page available at runtime from the About dialog for additional information. --> -
+
- +
+ + +
{{timer.text() || "--:--:--"}} diff --git a/platform/features/clock/src/actions/AbstractStartTimerAction.js b/platform/features/clock/src/actions/PauseTimerAction.js similarity index 66% rename from platform/features/clock/src/actions/AbstractStartTimerAction.js rename to platform/features/clock/src/actions/PauseTimerAction.js index dc55f446f4..f3e5910bef 100644 --- a/platform/features/clock/src/actions/AbstractStartTimerAction.js +++ b/platform/features/clock/src/actions/PauseTimerAction.js @@ -25,13 +25,10 @@ define( function () { /** - * Implements the "Start" and "Restart" action for timers. + * Implements the "Pause" action for timers. * - * Sets the reference timestamp in a timer to the current - * time, such that it begins counting up. - * - * Both "Start" and "Restart" share this implementation, but - * control their visibility with different `appliesTo` behavior. + * Sets the reference pausedTime in a timer to the current + * time, such that it stops counting up. * * @implements {Action} * @memberof platform/features/clock @@ -40,22 +37,35 @@ define( * time (typically wrapping `Date.now`) * @param {ActionContext} context the context for this action */ - function AbstractStartTimerAction(now, context) { + function PauseTimerAction(now, context) { this.domainObject = context.domainObject; this.now = now; } - AbstractStartTimerAction.prototype.perform = function () { + PauseTimerAction.appliesTo = function (context) { + var model = + (context.domainObject && context.domainObject.getModel()) || + {}; + + + // We show this variant for timers which have + // a target time, or is in a playing state. + return model.type === 'timer' && + model.timerState === 'started'; + }; + + PauseTimerAction.prototype.perform = function () { var domainObject = this.domainObject, now = this.now; - function setTimestamp(model) { - model.timestamp = now(); + function updateModel(model) { + model.timerState = 'paused'; + model.pausedTime = now(); } - return domainObject.useCapability('mutation', setTimestamp); + return domainObject.useCapability('mutation', updateModel); }; - return AbstractStartTimerAction; + return PauseTimerAction; } ); diff --git a/platform/features/clock/src/actions/RestartTimerAction.js b/platform/features/clock/src/actions/RestartTimerAction.js index bb12cb1392..dad88dad3a 100644 --- a/platform/features/clock/src/actions/RestartTimerAction.js +++ b/platform/features/clock/src/actions/RestartTimerAction.js @@ -21,8 +21,8 @@ *****************************************************************************/ define( - ['./AbstractStartTimerAction'], - function (AbstractStartTimerAction) { + [], + function () { /** * Implements the "Restart at 0" action. @@ -30,7 +30,6 @@ define( * Behaves the same as (and delegates functionality to) * the "Start" action. * - * @extends {platform/features/clock.AbstractTimerAction} * @implements {Action} * @memberof platform/features/clock * @constructor @@ -39,24 +38,33 @@ define( * @param {ActionContext} context the context for this action */ function RestartTimerAction(now, context) { - AbstractStartTimerAction.apply(this, [now, context]); + this.domainObject = context.domainObject; + this.now = now; } - RestartTimerAction.prototype = - Object.create(AbstractStartTimerAction.prototype); - RestartTimerAction.appliesTo = function (context) { var model = (context.domainObject && context.domainObject.getModel()) || {}; - // We show this variant for timers which already have - // a target time. + // We show this variant for timers which already have a target time. return model.type === 'timer' && - model.timestamp !== undefined; + model.timerState !== 'stopped'; + }; + + RestartTimerAction.prototype.perform = function () { + var domainObject = this.domainObject, + now = this.now; + + function updateModel(model) { + model.timestamp = now(); + model.timerState = 'started'; + model.pausedTime = undefined; + } + + return domainObject.useCapability('mutation', updateModel); }; return RestartTimerAction; - } ); diff --git a/platform/features/clock/src/actions/StartTimerAction.js b/platform/features/clock/src/actions/StartTimerAction.js index cf3952a99d..a88170db57 100644 --- a/platform/features/clock/src/actions/StartTimerAction.js +++ b/platform/features/clock/src/actions/StartTimerAction.js @@ -21,8 +21,8 @@ *****************************************************************************/ define( - ['./AbstractStartTimerAction'], - function (AbstractStartTimerAction) { + [], + function () { /** * Implements the "Start" action for timers. @@ -30,7 +30,6 @@ define( * Sets the reference timestamp in a timer to the current * time, such that it begins counting up. * - * @extends {platform/features/clock.AbstractTimerAction} * @implements {Action} * @memberof platform/features/clock * @constructor @@ -39,12 +38,10 @@ define( * @param {ActionContext} context the context for this action */ function StartTimerAction(now, context) { - AbstractStartTimerAction.apply(this, [now, context]); + this.domainObject = context.domainObject; + this.now = now; } - StartTimerAction.prototype = - Object.create(AbstractStartTimerAction.prototype); - StartTimerAction.appliesTo = function (context) { var model = (context.domainObject && context.domainObject.getModel()) || @@ -53,10 +50,28 @@ define( // We show this variant for timers which do not yet have // a target time. return model.type === 'timer' && - model.timestamp === undefined; + model.timerState !== 'started'; + }; + + StartTimerAction.prototype.perform = function () { + var domainObject = this.domainObject, + now = this.now; + + function updateModel(model) { + //if we are resuming + if (model.pausedTime) { + var timeShift = now() - model.pausedTime; + model.timestamp = model.timestamp + timeShift; + } else { + model.timestamp = now(); + } + model.timerState = 'started'; + model.pausedTime = undefined; + } + + return domainObject.useCapability('mutation', updateModel); }; return StartTimerAction; - } ); diff --git a/platform/features/clock/src/actions/StopTimerAction.js b/platform/features/clock/src/actions/StopTimerAction.js new file mode 100644 index 0000000000..806e27c746 --- /dev/null +++ b/platform/features/clock/src/actions/StopTimerAction.js @@ -0,0 +1,71 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2009-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +define( + [], + function () { + + /** + * Implements the "Stop" action for timers. + * + * Sets the reference timestamp in a timer undefined, + * such that it is reset and makes no movements. + * + * @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 StopTimerAction(now, context) { + this.domainObject = context.domainObject; + this.now = now; + } + + StopTimerAction.appliesTo = function (context) { + var model = + (context.domainObject && context.domainObject.getModel()) || + {}; + + + // We show this variant for timers which do not yet have + // a target time. + return model.type === 'timer' && + model.timerState !== 'stopped'; + }; + + StopTimerAction.prototype.perform = function () { + var domainObject = this.domainObject; + + function updateModel(model) { + model.timestamp = undefined; + model.timerState = 'stopped'; + model.pausedTime = undefined; + } + + return domainObject.useCapability('mutation', updateModel); + }; + + return StopTimerAction; + } +); diff --git a/platform/features/clock/src/controllers/TimerController.js b/platform/features/clock/src/controllers/TimerController.js index fa9f14fcaa..5c92ee4991 100644 --- a/platform/features/clock/src/controllers/TimerController.js +++ b/platform/features/clock/src/controllers/TimerController.js @@ -42,6 +42,7 @@ define( active = true, relativeTimestamp, lastTimestamp, + relativeTimerState, self = this; function update() { @@ -51,12 +52,9 @@ define( self.textValue = formatter(timeDelta); self.signValue = timeDelta < 0 ? "-" : timeDelta >= 1000 ? "+" : ""; - self.signCssClass = timeDelta < 0 ? "icon-minus" : - timeDelta >= 1000 ? "icon-plus" : ""; } else { self.textValue = ""; self.signValue = ""; - self.signCssClass = ""; } } @@ -68,19 +66,50 @@ define( relativeTimestamp = timestamp; } + function updateTimerState(timerState) { + self.timerState = relativeTimerState = timerState; + } + + function updateActions(actionCapability, actionKey) { + self.relevantAction = actionCapability && + actionCapability.getActions(actionKey)[0]; + + self.stopAction = relativeTimerState !== 'stopped' ? + actionCapability && actionCapability.getActions('timer.stop')[0] : undefined; + + } + + function isPaused() { + return relativeTimerState === 'paused'; + } + + function handleLegacyTimer(model) { + if (model.timerState === undefined) { + model.timerState = model.timestamp === undefined ? + 'stopped' : 'started'; + } + } + function updateObject(domainObject) { - var model = domainObject.getModel(), - timestamp = model.timestamp, + var model = domainObject.getModel(); + handleLegacyTimer(model); + + var timestamp = model.timestamp, formatKey = model.timerFormat, + timerState = model.timerState, actionCapability = domainObject.getCapability('action'), - actionKey = (timestamp === undefined) ? - 'timer.start' : 'timer.restart'; + actionKey = (timerState !== 'started') ? + 'timer.start' : 'timer.pause'; updateFormat(formatKey); updateTimestamp(timestamp); + updateTimerState(timerState); + updateActions(actionCapability, actionKey); - self.relevantAction = actionCapability && - actionCapability.getActions(actionKey)[0]; + //if paused on startup show last known position + if (isPaused() && !lastTimestamp) { + lastTimestamp = model.pausedTime; + } update(); } @@ -98,8 +127,16 @@ define( function tick() { var lastSign = self.signValue, lastText = self.textValue; - lastTimestamp = now(); - update(); + + if (!isPaused()) { + lastTimestamp = now(); + update(); + } + + if (relativeTimerState === undefined) { + handleModification(); + } + // We're running in an animation frame, not in a digest cycle. // We need to trigger a digest cycle if our displayable data // changes. @@ -130,27 +167,27 @@ define( /** * Get the CSS class to display the right icon - * for the start/restart button. - * @returns {string} cssClass to display + * for the start/pause button. + * @returns {string} cssclass to display */ TimerController.prototype.buttonCssClass = function () { return this.relevantAction ? - this.relevantAction.getMetadata().cssClass : ""; + this.relevantAction.getMetadata().cssClass : ""; }; /** - * Get the text to show for the start/restart button + * Get the text to show for the start/pause button * (e.g. in a tooltip) * @returns {string} name of the action */ TimerController.prototype.buttonText = function () { return this.relevantAction ? - this.relevantAction.getMetadata().name : ""; + this.relevantAction.getMetadata().name : ""; }; /** - * Perform the action associated with the start/restart button. + * Perform the action associated with the start/pause button. */ TimerController.prototype.clickButton = function () { if (this.relevantAction) { @@ -159,6 +196,16 @@ define( } }; + /** + * Perform the action associated with the stop button. + */ + TimerController.prototype.clickStopButton = function () { + if (this.stopAction) { + this.stopAction.perform(); + this.updateObject(this.$scope.domainObject); + } + }; + /** * Get the sign (+ or -) of the current timer value, as * displayable text. @@ -168,15 +215,6 @@ define( return this.signValue; }; - /** - * Get the sign (+ or -) of the current timer value, as - * a CSS class. - * @returns {string} sign of the current timer value - */ - TimerController.prototype.signClass = function () { - return this.signCssClass; - }; - /** * Get the text to display for the current timer value. * @returns {string} current timer value diff --git a/platform/features/clock/test/actions/AbstractStartTimerActionSpec.js b/platform/features/clock/test/actions/PauseTimerActionSpec.js similarity index 52% rename from platform/features/clock/test/actions/AbstractStartTimerActionSpec.js rename to platform/features/clock/test/actions/PauseTimerActionSpec.js index 6478d07877..098e29c634 100644 --- a/platform/features/clock/test/actions/AbstractStartTimerActionSpec.js +++ b/platform/features/clock/test/actions/PauseTimerActionSpec.js @@ -21,28 +21,41 @@ *****************************************************************************/ define( - ["../../src/actions/AbstractStartTimerAction"], - function (AbstractStartTimerAction) { + ["../../src/actions/PauseTimerAction"], + function (PauseTimerAction) { - describe("A timer's start/restart action", function () { + describe("A timer's Pause action", function () { var mockNow, mockDomainObject, testModel, + testContext, action; function asPromise(value) { return (value || {}).then ? value : { - then: function (callback) { - return asPromise(callback(value)); - } - }; + then: function (callback) { + return asPromise(callback(value)); + } + }; + } + + function testState(type, timerState, timestamp, expected) { + testModel.type = type; + testModel.timerState = timerState; + testModel.timestamp = timestamp; + + if (expected) { + expect(PauseTimerAction.appliesTo(testContext)).toBeTruthy(); + } else { + expect(PauseTimerAction.appliesTo(testContext)).toBeFalsy(); + } } beforeEach(function () { mockNow = jasmine.createSpy('now'); mockDomainObject = jasmine.createSpyObj( 'domainObject', - ['getCapability', 'useCapability'] + ['getCapability', 'useCapability', 'getModel'] ); mockDomainObject.useCapability.andCallFake(function (c, v) { @@ -51,24 +64,41 @@ define( return asPromise(true); } }); + mockDomainObject.getModel.andCallFake(function () { + return testModel; + }); testModel = {}; + testContext = {domainObject: mockDomainObject}; - action = new AbstractStartTimerAction(mockNow, { - domainObject: mockDomainObject - }); + action = new PauseTimerAction(mockNow, testContext); }); - it("updates the model with a timestamp", function () { + it("updates the model with a timerState", function () { + testModel.timerState = 'started'; + action.perform(); + expect(testModel.timerState).toEqual('paused'); + }); + + it("updates the model with a pausedTime", function () { + testModel.pausedTime = undefined; mockNow.andReturn(12000); action.perform(); - expect(testModel.timestamp).toEqual(12000); + expect(testModel.pausedTime).toEqual(12000); }); - it("does not truncate milliseconds", function () { - mockNow.andReturn(42321); - action.perform(); - expect(testModel.timestamp).toEqual(42321); + it("applies only to timers in a playing state", function () { + //in a stopped state + testState('timer', 'stopped', undefined, false); + + //in a paused state + testState('timer', 'paused', 12000, false); + + //in a playing state + testState('timer', 'started', 12000, true); + + //not a timer + testState('clock', 'started', 12000, false); }); }); } diff --git a/platform/features/clock/test/actions/RestartTimerActionSpec.js b/platform/features/clock/test/actions/RestartTimerActionSpec.js index c0d4ded90d..fc1978322f 100644 --- a/platform/features/clock/test/actions/RestartTimerActionSpec.js +++ b/platform/features/clock/test/actions/RestartTimerActionSpec.js @@ -39,6 +39,18 @@ define( }; } + function testState(type, timerState, timestamp, expected) { + testModel.type = type; + testModel.timerState = timerState; + testModel.timestamp = timestamp; + + if (expected) { + expect(RestartTimerAction.appliesTo(testContext)).toBeTruthy(); + } else { + expect(RestartTimerAction.appliesTo(testContext)).toBeFalsy(); + } + } + beforeEach(function () { mockNow = jasmine.createSpy('now'); mockDomainObject = jasmine.createSpyObj( @@ -63,23 +75,36 @@ define( }); it("updates the model with a timestamp", function () { + testModel.pausedTime = 12000; mockNow.andReturn(12000); action.perform(); expect(testModel.timestamp).toEqual(12000); }); - it("applies only to timers with a target time", function () { - testModel.type = 'timer'; - testModel.timestamp = 12000; - expect(RestartTimerAction.appliesTo(testContext)).toBeTruthy(); + it("updates the model with a pausedTime", function () { + testModel.pausedTime = 12000; + action.perform(); + expect(testModel.pausedTime).toEqual(undefined); + }); - testModel.type = 'timer'; - testModel.timestamp = undefined; - expect(RestartTimerAction.appliesTo(testContext)).toBeFalsy(); + it("updates the model with a timerState", function () { + testModel.timerState = 'stopped'; + action.perform(); + expect(testModel.timerState).toEqual('started'); + }); - testModel.type = 'clock'; - testModel.timestamp = 12000; - expect(RestartTimerAction.appliesTo(testContext)).toBeFalsy(); + it("applies only to timers in a non-stopped state", function () { + //in a stopped state + testState('timer', 'stopped', undefined, false); + + //in a paused state + testState('timer', 'paused', 12000, true); + + //in a playing state + testState('timer', 'started', 12000, true); + + //not a timer + testState('clock', 'paused', 12000, false); }); }); } diff --git a/platform/features/clock/test/actions/StartTimerActionSpec.js b/platform/features/clock/test/actions/StartTimerActionSpec.js index 588f776d70..1506c79738 100644 --- a/platform/features/clock/test/actions/StartTimerActionSpec.js +++ b/platform/features/clock/test/actions/StartTimerActionSpec.js @@ -33,10 +33,22 @@ define( function asPromise(value) { return (value || {}).then ? value : { - then: function (callback) { - return asPromise(callback(value)); - } - }; + then: function (callback) { + return asPromise(callback(value)); + } + }; + } + + function testState(type, timerState, timestamp, expected) { + testModel.type = type; + testModel.timerState = timerState; + testModel.timestamp = timestamp; + + if (expected) { + expect(StartTimerAction.appliesTo(testContext)).toBeTruthy(); + } else { + expect(StartTimerAction.appliesTo(testContext)).toBeFalsy(); + } } beforeEach(function () { @@ -57,7 +69,7 @@ define( }); testModel = {}; - testContext = { domainObject: mockDomainObject }; + testContext = {domainObject: mockDomainObject}; action = new StartTimerAction(mockNow, testContext); }); @@ -68,18 +80,30 @@ define( expect(testModel.timestamp).toEqual(12000); }); - it("applies only to timers without a target time", function () { - testModel.type = 'timer'; - testModel.timestamp = 12000; - expect(StartTimerAction.appliesTo(testContext)).toBeFalsy(); + it("updates the model with a pausedTime", function () { + testModel.pausedTime = 12000; + action.perform(); + expect(testModel.pausedTime).toEqual(undefined); + }); - testModel.type = 'timer'; - testModel.timestamp = undefined; - expect(StartTimerAction.appliesTo(testContext)).toBeTruthy(); + it("updates the model with a timerState", function () { + testModel.timerState = undefined; + action.perform(); + expect(testModel.timerState).toEqual('started'); + }); - testModel.type = 'clock'; - testModel.timestamp = 12000; - expect(StartTimerAction.appliesTo(testContext)).toBeFalsy(); + it("applies only to timers not in a playing state", function () { + //in a stopped state + testState('timer', 'stopped', undefined, true); + + //in a paused state + testState('timer', 'paused', 12000, true); + + //in a playing state + testState('timer', 'started', 12000, false); + + //not a timer + testState('clock', 'paused', 12000, false); }); }); } diff --git a/platform/features/clock/test/actions/StopTimerActionSpec.js b/platform/features/clock/test/actions/StopTimerActionSpec.js new file mode 100644 index 0000000000..a4c27ad921 --- /dev/null +++ b/platform/features/clock/test/actions/StopTimerActionSpec.js @@ -0,0 +1,110 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2009-2016, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +define( + ["../../src/actions/StopTimerAction"], + function (StopTimerAction) { + + describe("A timer's stop action", function () { + var mockNow, + mockDomainObject, + testModel, + testContext, + action; + + function asPromise(value) { + return (value || {}).then ? value : { + then: function (callback) { + return asPromise(callback(value)); + } + }; + } + + function testState(type, timerState, timestamp, expected) { + testModel.type = type; + testModel.timerState = timerState; + testModel.timestamp = timestamp; + + if (expected) { + expect(StopTimerAction.appliesTo(testContext)).toBeTruthy(); + } else { + expect(StopTimerAction.appliesTo(testContext)).toBeFalsy(); + } + } + + beforeEach(function () { + mockNow = jasmine.createSpy('now'); + mockDomainObject = jasmine.createSpyObj( + 'domainObject', + ['getCapability', 'useCapability', 'getModel'] + ); + + mockDomainObject.useCapability.andCallFake(function (c, v) { + if (c === 'mutation') { + testModel = v(testModel) || testModel; + return asPromise(true); + } + }); + mockDomainObject.getModel.andCallFake(function () { + return testModel; + }); + + testModel = {}; + testContext = {domainObject: mockDomainObject}; + + action = new StopTimerAction(mockNow, testContext); + }); + + it("updates the model with a timestamp", function () { + mockNow.andReturn(12000); + action.perform(); + expect(testModel.timestamp).toEqual(undefined); + }); + + it("updates the model with a pausedTime", function () { + testModel.pausedTime = 12000; + action.perform(); + expect(testModel.pausedTime).toEqual(undefined); + }); + + it("updates the model with a timerState", function () { + testModel.timerState = 'started'; + action.perform(); + expect(testModel.timerState).toEqual('stopped'); + }); + + it("applies only to timers in a non-stopped state", function () { + //in a stopped state + testState('timer', 'stopped', undefined, false); + + //in a paused state + testState('timer', 'paused', 12000, true); + + //in a playing state + testState('timer', 'started', 12000, true); + + //not a timer + testState('clock', 'paused', 12000, false); + }); + }); + } +); diff --git a/platform/features/clock/test/controllers/TimerControllerSpec.js b/platform/features/clock/test/controllers/TimerControllerSpec.js index 3481221949..002ce5c007 100644 --- a/platform/features/clock/test/controllers/TimerControllerSpec.js +++ b/platform/features/clock/test/controllers/TimerControllerSpec.js @@ -34,13 +34,14 @@ define( mockDomainObject, mockActionCapability, mockStart, - mockRestart, + mockPause, + mockStop, testModel, controller; function invokeWatch(expr, value) { mockScope.$watch.calls.forEach(function (call) { - if (call.args[0] === expr) { + if (call.args[0] === expr) { call.args[1](value); } }); @@ -67,8 +68,12 @@ define( 'start', ['getMetadata', 'perform'] ); - mockRestart = jasmine.createSpyObj( - 'restart', + mockPause = jasmine.createSpyObj( + 'paused', + ['getMetadata', 'perform'] + ); + mockStop = jasmine.createSpyObj( + 'stopped', ['getMetadata', 'perform'] ); mockNow = jasmine.createSpy('now'); @@ -82,11 +87,14 @@ define( mockActionCapability.getActions.andCallFake(function (k) { return [{ 'timer.start': mockStart, - 'timer.restart': mockRestart + 'timer.pause': mockPause, + 'timer.stop': mockStop }[k]]; }); - mockStart.getMetadata.andReturn({ cssClass: "icon-play", name: "Start" }); - mockRestart.getMetadata.andReturn({ cssClass: "icon-refresh", name: "Restart" }); + + mockStart.getMetadata.andReturn({cssClass: "icon-play", name: "Start"}); + mockPause.getMetadata.andReturn({cssClass: "icon-pause", name: "Pause"}); + mockStop.getMetadata.andReturn({cssClass: "icon-box", name: "Stop"}); mockScope.domainObject = mockDomainObject; testModel = {}; @@ -144,28 +152,37 @@ define( expect(controller.text()).toEqual("0D 00:00:00"); }); - it("shows cssClass & name for the applicable start/restart action", function () { + it("shows cssClass & name for the applicable start/pause action", function () { invokeWatch('domainObject', mockDomainObject); expect(controller.buttonCssClass()).toEqual("icon-play"); expect(controller.buttonText()).toEqual("Start"); testModel.timestamp = 12321; + testModel.timerState = 'started'; invokeWatch('model.modified', 1); - expect(controller.buttonCssClass()).toEqual("icon-refresh"); - expect(controller.buttonText()).toEqual("Restart"); + expect(controller.buttonCssClass()).toEqual("icon-pause"); + expect(controller.buttonText()).toEqual("Pause"); }); - it("performs correct start/restart action on click", function () { + it("performs correct start/pause/stop action on click", function () { + //test start invokeWatch('domainObject', mockDomainObject); expect(mockStart.perform).not.toHaveBeenCalled(); controller.clickButton(); expect(mockStart.perform).toHaveBeenCalled(); + //test pause testModel.timestamp = 12321; + testModel.timerState = 'started'; invokeWatch('model.modified', 1); - expect(mockRestart.perform).not.toHaveBeenCalled(); + expect(mockPause.perform).not.toHaveBeenCalled(); controller.clickButton(); - expect(mockRestart.perform).toHaveBeenCalled(); + expect(mockPause.perform).toHaveBeenCalled(); + + //test stop + expect(mockStop.perform).not.toHaveBeenCalled(); + controller.clickStopButton(); + expect(mockStop.perform).toHaveBeenCalled(); }); it("stops requesting animation frames when destroyed", function () {