728 lines
28 KiB
JavaScript
728 lines
28 KiB
JavaScript
/*****************************************************************************
|
|
* Open MCT, Copyright (c) 2014-2021, 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";
|
|
import ConditionPlugin from "./plugin";
|
|
import stylesManager from '@/ui/inspector/styles/StylesManager';
|
|
import StylesView from "./components/inspector/StylesView.vue";
|
|
import Vue from 'vue';
|
|
import {getApplicableStylesForItem} from "./utils/styleUtils";
|
|
import ConditionManager from "@/plugins/condition/ConditionManager";
|
|
|
|
describe('the plugin', function () {
|
|
let conditionSetDefinition;
|
|
let mockConditionSetDomainObject;
|
|
let mockListener;
|
|
let element;
|
|
let child;
|
|
let openmct;
|
|
let testTelemetryObject;
|
|
|
|
beforeEach((done) => {
|
|
testTelemetryObject = {
|
|
identifier: {
|
|
namespace: "",
|
|
key: "test-object"
|
|
},
|
|
type: "test-object",
|
|
name: "Test Object",
|
|
telemetry: {
|
|
values: [{
|
|
key: "some-key2",
|
|
source: "some-key2",
|
|
name: "Some attribute",
|
|
hints: {
|
|
range: 2
|
|
}
|
|
},
|
|
{
|
|
key: "utc",
|
|
name: "Time",
|
|
format: "utc",
|
|
hints: {
|
|
domain: 1
|
|
}
|
|
}, {
|
|
key: "testSource",
|
|
source: "value",
|
|
name: "Test",
|
|
format: "string"
|
|
},
|
|
{
|
|
key: "some-key",
|
|
source: "some-key",
|
|
hints: {
|
|
domain: 1
|
|
}
|
|
}]
|
|
}
|
|
};
|
|
|
|
openmct = createOpenMct();
|
|
openmct.install(new ConditionPlugin());
|
|
|
|
conditionSetDefinition = openmct.types.get('conditionSet').definition;
|
|
|
|
element = document.createElement('div');
|
|
child = document.createElement('div');
|
|
element.appendChild(child);
|
|
|
|
mockConditionSetDomainObject = {
|
|
identifier: {
|
|
key: 'testConditionSetKey',
|
|
namespace: ''
|
|
},
|
|
type: 'conditionSet'
|
|
};
|
|
|
|
mockListener = jasmine.createSpy('mockListener');
|
|
|
|
conditionSetDefinition.initialize(mockConditionSetDomainObject);
|
|
|
|
spyOn(openmct.objects, "save").and.returnValue(Promise.resolve(true));
|
|
|
|
openmct.on('start', done);
|
|
openmct.startHeadless();
|
|
});
|
|
|
|
afterEach(() => {
|
|
return resetApplicationState(openmct);
|
|
});
|
|
|
|
let mockConditionSetObject = {
|
|
name: 'Condition Set',
|
|
key: 'conditionSet',
|
|
creatable: true
|
|
};
|
|
|
|
it('defines a conditionSet object type with the correct key', () => {
|
|
expect(conditionSetDefinition.key).toEqual(mockConditionSetObject.key);
|
|
});
|
|
|
|
describe('the conditionSet object', () => {
|
|
|
|
it('is creatable', () => {
|
|
expect(conditionSetDefinition.creatable).toEqual(mockConditionSetObject.creatable);
|
|
});
|
|
|
|
it('initializes with an empty composition list', () => {
|
|
expect(mockConditionSetDomainObject.composition instanceof Array).toBeTrue();
|
|
expect(mockConditionSetDomainObject.composition.length).toEqual(0);
|
|
});
|
|
|
|
it('provides a view', () => {
|
|
const testViewObject = {
|
|
id: "test-object",
|
|
type: "conditionSet",
|
|
configuration: {
|
|
conditionCollection: []
|
|
}
|
|
};
|
|
|
|
const applicableViews = openmct.objectViews.get(testViewObject, []);
|
|
let conditionSetView = applicableViews.find((viewProvider) => viewProvider.key === 'conditionSet.view');
|
|
expect(conditionSetView).toBeDefined();
|
|
});
|
|
|
|
});
|
|
|
|
describe('the condition set usage for multiple display layout items', () => {
|
|
let displayLayoutItem;
|
|
let lineLayoutItem;
|
|
let boxLayoutItem;
|
|
let notCreatableObjectItem;
|
|
let notCreatableObject;
|
|
let selection;
|
|
let component;
|
|
let styleViewComponentObject;
|
|
const conditionSetDomainObject = {
|
|
"configuration": {
|
|
"conditionTestData": [
|
|
{
|
|
"telemetry": "",
|
|
"metadata": "",
|
|
"input": ""
|
|
}
|
|
],
|
|
"conditionCollection": [
|
|
{
|
|
"id": "39584410-cbf9-499e-96dc-76f27e69885d",
|
|
"configuration": {
|
|
"name": "Unnamed Condition",
|
|
"output": "Sine > 0",
|
|
"trigger": "all",
|
|
"criteria": [
|
|
{
|
|
"id": "85fbb2f7-7595-42bd-9767-a932266c5225",
|
|
"telemetry": {
|
|
"namespace": "",
|
|
"key": "be0ba97f-b510-4f40-a18d-4ff121d5ea1a"
|
|
},
|
|
"operation": "greaterThan",
|
|
"input": [
|
|
"0"
|
|
],
|
|
"metadata": "sin"
|
|
},
|
|
{
|
|
"id": "35400132-63b0-425c-ac30-8197df7d5862",
|
|
"telemetry": "any",
|
|
"operation": "enumValueIs",
|
|
"input": [
|
|
"0"
|
|
],
|
|
"metadata": "state"
|
|
}
|
|
]
|
|
},
|
|
"summary": "Match if all criteria are met: Sine Wave Generator Sine > 0 and any telemetry State is OFF "
|
|
},
|
|
{
|
|
"isDefault": true,
|
|
"id": "2532d90a-e0d6-4935-b546-3123522da2de",
|
|
"configuration": {
|
|
"name": "Default",
|
|
"output": "Default",
|
|
"trigger": "all",
|
|
"criteria": [
|
|
]
|
|
},
|
|
"summary": ""
|
|
}
|
|
]
|
|
},
|
|
"composition": [
|
|
{
|
|
"namespace": "",
|
|
"key": "be0ba97f-b510-4f40-a18d-4ff121d5ea1a"
|
|
},
|
|
{
|
|
"namespace": "",
|
|
"key": "077ffa67-e78f-4e99-80e0-522ac33a3888"
|
|
}
|
|
],
|
|
"telemetry": {
|
|
},
|
|
"name": "Condition Set",
|
|
"type": "conditionSet",
|
|
"identifier": {
|
|
"namespace": "",
|
|
"key": "863012c1-f6ca-4ab0-aed7-fd43d5e4cd12"
|
|
}
|
|
|
|
};
|
|
const staticStyle = {
|
|
"style": {
|
|
"backgroundColor": "#666666",
|
|
"border": "1px solid #00ffff"
|
|
}
|
|
};
|
|
const conditionalStyle = {
|
|
"conditionId": "39584410-cbf9-499e-96dc-76f27e69885d",
|
|
"style": {
|
|
"isStyleInvisible": "",
|
|
"backgroundColor": "#666666",
|
|
"border": "1px solid #ffff00"
|
|
}
|
|
};
|
|
|
|
beforeEach(() => {
|
|
displayLayoutItem = {
|
|
"composition": [
|
|
],
|
|
"configuration": {
|
|
"items": [
|
|
{
|
|
"fill": "#666666",
|
|
"stroke": "",
|
|
"x": 1,
|
|
"y": 1,
|
|
"width": 10,
|
|
"height": 5,
|
|
"type": "box-view",
|
|
"id": "89b88746-d325-487b-aec4-11b79afff9e8"
|
|
},
|
|
{
|
|
"fill": "#666666",
|
|
"stroke": "",
|
|
"x": 1,
|
|
"y": 1,
|
|
"width": 10,
|
|
"height": 5,
|
|
"type": "ellipse-view",
|
|
"id": "19b88746-d325-487b-aec4-11b79afff9z8"
|
|
},
|
|
{
|
|
"x": 18,
|
|
"y": 9,
|
|
"x2": 23,
|
|
"y2": 4,
|
|
"stroke": "#666666",
|
|
"type": "line-view",
|
|
"id": "57d49a28-7863-43bd-9593-6570758916f0"
|
|
},
|
|
{
|
|
"width": 32,
|
|
"height": 18,
|
|
"x": 36,
|
|
"y": 8,
|
|
"identifier": {
|
|
"key": "~TEST~image",
|
|
"namespace": "test-space"
|
|
},
|
|
"hasFrame": true,
|
|
"type": "subobject-view",
|
|
"id": "6d9fe81b-a3ce-4e59-b404-a4a0be1a5d85"
|
|
}
|
|
],
|
|
"layoutGrid": [
|
|
10,
|
|
10
|
|
]
|
|
},
|
|
"name": "Display Layout",
|
|
"type": "layout",
|
|
"identifier": {
|
|
"namespace": "",
|
|
"key": "c5e636c1-6771-4c9c-b933-8665cab189b3"
|
|
}
|
|
};
|
|
lineLayoutItem = {
|
|
"x": 18,
|
|
"y": 9,
|
|
"x2": 23,
|
|
"y2": 4,
|
|
"stroke": "#666666",
|
|
"type": "line-view",
|
|
"id": "57d49a28-7863-43bd-9593-6570758916f0"
|
|
};
|
|
boxLayoutItem = {
|
|
"fill": "#666666",
|
|
"stroke": "",
|
|
"x": 1,
|
|
"y": 1,
|
|
"width": 10,
|
|
"height": 5,
|
|
"type": "box-view",
|
|
"id": "89b88746-d325-487b-aec4-11b79afff9e8"
|
|
};
|
|
notCreatableObjectItem = {
|
|
"width": 32,
|
|
"height": 18,
|
|
"x": 36,
|
|
"y": 8,
|
|
"identifier": {
|
|
"key": "~TEST~image",
|
|
"namespace": "test-space"
|
|
},
|
|
"hasFrame": true,
|
|
"type": "subobject-view",
|
|
"id": "6d9fe81b-a3ce-4e59-b404-a4a0be1a5d85"
|
|
};
|
|
notCreatableObject = {
|
|
"identifier": {
|
|
"key": "~TEST~image",
|
|
"namespace": "test-space"
|
|
},
|
|
"name": "test~image",
|
|
"location": "test-space:~TEST",
|
|
"type": "test.image",
|
|
"telemetry": {
|
|
"values": [
|
|
{
|
|
"key": "value",
|
|
"name": "Value",
|
|
"hints": {
|
|
"image": 1,
|
|
"priority": 0
|
|
},
|
|
"format": "image",
|
|
"source": "value"
|
|
},
|
|
{
|
|
"key": "utc",
|
|
"source": "timestamp",
|
|
"name": "Timestamp",
|
|
"format": "iso",
|
|
"hints": {
|
|
"domain": 1,
|
|
"priority": 1
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
selection = [
|
|
[{
|
|
context: {
|
|
"layoutItem": lineLayoutItem,
|
|
"index": 1
|
|
}
|
|
},
|
|
{
|
|
context: {
|
|
"item": displayLayoutItem,
|
|
"supportsMultiSelect": true
|
|
}
|
|
}],
|
|
[{
|
|
context: {
|
|
"layoutItem": boxLayoutItem,
|
|
"index": 0
|
|
}
|
|
},
|
|
{
|
|
context: {
|
|
item: displayLayoutItem,
|
|
"supportsMultiSelect": true
|
|
}
|
|
}],
|
|
[{
|
|
context: {
|
|
"item": notCreatableObject,
|
|
"layoutItem": notCreatableObjectItem,
|
|
"index": 2
|
|
}
|
|
},
|
|
{
|
|
context: {
|
|
item: displayLayoutItem,
|
|
"supportsMultiSelect": true
|
|
}
|
|
}]
|
|
];
|
|
let viewContainer = document.createElement('div');
|
|
child.append(viewContainer);
|
|
component = new Vue({
|
|
el: viewContainer,
|
|
components: {
|
|
StylesView
|
|
},
|
|
provide: {
|
|
openmct: openmct,
|
|
selection: selection,
|
|
stylesManager
|
|
},
|
|
template: '<styles-view/>'
|
|
});
|
|
|
|
return Vue.nextTick().then(() => {
|
|
styleViewComponentObject = component.$root.$children[0];
|
|
styleViewComponentObject.setEditState(true);
|
|
});
|
|
});
|
|
|
|
it('initializes the items in the view', () => {
|
|
expect(styleViewComponentObject.items.length).toBe(3);
|
|
});
|
|
|
|
it('initializes conditional styles', () => {
|
|
styleViewComponentObject.conditionSetDomainObject = conditionSetDomainObject;
|
|
styleViewComponentObject.conditionalStyles = [];
|
|
styleViewComponentObject.initializeConditionalStyles();
|
|
expect(styleViewComponentObject.conditionalStyles.length).toBe(2);
|
|
});
|
|
|
|
it('updates applicable conditional styles', () => {
|
|
styleViewComponentObject.conditionSetDomainObject = conditionSetDomainObject;
|
|
styleViewComponentObject.conditionalStyles = [];
|
|
styleViewComponentObject.initializeConditionalStyles();
|
|
expect(styleViewComponentObject.conditionalStyles.length).toBe(2);
|
|
styleViewComponentObject.updateConditionalStyle(conditionalStyle, 'border');
|
|
|
|
return Vue.nextTick().then(() => {
|
|
expect(styleViewComponentObject.domainObject.configuration.objectStyles).toBeDefined();
|
|
[boxLayoutItem, lineLayoutItem, notCreatableObjectItem].forEach((item) => {
|
|
const itemStyles = styleViewComponentObject.domainObject.configuration.objectStyles[item.id].styles;
|
|
expect(itemStyles.length).toBe(2);
|
|
const foundStyle = itemStyles.find((style) => {
|
|
return style.conditionId === conditionalStyle.conditionId;
|
|
});
|
|
expect(foundStyle).toBeDefined();
|
|
const applicableStyles = getApplicableStylesForItem(styleViewComponentObject.domainObject, item);
|
|
const applicableStylesKeys = Object.keys(applicableStyles).concat(['isStyleInvisible']);
|
|
Object.keys(foundStyle.style).forEach((key) => {
|
|
expect(applicableStylesKeys.indexOf(key)).toBeGreaterThan(-1);
|
|
expect(foundStyle.style[key]).toEqual(conditionalStyle.style[key]);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('updates applicable static styles', () => {
|
|
styleViewComponentObject.updateStaticStyle(staticStyle, 'border');
|
|
|
|
return Vue.nextTick().then(() => {
|
|
expect(styleViewComponentObject.domainObject.configuration.objectStyles).toBeDefined();
|
|
[boxLayoutItem, lineLayoutItem, notCreatableObjectItem].forEach((item) => {
|
|
const itemStyle = styleViewComponentObject.domainObject.configuration.objectStyles[item.id].staticStyle;
|
|
expect(itemStyle).toBeDefined();
|
|
const applicableStyles = getApplicableStylesForItem(styleViewComponentObject.domainObject, item);
|
|
const applicableStylesKeys = Object.keys(applicableStyles).concat(['isStyleInvisible']);
|
|
Object.keys(itemStyle.style).forEach((key) => {
|
|
expect(applicableStylesKeys.indexOf(key)).toBeGreaterThan(-1);
|
|
expect(itemStyle.style[key]).toEqual(staticStyle.style[key]);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
describe('the condition check for staleness', () => {
|
|
let conditionSetDomainObject;
|
|
|
|
beforeEach(() => {
|
|
conditionSetDomainObject = {
|
|
"configuration": {
|
|
"conditionTestData": [
|
|
{
|
|
"telemetry": "",
|
|
"metadata": "",
|
|
"input": ""
|
|
}
|
|
],
|
|
"conditionCollection": [
|
|
{
|
|
"id": "39584410-cbf9-499e-96dc-76f27e69885d",
|
|
"configuration": {
|
|
"name": "Unnamed Condition",
|
|
"output": "Any stale telemetry",
|
|
"trigger": "all",
|
|
"criteria": [
|
|
{
|
|
"id": "35400132-63b0-425c-ac30-8197df7d5862",
|
|
"telemetry": "any",
|
|
"operation": "isStale",
|
|
"input": [
|
|
"0.2"
|
|
],
|
|
"metadata": "dataReceived"
|
|
}
|
|
]
|
|
},
|
|
"summary": "Match if all criteria are met: Any telemetry is stale after 5 seconds"
|
|
},
|
|
{
|
|
"isDefault": true,
|
|
"id": "2532d90a-e0d6-4935-b546-3123522da2de",
|
|
"configuration": {
|
|
"name": "Default",
|
|
"output": "Default",
|
|
"trigger": "all",
|
|
"criteria": [
|
|
]
|
|
},
|
|
"summary": ""
|
|
}
|
|
]
|
|
},
|
|
"composition": [
|
|
{
|
|
"namespace": "",
|
|
"key": "test-object"
|
|
}
|
|
],
|
|
"telemetry": {
|
|
},
|
|
"name": "Condition Set",
|
|
"type": "conditionSet",
|
|
"identifier": {
|
|
"namespace": "",
|
|
"key": "cf4456a9-296a-4e6b-b182-62ed29cd15b9"
|
|
}
|
|
|
|
};
|
|
});
|
|
|
|
it('should evaluate as stale when telemetry is not received in the allotted time', (done) => {
|
|
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
|
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
|
conditionMgr.telemetryObjects = {
|
|
"test-object": testTelemetryObject
|
|
};
|
|
conditionMgr.updateConditionTelemetryObjects();
|
|
setTimeout(() => {
|
|
expect(mockListener).toHaveBeenCalledWith({
|
|
output: 'Any stale telemetry',
|
|
id: {
|
|
namespace: '',
|
|
key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9'
|
|
},
|
|
conditionId: '39584410-cbf9-499e-96dc-76f27e69885d',
|
|
utc: undefined
|
|
});
|
|
done();
|
|
}, 400);
|
|
});
|
|
|
|
it('should not evaluate as stale when telemetry is received in the allotted time', (done) => {
|
|
const date = 1;
|
|
conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input = ["0.4"];
|
|
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
|
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
|
conditionMgr.telemetryObjects = {
|
|
"test-object": testTelemetryObject
|
|
};
|
|
conditionMgr.updateConditionTelemetryObjects();
|
|
conditionMgr.telemetryReceived(testTelemetryObject, {
|
|
utc: date
|
|
});
|
|
setTimeout(() => {
|
|
expect(mockListener).toHaveBeenCalledWith({
|
|
output: 'Default',
|
|
id: {
|
|
namespace: '',
|
|
key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9'
|
|
},
|
|
conditionId: '2532d90a-e0d6-4935-b546-3123522da2de',
|
|
utc: date
|
|
});
|
|
done();
|
|
}, 300);
|
|
});
|
|
});
|
|
|
|
describe('the condition evaluation', () => {
|
|
let conditionSetDomainObject;
|
|
|
|
beforeEach(() => {
|
|
conditionSetDomainObject = {
|
|
"configuration": {
|
|
"conditionTestData": [
|
|
{
|
|
"telemetry": "",
|
|
"metadata": "",
|
|
"input": ""
|
|
}
|
|
],
|
|
"conditionCollection": [
|
|
{
|
|
"id": "39584410-cbf9-499e-96dc-76f27e69885f",
|
|
"configuration": {
|
|
"name": "Unnamed Condition0",
|
|
"output": "Any telemetry less than 0",
|
|
"trigger": "all",
|
|
"criteria": [
|
|
{
|
|
"id": "35400132-63b0-425c-ac30-8197df7d5864",
|
|
"telemetry": "any",
|
|
"operation": "lessThan",
|
|
"input": [
|
|
"0"
|
|
],
|
|
"metadata": "some-key"
|
|
}
|
|
]
|
|
},
|
|
"summary": "Match if all criteria are met: Any telemetry value is less than 0"
|
|
},
|
|
{
|
|
"id": "39584410-cbf9-499e-96dc-76f27e69885d",
|
|
"configuration": {
|
|
"name": "Unnamed Condition",
|
|
"output": "Any telemetry greater than 0",
|
|
"trigger": "all",
|
|
"criteria": [
|
|
{
|
|
"id": "35400132-63b0-425c-ac30-8197df7d5862",
|
|
"telemetry": "any",
|
|
"operation": "greaterThan",
|
|
"input": [
|
|
"0"
|
|
],
|
|
"metadata": "some-key"
|
|
}
|
|
]
|
|
},
|
|
"summary": "Match if all criteria are met: Any telemetry value is greater than 0"
|
|
},
|
|
{
|
|
"id": "39584410-cbf9-499e-96dc-76f27e69885e",
|
|
"configuration": {
|
|
"name": "Unnamed Condition1",
|
|
"output": "Any telemetry greater than 1",
|
|
"trigger": "all",
|
|
"criteria": [
|
|
{
|
|
"id": "35400132-63b0-425c-ac30-8197df7d5863",
|
|
"telemetry": "any",
|
|
"operation": "greaterThan",
|
|
"input": [
|
|
"1"
|
|
],
|
|
"metadata": "some-key"
|
|
}
|
|
]
|
|
},
|
|
"summary": "Match if all criteria are met: Any telemetry value is greater than 1"
|
|
},
|
|
{
|
|
"isDefault": true,
|
|
"id": "2532d90a-e0d6-4935-b546-3123522da2de",
|
|
"configuration": {
|
|
"name": "Default",
|
|
"output": "Default",
|
|
"trigger": "all",
|
|
"criteria": [
|
|
]
|
|
},
|
|
"summary": ""
|
|
}
|
|
]
|
|
},
|
|
"composition": [
|
|
{
|
|
"namespace": "",
|
|
"key": "test-object"
|
|
}
|
|
],
|
|
"telemetry": {
|
|
},
|
|
"name": "Condition Set",
|
|
"type": "conditionSet",
|
|
"identifier": {
|
|
"namespace": "",
|
|
"key": "cf4456a9-296a-4e6b-b182-62ed29cd15b9"
|
|
}
|
|
|
|
};
|
|
});
|
|
|
|
it('should stop evaluating conditions when a condition evaluates to true', () => {
|
|
const date = Date.now();
|
|
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
|
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
|
conditionMgr.telemetryObjects = {
|
|
"test-object": testTelemetryObject
|
|
};
|
|
conditionMgr.updateConditionTelemetryObjects();
|
|
conditionMgr.telemetryReceived(testTelemetryObject, {
|
|
"some-key": 2,
|
|
utc: date
|
|
});
|
|
let result = conditionMgr.conditions.map(condition => condition.result);
|
|
expect(result[2]).toBeUndefined();
|
|
});
|
|
});
|
|
});
|