mirror of
https://github.com/nasa/openmct.git
synced 2024-12-18 20:57:53 +00:00
Replace Angular code that synchronizes URL parameters with Time API (#3089)
* Added new test to telemetry tables to check that telemetry data is correctly rendered in rows * Added test tools for mocking builtins * Changed order that promises are resolved to address race condition * Remove duplicate installation of UTC Time System * Added additional test telemetry * Start Open MCT headless * Added headless mode start option. Fixes #3064 * Added new non-angular URL handler * Removed legacy Angular TimeSettingsURLHandler * Added function to testTools to reset application state * Use resetApplicationState function from telemetry table spec * Added new TimeSettingsURLHandler to plugins * Added missing semicolons * #2826 Refactored code into separate class * Handling of hash-relative URLs * Refactoring URL sync code * Refactored to external class * Moved utils to new 'utils' directory. Refactored location util functions from class to exported functions * Added test specs for openmctLocation * Added new function to destroy instances of Open MCT between test runs * Ensure test specs are cleaning up after themselves * Added test spec for new URLTimeSettingsSynchronizer * Removed use of shell script as it doesn't work in windows * Pushed test coverage to 100% * Added missing copyright statement * Removed debugging output * Fixed linting error * Upgrade node version * Clear cache * Re-enabled tests Co-authored-by: Melanie Lean <melanielean@Melanies-MacBook-Pro.local> Co-authored-by: Shefali Joshi <simplyrender@gmail.com> Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
This commit is contained in:
parent
d9fafd2956
commit
e9968e3649
@ -2,7 +2,7 @@ version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/node:8-browsers
|
||||
- image: circleci/node:13-browsers
|
||||
environment:
|
||||
CHROME_BIN: "/usr/bin/google-chrome"
|
||||
steps:
|
||||
@ -11,12 +11,12 @@ jobs:
|
||||
name: Update npm
|
||||
command: 'sudo npm install -g npm@latest'
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "package.json" }}
|
||||
key: dependency-cache-13-{{ checksum "package.json" }}
|
||||
- run:
|
||||
name: Installing dependencies (npm install)
|
||||
command: npm install
|
||||
- save_cache:
|
||||
key: dependency-cache-{{ checksum "package.json" }}
|
||||
key: dependency-cache-13-{{ checksum "package.json" }}
|
||||
paths:
|
||||
- node_modules
|
||||
- run:
|
||||
|
@ -84,10 +84,10 @@
|
||||
"build:prod": "cross-env NODE_ENV=production webpack",
|
||||
"build:dev": "webpack",
|
||||
"build:watch": "webpack --watch",
|
||||
"test": "karma start --single-run",
|
||||
"test": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run",
|
||||
"test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
|
||||
"test:coverage": "./scripts/test-coverage.sh",
|
||||
"test:watch": "karma start --no-single-run",
|
||||
"test:coverage": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" COVERAGE=true karma start --single-run",
|
||||
"test:watch": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --no-single-run",
|
||||
"verify": "concurrently 'npm:test' 'npm:lint'",
|
||||
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
|
||||
"otherdoc": "node docs/gendocs.js --in docs/src --out dist/docs --suppress-toc 'docs/src/index.md|docs/src/process/index.md'",
|
||||
|
@ -1,2 +0,0 @@
|
||||
export NODE_OPTIONS=--max_old_space_size=4096
|
||||
cross-env COVERAGE=true karma start --single-run
|
@ -266,6 +266,7 @@ define([
|
||||
this.install(this.plugins.WebPage());
|
||||
this.install(this.plugins.Condition());
|
||||
this.install(this.plugins.ConditionWidget());
|
||||
this.install(this.plugins.URLTimeSettingsSynchronizer());
|
||||
this.install(this.plugins.NotificationIndicator());
|
||||
}
|
||||
|
||||
@ -433,6 +434,10 @@ define([
|
||||
plugin(this);
|
||||
};
|
||||
|
||||
MCT.prototype.destroy = function () {
|
||||
this.emit('destroy');
|
||||
};
|
||||
|
||||
MCT.prototype.plugins = plugins;
|
||||
|
||||
return MCT;
|
||||
|
@ -23,7 +23,7 @@
|
||||
define([
|
||||
'./plugins/plugins',
|
||||
'legacyRegistry',
|
||||
'testUtils'
|
||||
'utils/testing'
|
||||
], function (plugins, legacyRegistry, testUtils) {
|
||||
describe("MCT", function () {
|
||||
var openmct;
|
||||
@ -32,6 +32,10 @@ define([
|
||||
var mockListener;
|
||||
var oldBundles;
|
||||
|
||||
beforeAll(() => {
|
||||
testUtils.resetApplicationState();
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
mockPlugin = jasmine.createSpy('plugin');
|
||||
mockPlugin2 = jasmine.createSpy('plugin2');
|
||||
@ -52,6 +56,7 @@ define([
|
||||
legacyRegistry.delete(bundle);
|
||||
}
|
||||
});
|
||||
testUtils.resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it("exposes plugins", function () {
|
||||
|
@ -29,7 +29,6 @@ define([
|
||||
'./capabilities/APICapabilityDecorator',
|
||||
'./policies/AdaptedViewPolicy',
|
||||
'./runs/AlternateCompositionInitializer',
|
||||
'./runs/TimeSettingsURLHandler',
|
||||
'./runs/TypeDeprecationChecker',
|
||||
'./runs/LegacyTelemetryProvider',
|
||||
'./runs/RegisterLegacyTypes',
|
||||
@ -46,7 +45,6 @@ define([
|
||||
APICapabilityDecorator,
|
||||
AdaptedViewPolicy,
|
||||
AlternateCompositionInitializer,
|
||||
TimeSettingsURLHandler,
|
||||
TypeDeprecationChecker,
|
||||
LegacyTelemetryProvider,
|
||||
RegisterLegacyTypes,
|
||||
@ -134,16 +132,6 @@ define([
|
||||
implementation: AlternateCompositionInitializer,
|
||||
depends: ["openmct"]
|
||||
},
|
||||
{
|
||||
implementation: function (openmct, $location, $rootScope) {
|
||||
return new TimeSettingsURLHandler(
|
||||
openmct.time,
|
||||
$location,
|
||||
$rootScope
|
||||
);
|
||||
},
|
||||
depends: ["openmct", "$location", "$rootScope"]
|
||||
},
|
||||
{
|
||||
implementation: LegacyTelemetryProvider,
|
||||
depends: [
|
||||
|
@ -1,150 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, 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([
|
||||
'lodash'
|
||||
], function (
|
||||
_
|
||||
) {
|
||||
// Parameter names in query string
|
||||
var SEARCH = {
|
||||
MODE: 'tc.mode',
|
||||
TIME_SYSTEM: 'tc.timeSystem',
|
||||
START_BOUND: 'tc.startBound',
|
||||
END_BOUND: 'tc.endBound',
|
||||
START_DELTA: 'tc.startDelta',
|
||||
END_DELTA: 'tc.endDelta'
|
||||
};
|
||||
var TIME_EVENTS = ['bounds', 'timeSystem', 'clock', 'clockOffsets'];
|
||||
// Used to shorthand calls to $location, which clears null parameters
|
||||
var NULL_PARAMETERS = { key: null, start: null, end: null };
|
||||
|
||||
/**
|
||||
* Communicates settings from the URL to the time API,
|
||||
* and vice versa.
|
||||
*/
|
||||
function TimeSettingsURLHandler(time, $location, $rootScope) {
|
||||
this.time = time;
|
||||
this.$location = $location;
|
||||
|
||||
$rootScope.$on('$locationChangeSuccess', this.updateTime.bind(this));
|
||||
|
||||
TIME_EVENTS.forEach(function (event) {
|
||||
this.time.on(event, this.updateQueryParams.bind(this));
|
||||
}, this);
|
||||
|
||||
this.updateTime(); // Initialize
|
||||
}
|
||||
|
||||
TimeSettingsURLHandler.prototype.updateQueryParams = function () {
|
||||
var clock = this.time.clock();
|
||||
var fixed = !clock;
|
||||
var mode = fixed ? 'fixed' : clock.key;
|
||||
var timeSystem = this.time.timeSystem() || NULL_PARAMETERS;
|
||||
var bounds = fixed ? this.time.bounds() : NULL_PARAMETERS;
|
||||
var deltas = fixed ? NULL_PARAMETERS : this.time.clockOffsets();
|
||||
|
||||
bounds = bounds || NULL_PARAMETERS;
|
||||
deltas = deltas || NULL_PARAMETERS;
|
||||
if (deltas.start) {
|
||||
deltas = { start: -deltas.start, end: deltas.end };
|
||||
}
|
||||
|
||||
this.$location.search(SEARCH.MODE, mode);
|
||||
this.$location.search(SEARCH.TIME_SYSTEM, timeSystem.key);
|
||||
this.$location.search(SEARCH.START_BOUND, bounds.start);
|
||||
this.$location.search(SEARCH.END_BOUND, bounds.end);
|
||||
this.$location.search(SEARCH.START_DELTA, deltas.start);
|
||||
this.$location.search(SEARCH.END_DELTA, deltas.end);
|
||||
};
|
||||
|
||||
TimeSettingsURLHandler.prototype.parseQueryParams = function () {
|
||||
var searchParams = _.pick(this.$location.search(), Object.values(SEARCH));
|
||||
var parsedParams = {
|
||||
clock: searchParams[SEARCH.MODE],
|
||||
timeSystem: searchParams[SEARCH.TIME_SYSTEM]
|
||||
};
|
||||
if (!isNaN(parseInt(searchParams[SEARCH.START_DELTA], 0xA)) &&
|
||||
!isNaN(parseInt(searchParams[SEARCH.END_DELTA], 0xA))) {
|
||||
parsedParams.clockOffsets = {
|
||||
start: -searchParams[SEARCH.START_DELTA],
|
||||
end: +searchParams[SEARCH.END_DELTA]
|
||||
};
|
||||
}
|
||||
if (!isNaN(parseInt(searchParams[SEARCH.START_BOUND], 0xA)) &&
|
||||
!isNaN(parseInt(searchParams[SEARCH.END_BOUND], 0xA))) {
|
||||
parsedParams.bounds = {
|
||||
start: +searchParams[SEARCH.START_BOUND],
|
||||
end: +searchParams[SEARCH.END_BOUND]
|
||||
};
|
||||
}
|
||||
return parsedParams;
|
||||
};
|
||||
|
||||
TimeSettingsURLHandler.prototype.updateTime = function () {
|
||||
var params = this.parseQueryParams();
|
||||
if (_.isEqual(params, this.last)) {
|
||||
return; // Do nothing;
|
||||
}
|
||||
this.last = params;
|
||||
|
||||
if (!params.timeSystem) {
|
||||
this.updateQueryParams();
|
||||
} else if (params.clock === 'fixed' && params.bounds) {
|
||||
if (!this.time.timeSystem() ||
|
||||
this.time.timeSystem().key !== params.timeSystem) {
|
||||
|
||||
this.time.timeSystem(
|
||||
params.timeSystem,
|
||||
params.bounds
|
||||
);
|
||||
} else if (!_.isEqual(this.time.bounds(), params.bounds)) {
|
||||
this.time.bounds(params.bounds);
|
||||
}
|
||||
if (this.time.clock()) {
|
||||
this.time.stopClock();
|
||||
}
|
||||
} else if (params.clockOffsets) {
|
||||
if (params.clock === 'fixed') {
|
||||
this.time.stopClock();
|
||||
return;
|
||||
}
|
||||
if (!this.time.clock() ||
|
||||
this.time.clock().key !== params.clock) {
|
||||
|
||||
this.time.clock(params.clock, params.clockOffsets);
|
||||
} else if (!_.isEqual(this.time.clockOffsets(), params.clockOffsets)) {
|
||||
this.time.clockOffsets(params.clockOffsets);
|
||||
}
|
||||
if (!this.time.timeSystem() ||
|
||||
this.time.timeSystem().key !== params.timeSystem) {
|
||||
|
||||
this.time.timeSystem(params.timeSystem);
|
||||
}
|
||||
} else {
|
||||
// Neither found, update from timeSystem.
|
||||
this.updateQueryParams();
|
||||
}
|
||||
};
|
||||
|
||||
return TimeSettingsURLHandler;
|
||||
});
|
@ -1,576 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, 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([
|
||||
'./TimeSettingsURLHandler',
|
||||
'../../api/time/TimeAPI'
|
||||
], function (
|
||||
TimeSettingsURLHandler,
|
||||
TimeAPI
|
||||
) {
|
||||
describe("TimeSettingsURLHandler", function () {
|
||||
var time;
|
||||
var $location;
|
||||
var $rootScope;
|
||||
var search;
|
||||
var handler; // eslint-disable-line
|
||||
var clockA;
|
||||
var clockB;
|
||||
var timeSystemA;
|
||||
var timeSystemB;
|
||||
var boundsA;
|
||||
var boundsB;
|
||||
var offsetsA;
|
||||
var offsetsB;
|
||||
var initialize;
|
||||
var triggerLocationChange;
|
||||
|
||||
|
||||
beforeEach(function () {
|
||||
clockA = jasmine.createSpyObj('clockA', ['on', 'off']);
|
||||
clockA.key = 'clockA';
|
||||
clockA.currentValue = function () {
|
||||
return 1000;
|
||||
};
|
||||
clockB = jasmine.createSpyObj('clockB', ['on', 'off']);
|
||||
clockB.key = 'clockB';
|
||||
clockB.currentValue = function () {
|
||||
return 2000;
|
||||
};
|
||||
timeSystemA = {key: 'timeSystemA'};
|
||||
timeSystemB = {key: 'timeSystemB'};
|
||||
boundsA = {
|
||||
start: 10,
|
||||
end: 20
|
||||
};
|
||||
boundsB = {
|
||||
start: 120,
|
||||
end: 360
|
||||
};
|
||||
offsetsA = {
|
||||
start: -100,
|
||||
end: 0
|
||||
};
|
||||
offsetsB = {
|
||||
start: -50,
|
||||
end: 50
|
||||
};
|
||||
|
||||
time = new TimeAPI();
|
||||
|
||||
[
|
||||
'on',
|
||||
'bounds',
|
||||
'clockOffsets',
|
||||
'timeSystem',
|
||||
'clock',
|
||||
'stopClock'
|
||||
].forEach(function (method) {
|
||||
spyOn(time, method).and.callThrough();
|
||||
});
|
||||
time.addTimeSystem(timeSystemA);
|
||||
time.addTimeSystem(timeSystemB);
|
||||
time.addClock(clockA);
|
||||
time.addClock(clockB);
|
||||
|
||||
$location = jasmine.createSpyObj('$location', [
|
||||
'search'
|
||||
]);
|
||||
$rootScope = jasmine.createSpyObj('$rootScope', [
|
||||
'$on'
|
||||
]);
|
||||
|
||||
search = {};
|
||||
$location.search.and.callFake(function (key, value) {
|
||||
if (arguments.length === 0) {
|
||||
return search;
|
||||
}
|
||||
if (value === null) {
|
||||
delete search[key];
|
||||
} else {
|
||||
search[key] = String(value);
|
||||
}
|
||||
return this;
|
||||
});
|
||||
|
||||
expect(time.timeSystem()).toBeUndefined();
|
||||
expect(time.bounds()).toEqual({});
|
||||
expect(time.clockOffsets()).toBeUndefined();
|
||||
expect(time.clock()).toBeUndefined();
|
||||
|
||||
initialize = function () {
|
||||
handler = new TimeSettingsURLHandler(
|
||||
time,
|
||||
$location,
|
||||
$rootScope
|
||||
);
|
||||
expect($rootScope.$on).toHaveBeenCalledWith(
|
||||
'$locationChangeSuccess',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
triggerLocationChange = $rootScope.$on.calls.mostRecent().args[1];
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
it("initializes with missing time system", function () {
|
||||
// This handles an odd transitory case where a url does not include
|
||||
// a timeSystem. It's generally only experienced by those who
|
||||
// based their code on the tutorial before it specified a time
|
||||
// system.
|
||||
search['tc.mode'] = 'clockA';
|
||||
search['tc.timeSystem'] = undefined;
|
||||
search['tc.startDelta'] = '123';
|
||||
search['tc.endDelta'] = '456';
|
||||
|
||||
// We don't specify behavior right now other than "don't break."
|
||||
expect(initialize).not.toThrow();
|
||||
});
|
||||
|
||||
it("can initalize fixed mode from location", function () {
|
||||
search['tc.mode'] = 'fixed';
|
||||
search['tc.timeSystem'] = 'timeSystemA';
|
||||
search['tc.startBound'] = '123';
|
||||
search['tc.endBound'] = '456';
|
||||
|
||||
initialize();
|
||||
|
||||
expect(time.timeSystem).toHaveBeenCalledWith(
|
||||
'timeSystemA',
|
||||
{
|
||||
start: 123,
|
||||
end: 456
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("can initialize clock mode from location", function () {
|
||||
search['tc.mode'] = 'clockA';
|
||||
search['tc.timeSystem'] = 'timeSystemA';
|
||||
search['tc.startDelta'] = '123';
|
||||
search['tc.endDelta'] = '456';
|
||||
|
||||
initialize();
|
||||
|
||||
expect(time.clock).toHaveBeenCalledWith(
|
||||
'clockA',
|
||||
{
|
||||
start: -123,
|
||||
end: 456
|
||||
}
|
||||
);
|
||||
expect(time.timeSystem).toHaveBeenCalledWith(
|
||||
'timeSystemA'
|
||||
);
|
||||
});
|
||||
|
||||
it("can initialize fixed mode from time API", function () {
|
||||
time.timeSystem(timeSystemA.key, boundsA);
|
||||
initialize();
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.mode', 'fixed');
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.timeSystem', 'timeSystemA');
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.startBound', 10);
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.endBound', 20);
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.startDelta', null);
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.endDelta', null);
|
||||
});
|
||||
|
||||
it("can initialize clock mode from time API", function () {
|
||||
time.clock(clockA.key, offsetsA);
|
||||
time.timeSystem(timeSystemA.key);
|
||||
initialize();
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.mode', 'clockA');
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.timeSystem', 'timeSystemA');
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.startBound', null);
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.endBound', null);
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.startDelta', 100);
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.endDelta', 0);
|
||||
});
|
||||
|
||||
describe('location changes in fixed mode', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
time.timeSystem(timeSystemA.key, boundsA);
|
||||
initialize();
|
||||
time.timeSystem.calls.reset();
|
||||
time.bounds.calls.reset();
|
||||
time.clock.calls.reset();
|
||||
time.stopClock.calls.reset();
|
||||
});
|
||||
|
||||
it("does not change on spurious location change", function () {
|
||||
triggerLocationChange();
|
||||
expect(time.timeSystem).not.toHaveBeenCalledWith(
|
||||
'timeSystemA',
|
||||
jasmine.any(Object)
|
||||
);
|
||||
expect(time.bounds).not.toHaveBeenCalledWith(
|
||||
jasmine.any(Object)
|
||||
);
|
||||
expect(time.stopClock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates timeSystem changes", function () {
|
||||
search['tc.timeSystem'] = 'timeSystemB';
|
||||
triggerLocationChange();
|
||||
expect(time.timeSystem).toHaveBeenCalledWith(
|
||||
'timeSystemB',
|
||||
{
|
||||
start: 10,
|
||||
end: 20
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("updates bounds changes", function () {
|
||||
search['tc.startBound'] = '100';
|
||||
search['tc.endBound'] = '200';
|
||||
triggerLocationChange();
|
||||
expect(time.timeSystem).not.toHaveBeenCalledWith(
|
||||
jasmine.anything(), jasmine.anything()
|
||||
);
|
||||
expect(time.bounds).toHaveBeenCalledWith({
|
||||
start: 100,
|
||||
end: 200
|
||||
});
|
||||
search['tc.endBound'] = '300';
|
||||
triggerLocationChange();
|
||||
expect(time.timeSystem).not.toHaveBeenCalledWith(
|
||||
jasmine.anything(), jasmine.anything()
|
||||
);
|
||||
expect(time.bounds).toHaveBeenCalledWith({
|
||||
start: 100,
|
||||
end: 300
|
||||
});
|
||||
});
|
||||
|
||||
it("updates clock mode w/o timeSystem change", function () {
|
||||
search['tc.mode'] = 'clockA';
|
||||
search['tc.startDelta'] = '50';
|
||||
search['tc.endDelta'] = '50';
|
||||
delete search['tc.endBound'];
|
||||
delete search['tc.startBound'];
|
||||
triggerLocationChange();
|
||||
expect(time.clock).toHaveBeenCalledWith(
|
||||
'clockA',
|
||||
{
|
||||
start: -50,
|
||||
end: 50
|
||||
}
|
||||
);
|
||||
expect(time.timeSystem).not.toHaveBeenCalledWith(
|
||||
jasmine.anything(), jasmine.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it("updates clock mode and timeSystem", function () {
|
||||
search['tc.mode'] = 'clockA';
|
||||
search['tc.startDelta'] = '50';
|
||||
search['tc.endDelta'] = '50';
|
||||
search['tc.timeSystem'] = 'timeSystemB';
|
||||
delete search['tc.endBound'];
|
||||
delete search['tc.startBound'];
|
||||
triggerLocationChange();
|
||||
expect(time.clock).toHaveBeenCalledWith(
|
||||
'clockA',
|
||||
{
|
||||
start: -50,
|
||||
end: 50
|
||||
}
|
||||
);
|
||||
expect(time.timeSystem).toHaveBeenCalledWith('timeSystemB');
|
||||
});
|
||||
});
|
||||
|
||||
describe('location changes in clock mode', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
time.clock(clockA.key, offsetsA);
|
||||
time.timeSystem(timeSystemA.key);
|
||||
initialize();
|
||||
time.timeSystem.calls.reset();
|
||||
time.bounds.calls.reset();
|
||||
time.clock.calls.reset();
|
||||
time.clockOffsets.calls.reset();
|
||||
time.stopClock.calls.reset();
|
||||
});
|
||||
|
||||
it("does not change on spurious location change", function () {
|
||||
triggerLocationChange();
|
||||
expect(time.timeSystem).not.toHaveBeenCalledWith(
|
||||
'timeSystemA',
|
||||
jasmine.any(Object)
|
||||
);
|
||||
expect(time.clockOffsets).not.toHaveBeenCalledWith(
|
||||
jasmine.any(Object)
|
||||
);
|
||||
expect(time.clock).not.toHaveBeenCalledWith(
|
||||
jasmine.any(Object)
|
||||
);
|
||||
expect(time.bounds).not.toHaveBeenCalledWith(
|
||||
jasmine.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it("changes time system", function () {
|
||||
search['tc.timeSystem'] = 'timeSystemB';
|
||||
triggerLocationChange();
|
||||
expect(time.timeSystem).toHaveBeenCalledWith(
|
||||
'timeSystemB'
|
||||
);
|
||||
expect(time.clockOffsets).not.toHaveBeenCalledWith(
|
||||
jasmine.any(Object)
|
||||
);
|
||||
expect(time.clock).not.toHaveBeenCalledWith(
|
||||
jasmine.any(Object)
|
||||
);
|
||||
expect(time.stopClock).not.toHaveBeenCalled();
|
||||
expect(time.bounds).not.toHaveBeenCalledWith(
|
||||
jasmine.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it("changes offsets", function () {
|
||||
search['tc.startDelta'] = '50';
|
||||
search['tc.endDelta'] = '50';
|
||||
triggerLocationChange();
|
||||
expect(time.timeSystem).not.toHaveBeenCalledWith(
|
||||
'timeSystemA',
|
||||
jasmine.any(Object)
|
||||
);
|
||||
expect(time.clockOffsets).toHaveBeenCalledWith(
|
||||
{
|
||||
start: -50,
|
||||
end: 50
|
||||
}
|
||||
);
|
||||
expect(time.clock).not.toHaveBeenCalledWith(
|
||||
jasmine.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it("updates to fixed w/o timeSystem change", function () {
|
||||
search['tc.mode'] = 'fixed';
|
||||
search['tc.startBound'] = '234';
|
||||
search['tc.endBound'] = '567';
|
||||
delete search['tc.endDelta'];
|
||||
delete search['tc.startDelta'];
|
||||
|
||||
triggerLocationChange();
|
||||
expect(time.stopClock).toHaveBeenCalled();
|
||||
expect(time.bounds).toHaveBeenCalledWith({
|
||||
start: 234,
|
||||
end: 567
|
||||
});
|
||||
expect(time.timeSystem).not.toHaveBeenCalledWith(
|
||||
jasmine.anything(), jasmine.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it("updates fixed and timeSystem", function () {
|
||||
search['tc.mode'] = 'fixed';
|
||||
search['tc.startBound'] = '234';
|
||||
search['tc.endBound'] = '567';
|
||||
search['tc.timeSystem'] = 'timeSystemB';
|
||||
delete search['tc.endDelta'];
|
||||
delete search['tc.startDelta'];
|
||||
|
||||
triggerLocationChange();
|
||||
expect(time.stopClock).toHaveBeenCalled();
|
||||
expect(time.timeSystem).toHaveBeenCalledWith(
|
||||
'timeSystemB',
|
||||
{
|
||||
start: 234,
|
||||
end: 567
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("updates clock", function () {
|
||||
search['tc.mode'] = 'clockB';
|
||||
triggerLocationChange();
|
||||
expect(time.clock).toHaveBeenCalledWith(
|
||||
'clockB',
|
||||
{
|
||||
start: -100,
|
||||
end: 0
|
||||
}
|
||||
);
|
||||
expect(time.timeSystem).not.toHaveBeenCalledWith(jasmine.anything());
|
||||
});
|
||||
|
||||
it("updates clock and timeSystem", function () {
|
||||
search['tc.mode'] = 'clockB';
|
||||
search['tc.timeSystem'] = 'timeSystemB';
|
||||
triggerLocationChange();
|
||||
expect(time.clock).toHaveBeenCalledWith(
|
||||
'clockB',
|
||||
{
|
||||
start: -100,
|
||||
end: 0
|
||||
}
|
||||
);
|
||||
expect(time.timeSystem).toHaveBeenCalledWith(
|
||||
'timeSystemB'
|
||||
);
|
||||
});
|
||||
|
||||
it("updates clock and timeSystem and offsets", function () {
|
||||
search['tc.mode'] = 'clockB';
|
||||
search['tc.timeSystem'] = 'timeSystemB';
|
||||
search['tc.startDelta'] = '50';
|
||||
search['tc.endDelta'] = '50';
|
||||
triggerLocationChange();
|
||||
expect(time.clock).toHaveBeenCalledWith(
|
||||
'clockB',
|
||||
{
|
||||
start: -50,
|
||||
end: 50
|
||||
}
|
||||
);
|
||||
expect(time.timeSystem).toHaveBeenCalledWith(
|
||||
'timeSystemB'
|
||||
);
|
||||
});
|
||||
|
||||
it("stops the clock", function () {
|
||||
// this is a robustness test, unsure if desired, requires
|
||||
// user to be manually editing location strings.
|
||||
search['tc.mode'] = 'fixed';
|
||||
triggerLocationChange();
|
||||
expect(time.stopClock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("location updates from time API in fixed", function () {
|
||||
beforeEach(function () {
|
||||
time.timeSystem(timeSystemA.key, boundsA);
|
||||
initialize();
|
||||
});
|
||||
|
||||
it("updates on bounds change", function () {
|
||||
time.bounds(boundsB);
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'fixed',
|
||||
'tc.startBound': '120',
|
||||
'tc.endBound': '360',
|
||||
'tc.timeSystem': 'timeSystemA'
|
||||
});
|
||||
});
|
||||
|
||||
it("updates on timeSystem change", function () {
|
||||
time.timeSystem(timeSystemB, boundsA);
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'fixed',
|
||||
'tc.startBound': '10',
|
||||
'tc.endBound': '20',
|
||||
'tc.timeSystem': 'timeSystemB'
|
||||
});
|
||||
time.timeSystem(timeSystemA, boundsB);
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'fixed',
|
||||
'tc.startBound': '120',
|
||||
'tc.endBound': '360',
|
||||
'tc.timeSystem': 'timeSystemA'
|
||||
});
|
||||
});
|
||||
|
||||
it("Updates to clock", function () {
|
||||
time.clock(clockA, offsetsA);
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'clockA',
|
||||
'tc.startDelta': '100',
|
||||
'tc.endDelta': '0',
|
||||
'tc.timeSystem': 'timeSystemA'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("location updates from time API in fixed", function () {
|
||||
beforeEach(function () {
|
||||
time.clock(clockA.key, offsetsA);
|
||||
time.timeSystem(timeSystemA.key);
|
||||
initialize();
|
||||
});
|
||||
|
||||
it("updates offsets", function () {
|
||||
time.clockOffsets(offsetsB);
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'clockA',
|
||||
'tc.startDelta': '50',
|
||||
'tc.endDelta': '50',
|
||||
'tc.timeSystem': 'timeSystemA'
|
||||
});
|
||||
});
|
||||
|
||||
it("updates clocks", function () {
|
||||
time.clock(clockB, offsetsA);
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'clockB',
|
||||
'tc.startDelta': '100',
|
||||
'tc.endDelta': '0',
|
||||
'tc.timeSystem': 'timeSystemA'
|
||||
});
|
||||
time.clock(clockA, offsetsB);
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'clockA',
|
||||
'tc.startDelta': '50',
|
||||
'tc.endDelta': '50',
|
||||
'tc.timeSystem': 'timeSystemA'
|
||||
});
|
||||
});
|
||||
|
||||
it("updates timesystems", function () {
|
||||
time.timeSystem(timeSystemB);
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'clockA',
|
||||
'tc.startDelta': '100',
|
||||
'tc.endDelta': '0',
|
||||
'tc.timeSystem': 'timeSystemB'
|
||||
});
|
||||
});
|
||||
|
||||
it("stops the clock", function () {
|
||||
time.stopClock();
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'fixed',
|
||||
'tc.startBound': '900',
|
||||
'tc.endBound': '1000',
|
||||
'tc.timeSystem': 'timeSystemA'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,230 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, 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.
|
||||
*****************************************************************************/
|
||||
import {
|
||||
getAllSearchParams,
|
||||
setAllSearchParams
|
||||
} from 'utils/openmctLocation';
|
||||
|
||||
const TIME_EVENTS = ['bounds', 'timeSystem', 'clock', 'clockOffsets'];
|
||||
const SEARCH_MODE = 'tc.mode';
|
||||
const SEARCH_TIME_SYSTEM = 'tc.timeSystem';
|
||||
const SEARCH_START_BOUND = 'tc.startBound';
|
||||
const SEARCH_END_BOUND = 'tc.endBound';
|
||||
const SEARCH_START_DELTA = 'tc.startDelta';
|
||||
const SEARCH_END_DELTA = 'tc.endDelta';
|
||||
const MODE_FIXED = 'fixed';
|
||||
|
||||
export default class URLTimeSettingsSynchronizer {
|
||||
constructor(openmct) {
|
||||
this.openmct = openmct;
|
||||
this.isUrlUpdateInProgress = false;
|
||||
|
||||
this.initialize = this.initialize.bind(this);
|
||||
this.destroy = this.destroy.bind(this);
|
||||
this.updateTimeSettings = this.updateTimeSettings.bind(this);
|
||||
this.setUrlFromTimeApi = this.setUrlFromTimeApi.bind(this);
|
||||
|
||||
openmct.on('start', this.initialize);
|
||||
openmct.on('destroy', this.destroy);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.updateTimeSettings();
|
||||
|
||||
window.addEventListener('hashchange', this.updateTimeSettings);
|
||||
TIME_EVENTS.forEach(event => {
|
||||
this.openmct.time.on(event, this.setUrlFromTimeApi);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
destroy() {
|
||||
window.removeEventListener('hashchange', this.updateTimeSettings);
|
||||
this.openmct.off('start', this.initialize);
|
||||
this.openmct.off('destroy', this.destroy);
|
||||
|
||||
TIME_EVENTS.forEach(event => {
|
||||
this.openmct.time.off(event, this.setUrlFromTimeApi);
|
||||
});
|
||||
}
|
||||
|
||||
updateTimeSettings() {
|
||||
// Prevent from triggering self
|
||||
if (!this.isUrlUpdateInProgress) {
|
||||
let timeParameters = this.parseParametersFromUrl();
|
||||
|
||||
|
||||
if (this.areTimeParametersValid(timeParameters)) {
|
||||
this.setTimeApiFromUrl(timeParameters);
|
||||
} else {
|
||||
this.setUrlFromTimeApi();
|
||||
}
|
||||
} else {
|
||||
this.isUrlUpdateInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
parseParametersFromUrl() {
|
||||
let searchParams = getAllSearchParams();
|
||||
|
||||
let mode = searchParams.get(SEARCH_MODE);
|
||||
let timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
|
||||
|
||||
let startBound = parseInt(searchParams.get(SEARCH_START_BOUND), 10);
|
||||
let endBound = parseInt(searchParams.get(SEARCH_END_BOUND), 10);
|
||||
let bounds = {
|
||||
start: startBound,
|
||||
end: endBound
|
||||
};
|
||||
|
||||
let startOffset = parseInt(searchParams.get(SEARCH_START_DELTA), 10);
|
||||
let endOffset = parseInt(searchParams.get(SEARCH_END_DELTA), 10);
|
||||
let clockOffsets = {
|
||||
start: 0 - startOffset,
|
||||
end: endOffset
|
||||
};
|
||||
|
||||
return {
|
||||
mode,
|
||||
timeSystem,
|
||||
bounds,
|
||||
clockOffsets
|
||||
};
|
||||
}
|
||||
|
||||
setTimeApiFromUrl(timeParameters) {
|
||||
if (timeParameters.mode === 'fixed') {
|
||||
if (this.openmct.time.timeSystem().key !== timeParameters.timeSystem) {
|
||||
this.openmct.time.timeSystem(
|
||||
timeParameters.timeSystem,
|
||||
timeParameters.bounds
|
||||
);
|
||||
} else if (!this.areStartAndEndEqual(this.openmct.time.bounds(), timeParameters.bounds)) {
|
||||
this.openmct.time.bounds(timeParameters.bounds);
|
||||
}
|
||||
if (this.openmct.time.clock()) {
|
||||
this.openmct.time.stopClock();
|
||||
}
|
||||
} else {
|
||||
if (!this.openmct.time.clock() ||
|
||||
this.openmct.time.clock().key !== timeParameters.mode) {
|
||||
this.openmct.time.clock(timeParameters.mode, timeParameters.clockOffsets);
|
||||
} else if (!this.areStartAndEndEqual(this.openmct.time.clockOffsets(), timeParameters.clockOffsets)) {
|
||||
this.openmct.time.clockOffsets(timeParameters.clockOffsets);
|
||||
}
|
||||
if (!this.openmct.time.timeSystem() ||
|
||||
this.openmct.time.timeSystem().key !== timeParameters.timeSystem) {
|
||||
this.openmct.time.timeSystem(timeParameters.timeSystem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setUrlFromTimeApi() {
|
||||
let searchParams = getAllSearchParams();
|
||||
let clock = this.openmct.time.clock();
|
||||
let bounds = this.openmct.time.bounds();
|
||||
let clockOffsets = this.openmct.time.clockOffsets();
|
||||
|
||||
if (clock === undefined) {
|
||||
searchParams.set(SEARCH_MODE, MODE_FIXED);
|
||||
searchParams.set(SEARCH_START_BOUND, bounds.start);
|
||||
searchParams.set(SEARCH_END_BOUND, bounds.end);
|
||||
|
||||
searchParams.delete(SEARCH_START_DELTA);
|
||||
searchParams.delete(SEARCH_END_DELTA);
|
||||
} else {
|
||||
searchParams.set(SEARCH_MODE, clock.key);
|
||||
|
||||
if (clockOffsets !== undefined) {
|
||||
searchParams.set(SEARCH_START_DELTA, 0 - clockOffsets.start);
|
||||
searchParams.set(SEARCH_END_DELTA, clockOffsets.end);
|
||||
} else {
|
||||
searchParams.delete(SEARCH_START_DELTA);
|
||||
searchParams.delete(SEARCH_END_DELTA);
|
||||
}
|
||||
searchParams.delete(SEARCH_START_BOUND);
|
||||
searchParams.delete(SEARCH_END_BOUND);
|
||||
}
|
||||
|
||||
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key);
|
||||
this.isUrlUpdateInProgress = true;
|
||||
setAllSearchParams(searchParams);
|
||||
}
|
||||
|
||||
areTimeParametersValid(timeParameters) {
|
||||
let isValid = false;
|
||||
|
||||
if (this.isModeValid(timeParameters.mode) &&
|
||||
this.isTimeSystemValid(timeParameters.timeSystem)) {
|
||||
|
||||
if (timeParameters.mode === 'fixed') {
|
||||
isValid = this.areStartAndEndValid(timeParameters.bounds);
|
||||
} else {
|
||||
isValid = this.areStartAndEndValid(timeParameters.clockOffsets);
|
||||
}
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
areStartAndEndValid(bounds) {
|
||||
return bounds !== undefined &&
|
||||
bounds.start !== undefined &&
|
||||
bounds.start !== null &&
|
||||
bounds.end !== undefined &&
|
||||
bounds.start !== null &&
|
||||
!isNaN(bounds.start) &&
|
||||
!isNaN(bounds.end);
|
||||
}
|
||||
|
||||
isTimeSystemValid(timeSystem) {
|
||||
let isValid = timeSystem !== undefined;
|
||||
if (isValid) {
|
||||
let timeSystemObject = this.openmct.time.timeSystems.get(timeSystem);
|
||||
isValid = timeSystemObject !== undefined;
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
isModeValid(mode) {
|
||||
let isValid = false;
|
||||
|
||||
if (mode !== undefined &&
|
||||
mode !== null) {
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
if (isValid) {
|
||||
if (mode.toLowerCase() === MODE_FIXED) {
|
||||
isValid = true;
|
||||
} else {
|
||||
isValid = this.openmct.time.clocks.get(mode) !== undefined;
|
||||
}
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
areStartAndEndEqual(firstBounds, secondBounds) {
|
||||
return firstBounds.start === secondBounds.start &&
|
||||
firstBounds.end === secondBounds.end;
|
||||
}
|
||||
}
|
28
src/plugins/URLTimeSettingsSynchronizer/plugin.js
Normal file
28
src/plugins/URLTimeSettingsSynchronizer/plugin.js
Normal file
@ -0,0 +1,28 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, 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.
|
||||
*****************************************************************************/
|
||||
import URLTimeSettingsSynchronizer from "./URLTimeSettingsSynchronizer.js";
|
||||
|
||||
export default function () {
|
||||
return function install(openmct) {
|
||||
return new URLTimeSettingsSynchronizer(openmct);
|
||||
}
|
||||
}
|
307
src/plugins/URLTimeSettingsSynchronizer/pluginSpec.js
Normal file
307
src/plugins/URLTimeSettingsSynchronizer/pluginSpec.js
Normal file
@ -0,0 +1,307 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, 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.
|
||||
*****************************************************************************/
|
||||
import {
|
||||
createOpenMct,
|
||||
resetApplicationState
|
||||
} from 'utils/testing';
|
||||
|
||||
describe("The URLTimeSettingsSynchronizer", () => {
|
||||
let openmct;
|
||||
let testClock;
|
||||
beforeAll(() => resetApplicationState());
|
||||
|
||||
beforeEach((done) => {
|
||||
openmct = createOpenMct();
|
||||
openmct.install(openmct.plugins.LocalTimeSystem());
|
||||
testClock = jasmine.createSpyObj("testClock", ["start", "stop", "tick", "currentValue", "on", "off"]);
|
||||
testClock.key = "test-clock";
|
||||
testClock.currentValue.and.returnValue(0);
|
||||
|
||||
openmct.time.addClock(testClock);
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.startHeadless();
|
||||
});
|
||||
|
||||
afterEach(() => resetApplicationState(openmct));
|
||||
|
||||
describe("realtime mode", () => {
|
||||
it("when the clock is set via the time API, it is immediately reflected in the URL", () => {
|
||||
//Test expected initial conditions
|
||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
|
||||
|
||||
openmct.time.clock('local', {start: -1000, end: 100});
|
||||
|
||||
expect(window.location.hash.includes('tc.mode=local')).toBe(true);
|
||||
|
||||
//Test that expected initial conditions are no longer true
|
||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
||||
});
|
||||
it("when offsets are set via the time API, they are immediately reflected in the URL", () => {
|
||||
//Test expected initial conditions
|
||||
expect(window.location.hash.includes('tc.startDelta')).toBe(false);
|
||||
expect(window.location.hash.includes('tc.endDelta')).toBe(false);
|
||||
|
||||
openmct.time.clock('local', {start: -1000, end: 100});
|
||||
expect(window.location.hash.includes('tc.startDelta=1000')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endDelta=100')).toBe(true);
|
||||
|
||||
openmct.time.clockOffsets({start: -2000, end: 200});
|
||||
expect(window.location.hash.includes('tc.startDelta=2000')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endDelta=200')).toBe(true);
|
||||
|
||||
//Test that expected initial conditions are no longer true
|
||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
||||
});
|
||||
describe("when set in the url", () => {
|
||||
it("will change from fixed to realtime mode when the mode changes", () => {
|
||||
expectLocationToBeInFixedMode();
|
||||
return switchToRealtimeMode().then(() => {
|
||||
let clock = openmct.time.clock();
|
||||
|
||||
expect(clock).toBeDefined();
|
||||
expect(clock.key).toBe('local');
|
||||
});
|
||||
});
|
||||
it("the clock is correctly set in the API from the URL parameters", () => {
|
||||
return switchToRealtimeMode().then(() => {
|
||||
let resolveFunction;
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
|
||||
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
||||
//detected in the API.
|
||||
openmct.time.on('clock', resolveFunction);
|
||||
let hash = window.location.hash;
|
||||
hash = hash.replace('tc.mode=local', 'tc.mode=test-clock');
|
||||
window.location.hash = hash;
|
||||
}).then(() => {
|
||||
let clock = openmct.time.clock();
|
||||
expect(clock).toBeDefined();
|
||||
expect(clock.key).toBe('test-clock');
|
||||
openmct.time.off('clock', resolveFunction);
|
||||
});
|
||||
});
|
||||
});
|
||||
it("the clock offsets are correctly set in the API from the URL parameters", () => {
|
||||
return switchToRealtimeMode().then(() => {
|
||||
let resolveFunction;
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
||||
//detected in the API.
|
||||
openmct.time.on('clockOffsets', resolveFunction);
|
||||
let hash = window.location.hash;
|
||||
hash = hash.replace('tc.startDelta=1000', 'tc.startDelta=2000');
|
||||
hash = hash.replace('tc.endDelta=100', 'tc.endDelta=200');
|
||||
window.location.hash = hash;
|
||||
}).then(() => {
|
||||
let clockOffsets = openmct.time.clockOffsets();
|
||||
expect(clockOffsets).toBeDefined();
|
||||
expect(clockOffsets.start).toBe(-2000);
|
||||
expect(clockOffsets.end).toBe(200);
|
||||
openmct.time.off('clockOffsets', resolveFunction);
|
||||
});
|
||||
});
|
||||
});
|
||||
it("the time system is correctly set in the API from the URL parameters", () => {
|
||||
return switchToRealtimeMode().then(() => {
|
||||
let resolveFunction;
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
|
||||
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
||||
//detected in the API.
|
||||
openmct.time.on('timeSystem', resolveFunction);
|
||||
let hash = window.location.hash;
|
||||
hash = hash.replace('tc.timeSystem=utc', 'tc.timeSystem=local');
|
||||
window.location.hash = hash;
|
||||
}).then(() => {
|
||||
let timeSystem = openmct.time.timeSystem();
|
||||
expect(timeSystem).toBeDefined();
|
||||
expect(timeSystem.key).toBe('local');
|
||||
openmct.time.off('timeSystem', resolveFunction);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("fixed timespan mode", () => {
|
||||
beforeEach(() => {
|
||||
openmct.time.stopClock();
|
||||
openmct.time.timeSystem('utc', {start: 0, end: 1});
|
||||
});
|
||||
|
||||
it("when bounds are set via the time API, they are immediately reflected in the URL", ()=>{
|
||||
//Test expected initial conditions
|
||||
expect(window.location.hash.includes('tc.startBound=0')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endBound=1')).toBe(true);
|
||||
|
||||
openmct.time.bounds({start: 10, end: 20});
|
||||
|
||||
expect(window.location.hash.includes('tc.startBound=10')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endBound=20')).toBe(true);
|
||||
|
||||
//Test that expected initial conditions are no longer true
|
||||
expect(window.location.hash.includes('tc.startBound=0')).toBe(false);
|
||||
expect(window.location.hash.includes('tc.endBound=1')).toBe(false);
|
||||
});
|
||||
|
||||
it("when time system is set via the time API, it is immediately reflected in the URL", ()=>{
|
||||
//Test expected initial conditions
|
||||
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(true);
|
||||
|
||||
openmct.time.timeSystem('local', {start: 20, end: 30});
|
||||
|
||||
expect(window.location.hash.includes('tc.timeSystem=local')).toBe(true);
|
||||
|
||||
//Test that expected initial conditions are no longer true
|
||||
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(false);
|
||||
});
|
||||
describe("when set in the url", () => {
|
||||
it("time system changes are reflected in the API", () => {
|
||||
let resolveFunction;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let timeSystem = openmct.time.timeSystem();
|
||||
resolveFunction = resolve;
|
||||
|
||||
expect(timeSystem.key).toBe('utc');
|
||||
window.location.hash = window.location.hash.replace('tc.timeSystem=utc', 'tc.timeSystem=local');
|
||||
|
||||
openmct.time.on('timeSystem', resolveFunction);
|
||||
}).then(() => {
|
||||
let timeSystem = openmct.time.timeSystem();
|
||||
expect(timeSystem.key).toBe('local');
|
||||
|
||||
openmct.time.off('timeSystem', resolveFunction);
|
||||
});
|
||||
});
|
||||
it("mode can be changed from realtime to fixed", () => {
|
||||
return switchToRealtimeMode().then(() => {
|
||||
expectLocationToBeInRealtimeMode();
|
||||
|
||||
expect(openmct.time.clock()).toBeDefined();
|
||||
}).then(switchToFixedMode).then(() => {
|
||||
let clock = openmct.time.clock();
|
||||
expect(clock).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
it("bounds are correctly set in the API from the URL parameters", () => {
|
||||
let resolveFunction;
|
||||
|
||||
expectLocationToBeInFixedMode();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
openmct.time.on('bounds', resolveFunction);
|
||||
let hash = window.location.hash;
|
||||
hash = hash.replace('tc.startBound=0', 'tc.startBound=222')
|
||||
.replace('tc.endBound=1', 'tc.endBound=333');
|
||||
window.location.hash = hash;
|
||||
}).then(() => {
|
||||
let bounds = openmct.time.bounds();
|
||||
|
||||
expect(bounds).toBeDefined();
|
||||
expect(bounds.start).toBe(222);
|
||||
expect(bounds.end).toBe(333);
|
||||
});
|
||||
});
|
||||
it("bounds are correctly set in the API from the URL parameters where only the end bound changes", () => {
|
||||
let resolveFunction;
|
||||
|
||||
expectLocationToBeInFixedMode();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
openmct.time.on('bounds', resolveFunction);
|
||||
let hash = window.location.hash;
|
||||
hash = hash.replace('tc.endBound=1', 'tc.endBound=333');
|
||||
window.location.hash = hash;
|
||||
}).then(() => {
|
||||
let bounds = openmct.time.bounds();
|
||||
|
||||
expect(bounds).toBeDefined();
|
||||
expect(bounds.start).toBe(0);
|
||||
expect(bounds.end).toBe(333);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function setRealtimeLocationParameters() {
|
||||
let hash = window.location.hash.toString()
|
||||
.replace('tc.mode=fixed', 'tc.mode=local')
|
||||
.replace('tc.startBound=0', 'tc.startDelta=1000')
|
||||
.replace('tc.endBound=1', 'tc.endDelta=100');
|
||||
|
||||
window.location.hash = hash;
|
||||
}
|
||||
|
||||
function setFixedLocationParameters() {
|
||||
let hash = window.location.hash.toString()
|
||||
.replace('tc.mode=local', 'tc.mode=fixed')
|
||||
.replace('tc.timeSystem=utc', 'tc.timeSystem=local')
|
||||
.replace('tc.startDelta=1000', 'tc.startBound=50')
|
||||
.replace('tc.endDelta=100', 'tc.endBound=60');
|
||||
|
||||
window.location.hash = hash;
|
||||
}
|
||||
|
||||
function switchToRealtimeMode() {
|
||||
let resolveFunction;
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
openmct.time.on('clock', resolveFunction);
|
||||
setRealtimeLocationParameters();
|
||||
}).then(() => {
|
||||
openmct.time.off('clock', resolveFunction);
|
||||
});
|
||||
}
|
||||
|
||||
function switchToFixedMode() {
|
||||
let resolveFunction;
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
||||
//detected in the API.
|
||||
openmct.time.on('clock', resolveFunction);
|
||||
setFixedLocationParameters();
|
||||
}).then(() => {
|
||||
openmct.time.off('clock', resolveFunction);
|
||||
});
|
||||
}
|
||||
|
||||
function expectLocationToBeInRealtimeMode() {
|
||||
expect(window.location.hash.includes('tc.mode=local')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.startDelta=1000')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endDelta=100')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
||||
}
|
||||
|
||||
function expectLocationToBeInFixedMode() {
|
||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.startBound=0')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endBound=1')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.mode=local')).toBe(false);
|
||||
}
|
||||
});
|
@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import { createOpenMct } from "testUtils";
|
||||
import { createOpenMct, resetApplicationState } from "utils/testing";
|
||||
import ConditionPlugin from "./plugin";
|
||||
import StylesView from "./components/inspector/StylesView.vue";
|
||||
import Vue from 'vue';
|
||||
@ -33,6 +33,10 @@ describe('the plugin', function () {
|
||||
let child;
|
||||
let openmct;
|
||||
|
||||
beforeAll(() => {
|
||||
resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
beforeEach((done) => {
|
||||
openmct = createOpenMct();
|
||||
openmct.install(new ConditionPlugin());
|
||||
@ -57,6 +61,10 @@ describe('the plugin', function () {
|
||||
openmct.startHeadless();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
let mockConditionSetObject = {
|
||||
name: 'Condition Set',
|
||||
key: 'conditionSet',
|
||||
|
@ -23,8 +23,9 @@
|
||||
import NotificationIndicatorPlugin from './plugin.js';
|
||||
import Vue from 'vue';
|
||||
import {
|
||||
createOpenMct
|
||||
} from 'testUtils';
|
||||
createOpenMct,
|
||||
resetApplicationState
|
||||
} from 'utils/testing';
|
||||
|
||||
describe('the plugin', () => {
|
||||
let notificationIndicatorPlugin,
|
||||
@ -34,6 +35,10 @@ describe('the plugin', () => {
|
||||
parentElement,
|
||||
mockMessages = ['error', 'test', 'notifications'];
|
||||
|
||||
beforeAll(() => {
|
||||
resetApplicationState();
|
||||
});
|
||||
|
||||
beforeEach((done) => {
|
||||
openmct = createOpenMct();
|
||||
|
||||
@ -55,6 +60,10 @@ describe('the plugin', () => {
|
||||
openmct.startHeadless();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
describe('the indicator plugin element', () => {
|
||||
beforeEach(() => {
|
||||
parentElement.append(indicatorElement);
|
||||
|
@ -52,6 +52,7 @@ define([
|
||||
'./themes/espresso',
|
||||
'./themes/maelstrom',
|
||||
'./themes/snow',
|
||||
'./URLTimeSettingsSynchronizer/plugin',
|
||||
'./notificationIndicator/plugin'
|
||||
], function (
|
||||
_,
|
||||
@ -85,6 +86,7 @@ define([
|
||||
Espresso,
|
||||
Maelstrom,
|
||||
Snow,
|
||||
URLTimeSettingsSynchronizer,
|
||||
NotificationIndicator
|
||||
) {
|
||||
var bundleMap = {
|
||||
@ -194,6 +196,7 @@ define([
|
||||
plugins.Snow = Snow.default;
|
||||
plugins.Condition = ConditionPlugin.default;
|
||||
plugins.ConditionWidget = ConditionWidgetPlugin.default;
|
||||
plugins.URLTimeSettingsSynchronizer = URLTimeSettingsSynchronizer.default;
|
||||
plugins.NotificationIndicator = NotificationIndicator.default;
|
||||
|
||||
return plugins;
|
||||
|
@ -463,6 +463,7 @@ export default {
|
||||
start = end - VISIBLE_ROW_COUNT + 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.rowOffset = start;
|
||||
this.visibleRows = filteredRows.slice(start, end);
|
||||
|
||||
|
@ -23,8 +23,10 @@ import TablePlugin from './plugin.js';
|
||||
import Vue from 'vue';
|
||||
import {
|
||||
createOpenMct,
|
||||
createMouseEvent
|
||||
} from 'testUtils';
|
||||
createMouseEvent,
|
||||
spyOnBuiltins,
|
||||
resetApplicationState
|
||||
} from 'utils/testing';
|
||||
|
||||
describe("the plugin", () => {
|
||||
let openmct;
|
||||
@ -32,6 +34,10 @@ describe("the plugin", () => {
|
||||
let element;
|
||||
let child;
|
||||
|
||||
beforeAll(() => {
|
||||
resetApplicationState();
|
||||
})
|
||||
|
||||
beforeEach((done) => {
|
||||
openmct = createOpenMct();
|
||||
|
||||
@ -40,16 +46,27 @@ describe("the plugin", () => {
|
||||
tablePlugin = new TablePlugin();
|
||||
openmct.install(tablePlugin);
|
||||
|
||||
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
||||
|
||||
element = document.createElement('div');
|
||||
child = document.createElement('div');
|
||||
element.appendChild(child);
|
||||
|
||||
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
||||
openmct.time.timeSystem('utc', {start: 0, end: 4});
|
||||
|
||||
spyOnBuiltins(['requestAnimationFrame']);
|
||||
window.requestAnimationFrame.and.callFake((callBack) => {
|
||||
callBack();
|
||||
});
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.startHeadless();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
describe("defines a table object", function () {
|
||||
it("that is creatable", () => {
|
||||
let tableType = openmct.types.get('table');
|
||||
@ -86,32 +103,73 @@ describe("the plugin", () => {
|
||||
name: "Test Object",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "utc",
|
||||
format: "utc",
|
||||
name: "Time",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
},{
|
||||
key: "some-key",
|
||||
name: "Some attribute",
|
||||
hints: {
|
||||
domain: 1
|
||||
range: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-other-key",
|
||||
name: "Another attribute",
|
||||
hints: {
|
||||
range: 1
|
||||
range: 2
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
const testTelemetry = [
|
||||
{
|
||||
'utc': 1,
|
||||
'some-key': 'some-value 1',
|
||||
'some-other-key' : 'some-other-value 1'
|
||||
},
|
||||
{
|
||||
'utc': 2,
|
||||
'some-key': 'some-value 2',
|
||||
'some-other-key' : 'some-other-value 2'
|
||||
},
|
||||
{
|
||||
'utc': 3,
|
||||
'some-key': 'some-value 3',
|
||||
'some-other-key' : 'some-other-value 3'
|
||||
}
|
||||
];
|
||||
let telemetryPromiseResolve;
|
||||
let telemetryPromise = new Promise((resolve) => {
|
||||
telemetryPromiseResolve = resolve;
|
||||
});
|
||||
openmct.telemetry.request.and.callFake(() => {
|
||||
telemetryPromiseResolve(testTelemetry);
|
||||
return telemetryPromise;
|
||||
});
|
||||
|
||||
applicableViews = openmct.objectViews.get(testTelemetryObject);
|
||||
tableViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'table');
|
||||
tableView = tableViewProvider.view(testTelemetryObject, true, [testTelemetryObject]);
|
||||
tableView = tableViewProvider.view(testTelemetryObject, [testTelemetryObject]);
|
||||
tableView.show(child, true);
|
||||
return Vue.nextTick();
|
||||
|
||||
return telemetryPromise.then(() => Vue.nextTick());
|
||||
});
|
||||
|
||||
it("Renders a row for every telemetry datum returned",() => {
|
||||
let rows = element.querySelectorAll('table.c-telemetry-table__body tr');
|
||||
expect(rows.length).toBe(3);
|
||||
});
|
||||
|
||||
|
||||
it("Renders a column for every item in telemetry metadata",() => {
|
||||
let headers = element.querySelectorAll('span.c-telemetry-table__headers__label');
|
||||
expect(headers.length).toBe(2);
|
||||
expect(headers[0].innerText).toBe('Some attribute');
|
||||
expect(headers[1].innerText).toBe('Another attribute');
|
||||
expect(headers.length).toBe(3);
|
||||
expect(headers[0].innerText).toBe('Time');
|
||||
expect(headers[1].innerText).toBe('Some attribute');
|
||||
expect(headers[2].innerText).toBe('Another attribute');
|
||||
});
|
||||
|
||||
it("Supports column reordering via drag and drop",() => {
|
||||
|
@ -1,18 +0,0 @@
|
||||
import MCT from 'MCT';
|
||||
|
||||
export function createOpenMct() {
|
||||
const openmct = new MCT();
|
||||
openmct.install(openmct.plugins.LocalStorage());
|
||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||
openmct.time.timeSystem('utc', {start: 0, end: 1});
|
||||
|
||||
return openmct;
|
||||
}
|
||||
|
||||
export function createMouseEvent(eventName) {
|
||||
return new MouseEvent(eventName, {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
view: window
|
||||
});
|
||||
}
|
106
src/utils/openmctLocation.js
Normal file
106
src/utils/openmctLocation.js
Normal file
@ -0,0 +1,106 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import objectUtils from '../api/objects/object-utils.js';
|
||||
|
||||
/**
|
||||
* Utility functions for getting and setting Open MCT search parameters and navigated object path.
|
||||
* Open MCT encodes application state into the "hash" of the url, making it awkward to use standard browser API such
|
||||
* as URL for modifying state in the URL. This wraps native API with some utility functions that operate only on the
|
||||
* hash section of the URL.
|
||||
*/
|
||||
|
||||
export function setSearchParam(paramName, paramValue) {
|
||||
let url = getHashRelativeURL();
|
||||
|
||||
url.searchParams.set(paramName, paramValue);
|
||||
setLocationFromUrl(url);
|
||||
}
|
||||
|
||||
export function deleteSearchParam(paramName) {
|
||||
let url = getHashRelativeURL();
|
||||
|
||||
url.searchParams.delete(paramName);
|
||||
setLocationFromUrl(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will replace all current search parameters with the ones defined in urlSearchParams
|
||||
* @param {URLSearchParams} paramMap
|
||||
*/
|
||||
export function setAllSearchParams(newSearchParams) {
|
||||
let url = getHashRelativeURL();
|
||||
|
||||
Array.from(url.searchParams.keys()).forEach((key) => url.searchParams.delete(key));
|
||||
|
||||
Array.from(newSearchParams.keys()).forEach(key => {
|
||||
url.searchParams.set(key, newSearchParams.get(key));
|
||||
});
|
||||
|
||||
setLocationFromUrl(url);
|
||||
}
|
||||
|
||||
export function getSearchParam(paramName) {
|
||||
return getAllSearchParams().get(paramName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {URLSearchParams} A {@link https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/entries|URLSearchParams}
|
||||
* object for accessing all current search parameters
|
||||
*/
|
||||
export function getAllSearchParams() {
|
||||
return getHashRelativeURL().searchParams;
|
||||
}
|
||||
|
||||
export function getObjectPath() {
|
||||
return getHashRelativeURL().pathname;
|
||||
}
|
||||
|
||||
export function setObjectPath(objectPath) {
|
||||
let objectPathString;
|
||||
let url = getHashRelativeURL();
|
||||
|
||||
if (objectPath instanceof Array) {
|
||||
if (objectPath.length > 0 && isDomainObject(objectPath[0])) {
|
||||
throw 'setObjectPath must be called with either a string, or an array of Domain Objects';
|
||||
}
|
||||
objectPathString = objectPath.reduce((pathString, object) => {
|
||||
return `${pathString}/${objectUtils.makeKeyString(object.identifier)}`;
|
||||
}, '');
|
||||
} else {
|
||||
objectPathString = objectPath
|
||||
}
|
||||
url.pathname = objectPathString;
|
||||
setLocationFromUrl(url);
|
||||
}
|
||||
|
||||
function isDomainObject(potentialObject) {
|
||||
return potentialObject.identifier === undefined;
|
||||
}
|
||||
|
||||
function setLocationFromUrl(url) {
|
||||
window.location.hash = `${url.pathname}${url.search}`;
|
||||
}
|
||||
|
||||
function getHashRelativeURL() {
|
||||
return new URL(window.location.hash.substring(1), window.location.origin);
|
||||
}
|
114
src/utils/openmctLocationSpec.js
Normal file
114
src/utils/openmctLocationSpec.js
Normal file
@ -0,0 +1,114 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, 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.
|
||||
*****************************************************************************/
|
||||
import {
|
||||
setSearchParam,
|
||||
deleteSearchParam,
|
||||
getAllSearchParams,
|
||||
getSearchParam,
|
||||
setAllSearchParams,
|
||||
getObjectPath,
|
||||
setObjectPath
|
||||
} from './openmctLocation';
|
||||
|
||||
import {resetApplicationState} from 'utils/testing';
|
||||
|
||||
describe('the openmct location utility functions', () => {
|
||||
beforeAll(() => resetApplicationState());
|
||||
afterEach(() => resetApplicationState());
|
||||
|
||||
it('The setSearchParam function sets an individual search parameters in the window location hash', () => {
|
||||
setSearchParam('testParam', 'testValue');
|
||||
expect(window.location.hash.includes('testParam=testValue')).toBe(true);
|
||||
});
|
||||
|
||||
it('The deleteSearchParam function deletes an individual search paramater in the window location hash', () => {
|
||||
window.location.hash = '#/?testParam=testValue';
|
||||
deleteSearchParam('testParam');
|
||||
expect(window.location.hash.includes('testParam=testValue')).toBe(false);
|
||||
});
|
||||
|
||||
it('The getSearchParam function returns the value of an individual search paramater in the window location hash', () => {
|
||||
window.location.hash = '#/?testParam=testValue';
|
||||
expect(getSearchParam('testParam')).toBe('testValue');
|
||||
});
|
||||
|
||||
it('The getAllSearchParams function returns the values of all search paramaters in the window location hash', () => {
|
||||
window.location.hash = '#/?testParam1=testValue1&testParam2=testValue2&testParam3=testValue3';
|
||||
let searchParams = getAllSearchParams();
|
||||
expect(searchParams.get('testParam1')).toBe('testValue1');
|
||||
expect(searchParams.get('testParam2')).toBe('testValue2');
|
||||
expect(searchParams.get('testParam3')).toBe('testValue3');
|
||||
});
|
||||
|
||||
it('The setAllSearchParams function replaces all search paramaters in the window location hash', () => {
|
||||
window.location.hash = '#/?testParam1=testValue1&testParam2=testValue2&testParam3=testValue3';
|
||||
let searchParams = getAllSearchParams();
|
||||
searchParams.delete('testParam3');
|
||||
searchParams.set('testParam1', 'updatedTestValue1');
|
||||
searchParams.set('newTestParam4', 'newTestValue4');
|
||||
setAllSearchParams(searchParams);
|
||||
expect(window.location.hash).toBe('#/?testParam1=updatedTestValue1&testParam2=testValue2&newTestParam4=newTestValue4');
|
||||
});
|
||||
|
||||
it('The getObjectPath function returns the current object path', () => {
|
||||
window.location.hash = '#/some/object/path?someParameter=someValue';
|
||||
expect(getObjectPath()).toBe('/some/object/path');
|
||||
});
|
||||
|
||||
it('The setObjectPath function allows the object path to be set to a given string', () => {
|
||||
window.location.hash = '#/some/object/path?someParameter=someValue';
|
||||
setObjectPath('/some/other/object/path');
|
||||
expect(window.location.hash).toBe('#/some/other/object/path?someParameter=someValue');
|
||||
});
|
||||
|
||||
it('The setObjectPath function allows the object path to be set from an array of domain objects', () => {
|
||||
const OBJECT_PATH = [
|
||||
{
|
||||
identifier: {
|
||||
namespace: 'namespace',
|
||||
key: 'objectKey1'
|
||||
}
|
||||
},
|
||||
{
|
||||
identifier: {
|
||||
namespace: 'namespace',
|
||||
key: 'objectKey2'
|
||||
}
|
||||
},
|
||||
{
|
||||
identifier: {
|
||||
namespace: 'namespace',
|
||||
key: 'objectKey3'
|
||||
}
|
||||
}
|
||||
]
|
||||
window.location.hash = '#/some/object/path?someParameter=someValue';
|
||||
setObjectPath(OBJECT_PATH);
|
||||
expect(window.location.hash).toBe('#/namespace:objectKey1/namespace:objectKey2/namespace:objectKey3?someParameter=someValue');
|
||||
});
|
||||
|
||||
it('The setObjectPath function throws an error if called with anything other than a string or an array of domain objects', () => {
|
||||
expect(() => setObjectPath(["array", "of", "strings"])).toThrow();
|
||||
expect(() => setObjectPath([{}, {someKey: 'someValue'}])).toThrow();
|
||||
});
|
||||
});
|
||||
|
70
src/utils/testing.js
Normal file
70
src/utils/testing.js
Normal file
@ -0,0 +1,70 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import MCT from 'MCT';
|
||||
let nativeFunctions = [];
|
||||
|
||||
export function createOpenMct() {
|
||||
const openmct = new MCT();
|
||||
openmct.install(openmct.plugins.LocalStorage());
|
||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||
openmct.time.timeSystem('utc', {start: 0, end: 1});
|
||||
|
||||
return openmct;
|
||||
}
|
||||
|
||||
export function createMouseEvent(eventName) {
|
||||
return new MouseEvent(eventName, {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
view: window
|
||||
});
|
||||
}
|
||||
|
||||
export function spyOnBuiltins(functionNames, object = window) {
|
||||
functionNames.forEach(functionName => {
|
||||
if (nativeFunctions[functionName]) {
|
||||
throw `Builtin spy function already defined for ${functionName}`;
|
||||
}
|
||||
|
||||
nativeFunctions.push({functionName, object, nativeFunction: object[functionName]});
|
||||
spyOn(object, functionName);
|
||||
});
|
||||
}
|
||||
|
||||
export function clearBuiltinSpies() {
|
||||
nativeFunctions.forEach(clearBuiltinSpy);
|
||||
nativeFunctions = [];
|
||||
}
|
||||
|
||||
export function resetApplicationState(openmct) {
|
||||
clearBuiltinSpies();
|
||||
window.location.hash = '#';
|
||||
|
||||
if (openmct !== undefined) {
|
||||
openmct.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
function clearBuiltinSpy(funcDefinition) {
|
||||
funcDefinition.object[funcDefinition.functionName] = funcDefinition.nativeFunction;
|
||||
}
|
@ -42,8 +42,9 @@ const webpackConfig = {
|
||||
"printj": path.join(__dirname, "node_modules/printj/dist/printj.min.js"),
|
||||
"styles": path.join(__dirname, "src/styles"),
|
||||
"MCT": path.join(__dirname, "src/MCT"),
|
||||
"testUtils": path.join(__dirname, "src/testUtils.js"),
|
||||
"objectUtils": path.join(__dirname, "src/api/objects/object-utils.js")
|
||||
"testUtils": path.join(__dirname, "src/utils/testUtils.js"),
|
||||
"objectUtils": path.join(__dirname, "src/api/objects/object-utils.js"),
|
||||
"utils": path.join(__dirname, "src/utils")
|
||||
}
|
||||
},
|
||||
devtool: devMode ? 'eval-source-map' : 'source-map',
|
||||
|
Loading…
Reference in New Issue
Block a user