mirror of
https://github.com/nasa/openmct.git
synced 2025-05-21 01:37:38 +00:00
* docs: fix type imports in openmct.js * docs: fix type imports * docs: fix types for eventHelpers * docs: types for TypeRegistry * docs: types for StatusAPI * docs: fix ObjectAPI types and docs * docs: more types * docs: improved types for main entry * docs: improved types * fix: unbreak the linting * chore: remove EventEmitter webpack alias as it hide types * fix: return type * fix: parameter type * fix: types for composables * chore: add webpack consts to eslintrc * fix: remove usage of deprecated timeAPI methods and add a ton of docs and types * docs: update README.md * lint: clean up API.md * chore: upgrade eventemitter to v5.0.2 * refactor: update imports for EventEmitter to remove alias * format: lint * docs: update types for Views and ViewProviders * docs: expose common types at the base import level * docs(types): remove unnecessary tsconfig options * docs: ActionAPI * docs: AnnotationAPI * docs: import common types from the same origin * docs: FormsAPI & TelemetryAPI types * docs: FormController, IndicatorAPI * docs: MenuAPI, ActionsAPI * docs: `@memberof` is not supported by `tsc` and JSDoc generation so remove it * docs: RootRegistry and RootObjectProvider * docs: Transaction + Overlay * lint: words for the word god * fix: review comments
953 lines
28 KiB
JavaScript
953 lines
28 KiB
JavaScript
/*****************************************************************************
|
|
* Open MCT, Copyright (c) 2014-2024, 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 { EventEmitter } from 'eventemitter3';
|
|
import mount from 'utils/mount';
|
|
import {
|
|
createMouseEvent,
|
|
createOpenMct,
|
|
renderWhenVisible,
|
|
resetApplicationState,
|
|
spyOnBuiltins
|
|
} from 'utils/testing';
|
|
import { nextTick } from 'vue';
|
|
|
|
import configStore from './configuration/ConfigStore.js';
|
|
import PlotConfigurationModel from './configuration/PlotConfigurationModel.js';
|
|
import PlotOptions from './inspector/PlotOptions.vue';
|
|
import PlotVuePlugin from './plugin.js';
|
|
|
|
const TEST_KEY_ID = 'some-other-key';
|
|
|
|
describe('the plugin', function () {
|
|
let element;
|
|
let child;
|
|
let openmct;
|
|
let telemetryPromise;
|
|
let telemetryPromiseResolve;
|
|
let mockObjectPath;
|
|
let telemetrylimitProvider;
|
|
|
|
beforeEach((done) => {
|
|
mockObjectPath = [
|
|
{
|
|
name: 'mock folder',
|
|
type: 'fake-folder',
|
|
identifier: {
|
|
key: 'mock-folder',
|
|
namespace: ''
|
|
}
|
|
},
|
|
{
|
|
name: 'mock parent folder',
|
|
type: 'time-strip',
|
|
identifier: {
|
|
key: 'mock-parent-folder',
|
|
namespace: ''
|
|
}
|
|
}
|
|
];
|
|
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'
|
|
}
|
|
];
|
|
|
|
const timeSystem = {
|
|
timeSystemKey: 'utc',
|
|
bounds: {
|
|
start: 0,
|
|
end: 4
|
|
}
|
|
};
|
|
|
|
openmct = createOpenMct(timeSystem);
|
|
|
|
telemetryPromise = new Promise((resolve) => {
|
|
telemetryPromiseResolve = resolve;
|
|
});
|
|
|
|
spyOn(openmct.telemetry, 'request').and.callFake(() => {
|
|
telemetryPromiseResolve(testTelemetry);
|
|
|
|
return telemetryPromise;
|
|
});
|
|
|
|
telemetrylimitProvider = jasmine.createSpyObj('telemetrylimitProvider', [
|
|
'supportsLimits',
|
|
'getLimits',
|
|
'getLimitEvaluator'
|
|
]);
|
|
telemetrylimitProvider.supportsLimits.and.returnValue(true);
|
|
telemetrylimitProvider.getLimits.and.returnValue({
|
|
limits: function () {
|
|
return Promise.resolve({
|
|
WARNING: {
|
|
low: {
|
|
cssClass: 'is-limit--lwr is-limit--yellow',
|
|
'some-key': -0.5
|
|
},
|
|
high: {
|
|
cssClass: 'is-limit--upr is-limit--yellow',
|
|
'some-key': 0.5
|
|
}
|
|
},
|
|
DISTRESS: {
|
|
low: {
|
|
cssClass: 'is-limit--lwr is-limit--red',
|
|
'some-key': -0.9
|
|
},
|
|
high: {
|
|
cssClass: 'is-limit--upr is-limit--red',
|
|
'some-key': 0.9
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
telemetrylimitProvider.getLimitEvaluator.and.returnValue({
|
|
evaluate: function () {
|
|
return {};
|
|
}
|
|
});
|
|
openmct.telemetry.addProvider(telemetrylimitProvider);
|
|
|
|
openmct.install(new PlotVuePlugin());
|
|
|
|
element = document.createElement('div');
|
|
element.style.width = '640px';
|
|
element.style.height = '480px';
|
|
child = document.createElement('div');
|
|
child.style.width = '640px';
|
|
child.style.height = '480px';
|
|
element.appendChild(child);
|
|
document.body.appendChild(element);
|
|
|
|
openmct.types.addType('test-object', {
|
|
creatable: true
|
|
});
|
|
|
|
spyOnBuiltins(['requestAnimationFrame']);
|
|
window.requestAnimationFrame.and.callFake((callBack) => {
|
|
callBack();
|
|
});
|
|
|
|
openmct.on('start', done);
|
|
openmct.startHeadless();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
openmct.time.setTimeSystem('utc', {
|
|
start: 0,
|
|
end: 2
|
|
});
|
|
|
|
await nextTick();
|
|
configStore.deleteAll();
|
|
return resetApplicationState(openmct);
|
|
});
|
|
|
|
describe('the plot views', () => {
|
|
it('provides a plot view for objects with telemetry', () => {
|
|
const testTelemetryObject = {
|
|
id: 'test-object',
|
|
type: 'test-object',
|
|
telemetry: {
|
|
values: [
|
|
{
|
|
key: 'some-key',
|
|
hints: {
|
|
domain: 1
|
|
}
|
|
},
|
|
{
|
|
key: 'other-key',
|
|
hints: {
|
|
range: 1
|
|
}
|
|
},
|
|
{
|
|
key: 'yet-another-key',
|
|
format: 'string',
|
|
hints: {
|
|
range: 2
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
|
|
const plotView = applicableViews.find((viewProvider) => viewProvider.key === 'plot-single');
|
|
|
|
expect(plotView).toBeDefined();
|
|
});
|
|
|
|
it('does not provide a plot view if the telemetry is entirely non numeric', () => {
|
|
const testTelemetryObject = {
|
|
id: 'test-object',
|
|
type: 'test-object',
|
|
telemetry: {
|
|
values: [
|
|
{
|
|
key: 'some-key',
|
|
hints: {
|
|
domain: 1
|
|
}
|
|
},
|
|
{
|
|
key: 'other-key',
|
|
format: 'string',
|
|
hints: {
|
|
range: 1
|
|
}
|
|
},
|
|
{
|
|
key: 'yet-another-key',
|
|
format: 'string',
|
|
hints: {
|
|
range: 1
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
|
|
const plotView = applicableViews.find((viewProvider) => viewProvider.key === 'plot-single');
|
|
|
|
expect(plotView).toBeUndefined();
|
|
});
|
|
|
|
it('provides an overlay plot view for objects with telemetry', () => {
|
|
const testTelemetryObject = {
|
|
id: 'test-object',
|
|
type: 'telemetry.plot.overlay',
|
|
telemetry: {
|
|
values: [
|
|
{
|
|
key: 'some-key'
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
|
|
let plotView = applicableViews.find((viewProvider) => viewProvider.key === 'plot-overlay');
|
|
expect(plotView).toBeDefined();
|
|
});
|
|
|
|
it('provides an inspector view for overlay plots', () => {
|
|
let selection = [
|
|
[
|
|
{
|
|
context: {
|
|
item: {
|
|
id: 'test-object',
|
|
type: 'telemetry.plot.overlay',
|
|
telemetry: {
|
|
values: [
|
|
{
|
|
key: 'some-key'
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
context: {
|
|
item: {
|
|
type: 'time-strip'
|
|
}
|
|
}
|
|
}
|
|
]
|
|
];
|
|
const applicableInspectorViews = openmct.inspectorViews.get(selection);
|
|
const plotInspectorView = applicableInspectorViews.find(
|
|
(view) => view.key === 'plots-inspector'
|
|
);
|
|
|
|
expect(plotInspectorView).toBeDefined();
|
|
});
|
|
|
|
it('provides a stacked plot view for objects with telemetry', () => {
|
|
const testTelemetryObject = {
|
|
id: 'test-object',
|
|
type: 'telemetry.plot.stacked',
|
|
telemetry: {
|
|
values: [
|
|
{
|
|
key: 'some-key'
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
|
|
let plotView = applicableViews.find((viewProvider) => viewProvider.key === 'plot-stacked');
|
|
expect(plotView).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('The single plot view', () => {
|
|
let testTelemetryObject;
|
|
let applicableViews;
|
|
let plotViewProvider;
|
|
let plotView;
|
|
|
|
beforeEach(() => {
|
|
openmct.time.timeSystem('utc', {
|
|
start: 0,
|
|
end: 4
|
|
});
|
|
testTelemetryObject = {
|
|
identifier: {
|
|
namespace: '',
|
|
key: 'test-object'
|
|
},
|
|
type: 'test-object',
|
|
name: 'Test Object',
|
|
telemetry: {
|
|
values: [
|
|
{
|
|
key: 'utc',
|
|
format: 'utc',
|
|
name: 'Time',
|
|
hints: {
|
|
domain: 1
|
|
}
|
|
},
|
|
{
|
|
key: 'some-key',
|
|
name: 'Some attribute',
|
|
hints: {
|
|
range: 1
|
|
}
|
|
},
|
|
{
|
|
key: 'some-other-key',
|
|
name: 'Another attribute',
|
|
hints: {
|
|
range: 2
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
openmct.router.path = [testTelemetryObject];
|
|
|
|
applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
|
|
plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'plot-single');
|
|
plotView = plotViewProvider.view(testTelemetryObject, []);
|
|
plotView.show(child, true, { renderWhenVisible });
|
|
|
|
return nextTick();
|
|
});
|
|
|
|
afterEach(() => {
|
|
openmct.router.path = null;
|
|
});
|
|
|
|
it('Makes only one request for telemetry on load', () => {
|
|
expect(openmct.telemetry.request).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('Renders a collapsed legend for every telemetry', async () => {
|
|
await nextTick();
|
|
let legend = element.querySelectorAll('.plot-wrapper-collapsed-legend .plot-series-name');
|
|
expect(legend.length).toBe(1);
|
|
expect(legend[0].innerHTML).toEqual('Test Object');
|
|
});
|
|
|
|
it('Renders an expanded legend for every telemetry', async () => {
|
|
await nextTick();
|
|
let legendControl = element.querySelector(
|
|
'.c-plot-legend__view-control.gl-plot-legend__view-control.c-disclosure-triangle'
|
|
);
|
|
const clickEvent = createMouseEvent('click');
|
|
|
|
legendControl.dispatchEvent(clickEvent);
|
|
await nextTick();
|
|
|
|
let legend = element.querySelectorAll('.plot-wrapper-expanded-legend .plot-legend-item td');
|
|
expect(legend.length).toBe(6);
|
|
});
|
|
|
|
it('Renders X-axis ticks for the telemetry object', (done) => {
|
|
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
|
|
const config = configStore.get(configId);
|
|
config.xAxis.set('displayRange', {
|
|
min: 0,
|
|
max: 4
|
|
});
|
|
|
|
nextTick(() => {
|
|
let xAxisElement = element.querySelectorAll(
|
|
'.gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper'
|
|
);
|
|
expect(xAxisElement.length).toBe(1);
|
|
|
|
let ticks = xAxisElement[0].querySelectorAll('.gl-plot-tick');
|
|
expect(ticks.length).toBe(9);
|
|
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('Renders Y-axis options for the telemetry object', async () => {
|
|
await nextTick();
|
|
let yAxisElement = element.querySelectorAll(
|
|
'.gl-plot-axis-area.gl-plot-y .gl-plot-y-label__select'
|
|
);
|
|
expect(yAxisElement.length).toBe(1);
|
|
//Object{name: "Some attribute", key: "some-key"}, Object{name: "Another attribute", key: "some-other-key"}
|
|
let options = yAxisElement[0].querySelectorAll('option');
|
|
expect(options.length).toBe(2);
|
|
expect(options[0].value).toBe('Some attribute');
|
|
expect(options[1].value).toBe('Another attribute');
|
|
});
|
|
|
|
xit('Updates the Y-axis label when changed', () => {
|
|
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
|
|
const config = configStore.get(configId);
|
|
const yAxisElement = element.querySelectorAll('.gl-plot-axis-area.gl-plot-y')[0].__vue__;
|
|
config.yAxis.seriesCollection.models.forEach((plotSeries) => {
|
|
expect(plotSeries.model.yKey).toBe('some-key');
|
|
});
|
|
|
|
yAxisElement.$emit('y-key-changed', TEST_KEY_ID, 1);
|
|
config.yAxis.seriesCollection.models.forEach((plotSeries) => {
|
|
expect(plotSeries.model.yKey).toBe(TEST_KEY_ID);
|
|
});
|
|
});
|
|
|
|
it('hides the pause and play controls', () => {
|
|
let pauseEl = element.querySelectorAll('.c-button-set .icon-pause');
|
|
let playEl = element.querySelectorAll('.c-button-set .icon-arrow-right');
|
|
expect(pauseEl.length).toBe(0);
|
|
expect(playEl.length).toBe(0);
|
|
});
|
|
|
|
describe('pause and play controls', () => {
|
|
beforeEach(() => {
|
|
openmct.time.setClock('local');
|
|
openmct.time.setClockOffsets({
|
|
start: -1000,
|
|
end: 100
|
|
});
|
|
|
|
return nextTick();
|
|
});
|
|
|
|
it('shows the pause controls', (done) => {
|
|
nextTick(() => {
|
|
let pauseEl = element.querySelectorAll('.c-button-set .icon-pause');
|
|
expect(pauseEl.length).toBe(1);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('shows the play control if plot is paused', (done) => {
|
|
let pauseEl = element.querySelector('.c-button-set .icon-pause');
|
|
const clickEvent = createMouseEvent('click');
|
|
|
|
pauseEl.dispatchEvent(clickEvent);
|
|
nextTick(() => {
|
|
let playEl = element.querySelectorAll('.c-button-set .is-paused');
|
|
expect(playEl.length).toBe(1);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('resume actions on errant click', () => {
|
|
beforeEach(() => {
|
|
openmct.time.setClock('local');
|
|
openmct.time.setClockOffsets({
|
|
start: -1000,
|
|
end: 100
|
|
});
|
|
|
|
return nextTick();
|
|
});
|
|
|
|
it('clicking the plot view without movement resumes the plot while active', async () => {
|
|
const pauseEl = element.querySelectorAll('.c-button-set .icon-pause');
|
|
// if the pause button is present, the chart is running
|
|
expect(pauseEl.length).toBe(1);
|
|
|
|
// simulate an errant mouse click
|
|
// the second item is the canvas we need to use
|
|
const canvas = element.querySelectorAll('canvas')[1];
|
|
const mouseDownEvent = new MouseEvent('mousedown');
|
|
const mouseUpEvent = new MouseEvent('mouseup');
|
|
canvas.dispatchEvent(mouseDownEvent);
|
|
// mouseup event is bound to the window
|
|
window.dispatchEvent(mouseUpEvent);
|
|
await nextTick();
|
|
|
|
const pauseElAfterClick = element.querySelectorAll('.c-button-set .icon-pause');
|
|
console.log('pauseElAfterClick', pauseElAfterClick);
|
|
expect(pauseElAfterClick.length).toBe(1);
|
|
});
|
|
|
|
it('clicking the plot view without movement leaves the plot paused', async () => {
|
|
const pauseEl = element.querySelector('.c-button-set .icon-pause');
|
|
// pause the plot
|
|
pauseEl.dispatchEvent(createMouseEvent('click'));
|
|
await nextTick();
|
|
|
|
const playEl = element.querySelectorAll('.c-button-set .is-paused');
|
|
expect(playEl.length).toBe(1);
|
|
|
|
// simulate an errant mouse click
|
|
// the second item is the canvas we need to use
|
|
const canvas = element.querySelectorAll('canvas')[1];
|
|
const mouseDownEvent = new MouseEvent('mousedown');
|
|
const mouseUpEvent = new MouseEvent('mouseup');
|
|
canvas.dispatchEvent(mouseDownEvent);
|
|
// mouseup event is bound to the window
|
|
window.dispatchEvent(mouseUpEvent);
|
|
await nextTick();
|
|
|
|
const playElAfterChartClick = element.querySelectorAll('.c-button-set .is-paused');
|
|
expect(playElAfterChartClick.length).toBe(1);
|
|
});
|
|
|
|
it('clicking the plot does not request historical data', async () => {
|
|
expect(openmct.telemetry.request).toHaveBeenCalledTimes(2);
|
|
|
|
// simulate an errant mouse click
|
|
// the second item is the canvas we need to use
|
|
const canvas = element.querySelectorAll('canvas')[1];
|
|
const mouseDownEvent = new MouseEvent('mousedown');
|
|
const mouseUpEvent = new MouseEvent('mouseup');
|
|
canvas.dispatchEvent(mouseDownEvent);
|
|
// mouseup event is bound to the window
|
|
window.dispatchEvent(mouseUpEvent);
|
|
await nextTick();
|
|
|
|
expect(openmct.telemetry.request).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
describe('limits', () => {
|
|
it('lines are not displayed by default', () => {
|
|
let limitEl = element.querySelectorAll('.js-limit-area .js-limit-line');
|
|
expect(limitEl.length).toBe(0);
|
|
});
|
|
|
|
it('lines are displayed when configuration is set to true', async () => {
|
|
await nextTick();
|
|
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
|
|
const config = configStore.get(configId);
|
|
config.yAxis.set('displayRange', {
|
|
min: 0,
|
|
max: 4
|
|
});
|
|
config.series.models[0].set('limitLines', true);
|
|
|
|
await nextTick();
|
|
let limitEl = element.querySelectorAll('.js-limit-area .js-limit-line');
|
|
expect(limitEl.length).toBe(4);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('controls in time strip view', () => {
|
|
it('zoom controls are hidden', () => {
|
|
let pauseEl = element.querySelectorAll('.c-button-set .js-zoom');
|
|
expect(pauseEl.length).toBe(0);
|
|
});
|
|
|
|
it('pan controls are hidden', () => {
|
|
let pauseEl = element.querySelectorAll('.c-button-set .js-pan');
|
|
expect(pauseEl.length).toBe(0);
|
|
});
|
|
|
|
it('pause/play controls are hidden', () => {
|
|
let pauseEl = element.querySelectorAll('.c-button-set .js-pause');
|
|
expect(pauseEl.length).toBe(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('resizing the plot', () => {
|
|
let plotContainerResizeObserver;
|
|
let resizePromiseResolve;
|
|
let testTelemetryObject;
|
|
let applicableViews;
|
|
let plotViewProvider;
|
|
let plotView;
|
|
let resizePromise;
|
|
|
|
beforeEach(() => {
|
|
testTelemetryObject = {
|
|
identifier: {
|
|
namespace: '',
|
|
key: 'test-object'
|
|
},
|
|
type: 'test-object',
|
|
name: 'Test Object',
|
|
telemetry: {
|
|
values: [
|
|
{
|
|
key: 'utc',
|
|
format: 'utc',
|
|
name: 'Time',
|
|
hints: {
|
|
domain: 1
|
|
}
|
|
},
|
|
{
|
|
key: 'some-key',
|
|
name: 'Some attribute',
|
|
hints: {
|
|
range: 1
|
|
}
|
|
},
|
|
{
|
|
key: 'some-other-key',
|
|
name: 'Another attribute',
|
|
hints: {
|
|
range: 2
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
openmct.router.path = [testTelemetryObject];
|
|
|
|
applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
|
|
plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'plot-single');
|
|
plotView = plotViewProvider.view(testTelemetryObject, []);
|
|
|
|
plotView.show(child, true, { renderWhenVisible });
|
|
|
|
resizePromise = new Promise((resolve) => {
|
|
resizePromiseResolve = resolve;
|
|
});
|
|
|
|
const handlePlotResize = _.debounce(() => {
|
|
resizePromiseResolve(true);
|
|
}, 600);
|
|
|
|
plotContainerResizeObserver = new ResizeObserver(handlePlotResize);
|
|
plotContainerResizeObserver.observe(
|
|
plotView.getComponent().$refs.plotComponent.$refs.plotWrapper
|
|
);
|
|
|
|
return nextTick(() => {
|
|
plotView.getComponent().$refs.plotComponent.$refs.mctPlot.stopFollowingTimeContext();
|
|
spyOn(
|
|
plotView.getComponent().$refs.plotComponent.$refs.mctPlot,
|
|
'loadSeriesData'
|
|
).and.callThrough();
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
plotContainerResizeObserver.disconnect();
|
|
openmct.router.path = null;
|
|
});
|
|
|
|
xit('requests historical data when over the threshold', async () => {
|
|
await nextTick();
|
|
element.style.width = '680px';
|
|
await resizePromise;
|
|
expect(
|
|
plotView.getComponent().$refs.plotComponent.$refs.mctPlot.loadSeriesData
|
|
).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('does not request historical data when under the threshold', async () => {
|
|
element.style.width = '644px';
|
|
await resizePromise;
|
|
expect(
|
|
plotView.getComponent().$refs.plotComponent.$refs.mctPlot.loadSeriesData
|
|
).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('the inspector view', () => {
|
|
let component;
|
|
let viewComponentObject;
|
|
let mockComposition;
|
|
let testTelemetryObject;
|
|
let selection;
|
|
let config;
|
|
beforeEach((done) => {
|
|
testTelemetryObject = {
|
|
identifier: {
|
|
namespace: '',
|
|
key: 'test-object'
|
|
},
|
|
type: 'test-object',
|
|
name: 'Test Object',
|
|
telemetry: {
|
|
values: [
|
|
{
|
|
key: 'utc',
|
|
format: 'utc',
|
|
name: 'Time',
|
|
hints: {
|
|
domain: 1
|
|
}
|
|
},
|
|
{
|
|
key: 'some-key',
|
|
name: 'Some attribute',
|
|
hints: {
|
|
range: 1
|
|
}
|
|
},
|
|
{
|
|
key: 'some-other-key',
|
|
name: 'Another attribute',
|
|
hints: {
|
|
range: 2
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
selection = [
|
|
[
|
|
{
|
|
context: {
|
|
item: {
|
|
id: 'test-object',
|
|
identifier: {
|
|
key: 'test-object',
|
|
namespace: ''
|
|
},
|
|
type: 'telemetry.plot.overlay',
|
|
configuration: {
|
|
series: [
|
|
{
|
|
identifier: {
|
|
key: 'test-object',
|
|
namespace: ''
|
|
}
|
|
}
|
|
]
|
|
},
|
|
composition: []
|
|
}
|
|
}
|
|
},
|
|
{
|
|
context: {
|
|
item: {
|
|
type: 'time-strip',
|
|
identifier: {
|
|
key: 'some-other-key',
|
|
namespace: ''
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
];
|
|
|
|
openmct.router.path = [testTelemetryObject];
|
|
mockComposition = new EventEmitter();
|
|
mockComposition.load = () => {
|
|
mockComposition.emit('add', testTelemetryObject);
|
|
|
|
return [testTelemetryObject];
|
|
};
|
|
|
|
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
|
|
|
const configId = openmct.objects.makeKeyString(selection[0][0].context.item.identifier);
|
|
config = new PlotConfigurationModel({
|
|
id: configId,
|
|
domainObject: selection[0][0].context.item,
|
|
openmct: openmct
|
|
});
|
|
configStore.add(configId, config);
|
|
|
|
let viewContainer = document.createElement('div');
|
|
child.append(viewContainer);
|
|
const { vNode } = mount(
|
|
{
|
|
components: {
|
|
PlotOptions
|
|
},
|
|
provide: {
|
|
openmct: openmct,
|
|
domainObject: selection[0][0].context.item,
|
|
path: [selection[0][0].context.item, selection[0][1].context.item],
|
|
renderWhenVisible
|
|
},
|
|
template: '<plot-options ref="root"/>'
|
|
},
|
|
{
|
|
element: viewContainer
|
|
}
|
|
);
|
|
component = vNode.componentInstance;
|
|
|
|
nextTick(() => {
|
|
viewComponentObject = component.$refs.root;
|
|
done();
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
openmct.router.path = null;
|
|
});
|
|
|
|
describe('in view only mode', () => {
|
|
let browseOptionsEl;
|
|
let editOptionsEl;
|
|
beforeEach(() => {
|
|
browseOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-browse');
|
|
editOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-edit');
|
|
});
|
|
|
|
it('does not show the edit options', () => {
|
|
expect(editOptionsEl).toBeNull();
|
|
});
|
|
|
|
it('shows the name', () => {
|
|
const seriesEl = browseOptionsEl.querySelector('.c-object-label__name');
|
|
expect(seriesEl.innerHTML).toEqual(testTelemetryObject.name);
|
|
});
|
|
|
|
it('shows in collapsed mode', () => {
|
|
const seriesEl = browseOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded');
|
|
expect(seriesEl.length).toEqual(0);
|
|
});
|
|
|
|
it('shows in expanded mode', () => {
|
|
let expandControl = browseOptionsEl.querySelector('.c-disclosure-triangle');
|
|
const clickEvent = createMouseEvent('click');
|
|
expandControl.dispatchEvent(clickEvent);
|
|
|
|
const plotOptionsProperties = browseOptionsEl.querySelectorAll(
|
|
'.js-plot-options-browse-properties .grid-row'
|
|
);
|
|
expect(plotOptionsProperties.length).toEqual(6);
|
|
});
|
|
});
|
|
|
|
describe('in edit mode', () => {
|
|
let editOptionsEl;
|
|
let browseOptionsEl;
|
|
|
|
beforeEach((done) => {
|
|
viewComponentObject.setEditState(true);
|
|
nextTick(() => {
|
|
editOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-edit');
|
|
browseOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-browse');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('does not show the browse options', () => {
|
|
expect(browseOptionsEl).toBeNull();
|
|
});
|
|
|
|
it('shows the name', () => {
|
|
const seriesEl = editOptionsEl.querySelector('.c-object-label__name');
|
|
expect(seriesEl.innerHTML).toEqual(testTelemetryObject.name);
|
|
});
|
|
|
|
it('shows in collapsed mode', () => {
|
|
const seriesEl = editOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded');
|
|
expect(seriesEl.length).toEqual(0);
|
|
});
|
|
|
|
it('shows in collapsed mode', () => {
|
|
const seriesEl = editOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded');
|
|
expect(seriesEl.length).toEqual(0);
|
|
});
|
|
|
|
it('renders expanded', () => {
|
|
const expandControl = editOptionsEl.querySelector('.c-disclosure-triangle');
|
|
const clickEvent = createMouseEvent('click');
|
|
expandControl.dispatchEvent(clickEvent);
|
|
|
|
const plotOptionsProperties = editOptionsEl.querySelectorAll(
|
|
'.js-plot-options-edit-properties .grid-row'
|
|
);
|
|
expect(plotOptionsProperties.length).toEqual(8);
|
|
});
|
|
|
|
it('shows yKeyOptions', () => {
|
|
const expandControl = editOptionsEl.querySelector('.c-disclosure-triangle');
|
|
const clickEvent = createMouseEvent('click');
|
|
expandControl.dispatchEvent(clickEvent);
|
|
|
|
const plotOptionsProperties = editOptionsEl.querySelectorAll(
|
|
'.js-plot-options-edit-properties .grid-row'
|
|
);
|
|
|
|
const yKeySelection = plotOptionsProperties[0].querySelector('select');
|
|
const options = Array.from(yKeySelection.options).map((option) => {
|
|
return option.value;
|
|
});
|
|
expect(options).toEqual([
|
|
testTelemetryObject.telemetry.values[1].key,
|
|
testTelemetryObject.telemetry.values[2].key
|
|
]);
|
|
});
|
|
|
|
it('shows yAxis options', () => {
|
|
const expandControl = editOptionsEl.querySelector('.c-disclosure-triangle');
|
|
const clickEvent = createMouseEvent('click');
|
|
expandControl.dispatchEvent(clickEvent);
|
|
|
|
const yAxisProperties = editOptionsEl.querySelectorAll(
|
|
'div.grid-properties:first-of-type .l-inspector-part'
|
|
);
|
|
|
|
// TODO better test
|
|
expect(yAxisProperties.length).toEqual(2);
|
|
});
|
|
|
|
it('renders color palette options', () => {
|
|
const colorSwatch = editOptionsEl.querySelector('.c-click-swatch');
|
|
expect(colorSwatch).toBeDefined();
|
|
});
|
|
});
|
|
});
|
|
});
|