mirror of
https://github.com/nasa/openmct.git
synced 2025-06-14 21:28:12 +00:00
Fix Independent Time Conductor for Plans within Time Strips (#5671)
* Update `IndependentTimeContext` only if its `objectPath` differs * Ensure independent time conductor, fixed and realtime inputs have the right objectPath * [e2e] Add Plan creation test * [e2e] add Create TimeStrip test * mark new faultManagement suite with @unstable * Upgrade to @playwright/test v1.25.0 * Extract `createPlanFromJSON` to appActions * [e2e] Add TimeStrip test for Plans, Independent Time Contexts * [e2e] Move test annotation to the top * [e2e] fix timestrip test * Update docker image so the tests run * update playwright on GHA as well * [e2e] Fix menu test * Error if no objectPath provided Co-authored-by: Shefali <simplyrender@gmail.com> Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
This commit is contained in:
@ -2,7 +2,7 @@ version: 2.1
|
|||||||
executors:
|
executors:
|
||||||
pw-focal-development:
|
pw-focal-development:
|
||||||
docker:
|
docker:
|
||||||
- image: mcr.microsoft.com/playwright:v1.23.0-focal
|
- image: mcr.microsoft.com/playwright:v1.25.0-focal
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
|
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
|
||||||
PERCY_POSTINSTALL_BROWSER: 'true' # Needed to store the percy browser in cache deps
|
PERCY_POSTINSTALL_BROWSER: 'true' # Needed to store the percy browser in cache deps
|
||||||
|
2
.github/workflows/e2e-couchdb.yml
vendored
2
.github/workflows/e2e-couchdb.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '16'
|
||||||
- run: npx playwright@1.23.0 install
|
- run: npx playwright@1.25.0 install
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- run: sh src/plugins/persistence/couch/replace-localstorage-with-couchdb-indexhtml.sh
|
- run: sh src/plugins/persistence/couch/replace-localstorage-with-couchdb-indexhtml.sh
|
||||||
- run: npm run test:e2e:couchdb
|
- run: npm run test:e2e:couchdb
|
||||||
|
2
.github/workflows/e2e-pr.yml
vendored
2
.github/workflows/e2e-pr.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
|||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '16'
|
||||||
- run: npx playwright@1.23.0 install
|
- run: npx playwright@1.25.0 install
|
||||||
- run: npx playwright install chrome-beta
|
- run: npx playwright install chrome-beta
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm run test:e2e:full
|
- run: npm run test:e2e:full
|
||||||
|
@ -45,6 +45,8 @@
|
|||||||
* @property {string} url the relative url to the object (for use with `page.goto()`)
|
* @property {string} url the relative url to the object (for use with `page.goto()`)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const Buffer = require('buffer').Buffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This common function creates a domain object with the default options. It is the preferred way of creating objects
|
* This common function creates a domain object with the default options. It is the preferred way of creating objects
|
||||||
* in the e2e suite when uninterested in properties of the objects themselves.
|
* in the e2e suite when uninterested in properties of the objects themselves.
|
||||||
@ -100,6 +102,59 @@ async function createDomainObjectWithDefaults(page, { type, name, parent = 'mine
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Plan object from JSON with the provided options.
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {*} options
|
||||||
|
* @returns {Promise<CreatedObjectInfo>} An object containing information about the newly created domain object.
|
||||||
|
*/
|
||||||
|
async function createPlanFromJSON(page, { name, json, parent = 'mine' }) {
|
||||||
|
const parentUrl = await getHashUrlToDomainObject(page, parent);
|
||||||
|
|
||||||
|
// Navigate to the parent object. This is necessary to create the object
|
||||||
|
// in the correct location, such as a folder, layout, or plot.
|
||||||
|
await page.goto(`${parentUrl}?hideTree=true`);
|
||||||
|
|
||||||
|
//Click the Create button
|
||||||
|
await page.click('button:has-text("Create")');
|
||||||
|
|
||||||
|
// Click 'Plan' menu option
|
||||||
|
await page.click(`li:text("Plan")`);
|
||||||
|
|
||||||
|
// Modify the name input field of the domain object to accept 'name'
|
||||||
|
if (name) {
|
||||||
|
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
|
||||||
|
await nameInput.fill("");
|
||||||
|
await nameInput.fill(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload buffer from memory
|
||||||
|
await page.locator('input#fileElem').setInputFiles({
|
||||||
|
name: 'plan.txt',
|
||||||
|
mimeType: 'text/plain',
|
||||||
|
buffer: Buffer.from(JSON.stringify(json))
|
||||||
|
});
|
||||||
|
|
||||||
|
// Click OK button and wait for Navigate event
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForLoadState(),
|
||||||
|
page.click('[aria-label="Save"]'),
|
||||||
|
// Wait for Save Banner to appear
|
||||||
|
page.waitForSelector('.c-message-banner__message')
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Wait until the URL is updated
|
||||||
|
await page.waitForURL(`**/mine/*`);
|
||||||
|
const uuid = await getFocusedObjectUuid(page);
|
||||||
|
const objectUrl = await getHashUrlToDomainObject(page, uuid);
|
||||||
|
|
||||||
|
return {
|
||||||
|
uuid,
|
||||||
|
name,
|
||||||
|
url: objectUrl
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the given `domainObject`'s context menu from the object tree.
|
* Open the given `domainObject`'s context menu from the object tree.
|
||||||
* Expands the path to the object and scrolls to it if necessary.
|
* Expands the path to the object and scrolls to it if necessary.
|
||||||
@ -258,6 +313,7 @@ async function setEndOffset(page, offset) {
|
|||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createDomainObjectWithDefaults,
|
createDomainObjectWithDefaults,
|
||||||
|
createPlanFromJSON,
|
||||||
openObjectTreeContextMenu,
|
openObjectTreeContextMenu,
|
||||||
getHashUrlToDomainObject,
|
getHashUrlToDomainObject,
|
||||||
getFocusedObjectUuid,
|
getFocusedObjectUuid,
|
||||||
|
@ -42,7 +42,7 @@ test.describe('Persistence operations @addInit', () => {
|
|||||||
button: 'right'
|
button: 'right'
|
||||||
});
|
});
|
||||||
|
|
||||||
const menuOptions = page.locator('.c-menu ul');
|
const menuOptions = page.locator('.c-menu li');
|
||||||
|
|
||||||
await expect.soft(menuOptions).toContainText(['Open In New Tab', 'View', 'Create Link']);
|
await expect.soft(menuOptions).toContainText(['Open In New Tab', 'View', 'Create Link']);
|
||||||
await expect(menuOptions).not.toContainText(['Move', 'Duplicate', 'Remove', 'Add New Folder', 'Edit Properties...', 'Export as JSON', 'Import from JSON']);
|
await expect(menuOptions).not.toContainText(['Move', 'Duplicate', 'Remove', 'Add New Folder', 'Edit Properties...', 'Export as JSON', 'Import from JSON']);
|
||||||
|
87
e2e/tests/functional/planning/plan.e2e.spec.js
Normal file
87
e2e/tests/functional/planning/plan.e2e.spec.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
const { test, expect } = require('../../../pluginFixtures');
|
||||||
|
const { createPlanFromJSON } = require('../../../appActions');
|
||||||
|
|
||||||
|
const testPlan = {
|
||||||
|
"TEST_GROUP": [
|
||||||
|
{
|
||||||
|
"name": "Past event 1",
|
||||||
|
"start": 1660320408000,
|
||||||
|
"end": 1660343797000,
|
||||||
|
"type": "TEST-GROUP",
|
||||||
|
"color": "orange",
|
||||||
|
"textColor": "white"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Past event 2",
|
||||||
|
"start": 1660406808000,
|
||||||
|
"end": 1660429160000,
|
||||||
|
"type": "TEST-GROUP",
|
||||||
|
"color": "orange",
|
||||||
|
"textColor": "white"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Past event 3",
|
||||||
|
"start": 1660493208000,
|
||||||
|
"end": 1660503981000,
|
||||||
|
"type": "TEST-GROUP",
|
||||||
|
"color": "orange",
|
||||||
|
"textColor": "white"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Past event 4",
|
||||||
|
"start": 1660579608000,
|
||||||
|
"end": 1660624108000,
|
||||||
|
"type": "TEST-GROUP",
|
||||||
|
"color": "orange",
|
||||||
|
"textColor": "white"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Past event 5",
|
||||||
|
"start": 1660666008000,
|
||||||
|
"end": 1660681529000,
|
||||||
|
"type": "TEST-GROUP",
|
||||||
|
"color": "orange",
|
||||||
|
"textColor": "white"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
test.describe("Plan", () => {
|
||||||
|
test("Create a Plan and display all plan events @unstable", async ({ page }) => {
|
||||||
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
const plan = await createPlanFromJSON(page, {
|
||||||
|
name: 'Test Plan',
|
||||||
|
json: testPlan
|
||||||
|
});
|
||||||
|
const startBound = testPlan.TEST_GROUP[0].start;
|
||||||
|
const endBound = testPlan.TEST_GROUP[testPlan.TEST_GROUP.length - 1].end;
|
||||||
|
|
||||||
|
// Switch to fixed time mode with all plan events within the bounds
|
||||||
|
await page.goto(`${plan.url}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=plan.view`);
|
||||||
|
const eventCount = await page.locator('.activity-bounds').count();
|
||||||
|
expect(eventCount).toEqual(testPlan.TEST_GROUP.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
181
e2e/tests/functional/planning/timestrip.e2e.spec.js
Normal file
181
e2e/tests/functional/planning/timestrip.e2e.spec.js
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
const { test, expect } = require('../../../pluginFixtures');
|
||||||
|
const { createDomainObjectWithDefaults, createPlanFromJSON } = require('../../../appActions');
|
||||||
|
|
||||||
|
const testPlan = {
|
||||||
|
"TEST_GROUP": [
|
||||||
|
{
|
||||||
|
"name": "Past event 1",
|
||||||
|
"start": 1660320408000,
|
||||||
|
"end": 1660343797000,
|
||||||
|
"type": "TEST-GROUP",
|
||||||
|
"color": "orange",
|
||||||
|
"textColor": "white"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Past event 2",
|
||||||
|
"start": 1660406808000,
|
||||||
|
"end": 1660429160000,
|
||||||
|
"type": "TEST-GROUP",
|
||||||
|
"color": "orange",
|
||||||
|
"textColor": "white"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Past event 3",
|
||||||
|
"start": 1660493208000,
|
||||||
|
"end": 1660503981000,
|
||||||
|
"type": "TEST-GROUP",
|
||||||
|
"color": "orange",
|
||||||
|
"textColor": "white"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Past event 4",
|
||||||
|
"start": 1660579608000,
|
||||||
|
"end": 1660624108000,
|
||||||
|
"type": "TEST-GROUP",
|
||||||
|
"color": "orange",
|
||||||
|
"textColor": "white"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Past event 5",
|
||||||
|
"start": 1660666008000,
|
||||||
|
"end": 1660681529000,
|
||||||
|
"type": "TEST-GROUP",
|
||||||
|
"color": "orange",
|
||||||
|
"textColor": "white"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
test.describe("Time Strip", () => {
|
||||||
|
test("Create two Time Strips, add a single Plan to both, and verify they can have separate Indepdenent Time Contexts @unstable", async ({ page }) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/5627'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Constant locators
|
||||||
|
const independentTimeConductorInputs = page.locator('.l-shell__main-independent-time-conductor .c-input--datetime');
|
||||||
|
const activityBounds = page.locator('.activity-bounds');
|
||||||
|
|
||||||
|
// Goto baseURL
|
||||||
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
const timestrip = await test.step("Create a Time Strip", async () => {
|
||||||
|
const createdTimeStrip = await createDomainObjectWithDefaults(page, { type: 'Time Strip' });
|
||||||
|
const objectName = await page.locator('.l-browse-bar__object-name').innerText();
|
||||||
|
expect(objectName).toBe(createdTimeStrip.name);
|
||||||
|
|
||||||
|
return createdTimeStrip;
|
||||||
|
});
|
||||||
|
|
||||||
|
const plan = await test.step("Create a Plan and add it to the timestrip", async () => {
|
||||||
|
const createdPlan = await createPlanFromJSON(page, {
|
||||||
|
name: 'Test Plan',
|
||||||
|
json: testPlan
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto(timestrip.url);
|
||||||
|
// Expand the tree to show the plan
|
||||||
|
await page.click("button[title='Show selected item in tree']");
|
||||||
|
await page.dragAndDrop(`role=treeitem[name=/${createdPlan.name}/]`, '.c-object-view');
|
||||||
|
await page.click("button[title='Save']");
|
||||||
|
await page.click("li[title='Save and Finish Editing']");
|
||||||
|
const startBound = testPlan.TEST_GROUP[0].start;
|
||||||
|
const endBound = testPlan.TEST_GROUP[testPlan.TEST_GROUP.length - 1].end;
|
||||||
|
|
||||||
|
// Switch to fixed time mode with all plan events within the bounds
|
||||||
|
await page.goto(`${timestrip.url}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=time-strip.view`);
|
||||||
|
|
||||||
|
// Verify all events are displayed
|
||||||
|
const eventCount = await page.locator('.activity-bounds').count();
|
||||||
|
expect(eventCount).toEqual(testPlan.TEST_GROUP.length);
|
||||||
|
|
||||||
|
return createdPlan;
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step("TimeStrip can use the Independent Time Conductor", async () => {
|
||||||
|
// Activate Independent Time Conductor in Fixed Time Mode
|
||||||
|
await page.click('.c-toggle-switch__slider');
|
||||||
|
expect(await activityBounds.count()).toEqual(0);
|
||||||
|
|
||||||
|
// Set the independent time bounds so that only one event is shown
|
||||||
|
const startBound = testPlan.TEST_GROUP[0].start;
|
||||||
|
const endBound = testPlan.TEST_GROUP[0].end;
|
||||||
|
const startBoundString = new Date(startBound).toISOString().replace('T', ' ');
|
||||||
|
const endBoundString = new Date(endBound).toISOString().replace('T', ' ');
|
||||||
|
|
||||||
|
await independentTimeConductorInputs.nth(0).fill('');
|
||||||
|
await independentTimeConductorInputs.nth(0).fill(startBoundString);
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await independentTimeConductorInputs.nth(1).fill('');
|
||||||
|
await independentTimeConductorInputs.nth(1).fill(endBoundString);
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
expect(await activityBounds.count()).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step("Can have multiple TimeStrips with the same plan linked and different Independent Time Contexts", async () => {
|
||||||
|
// Create another Time Strip and verify that it has been created
|
||||||
|
const createdTimeStrip = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Time Strip',
|
||||||
|
name: "Another Time Strip"
|
||||||
|
});
|
||||||
|
|
||||||
|
const objectName = await page.locator('.l-browse-bar__object-name').innerText();
|
||||||
|
expect(objectName).toBe(createdTimeStrip.name);
|
||||||
|
|
||||||
|
// Drag the existing Plan onto the newly created Time Strip, and save.
|
||||||
|
await page.dragAndDrop(`role=treeitem[name=/${plan.name}/]`, '.c-object-view');
|
||||||
|
await page.click("button[title='Save']");
|
||||||
|
await page.click("li[title='Save and Finish Editing']");
|
||||||
|
|
||||||
|
// Activate Independent Time Conductor in Fixed Time Mode
|
||||||
|
await page.click('.c-toggle-switch__slider');
|
||||||
|
|
||||||
|
// All events should be displayed at this point because the
|
||||||
|
// initial independent context bounds will match the global bounds
|
||||||
|
expect(await activityBounds.count()).toEqual(5);
|
||||||
|
|
||||||
|
// Set the independent time bounds so that two events are shown
|
||||||
|
const startBound = testPlan.TEST_GROUP[0].start;
|
||||||
|
const endBound = testPlan.TEST_GROUP[1].end;
|
||||||
|
const startBoundString = new Date(startBound).toISOString().replace('T', ' ');
|
||||||
|
const endBoundString = new Date(endBound).toISOString().replace('T', ' ');
|
||||||
|
|
||||||
|
await independentTimeConductorInputs.nth(0).fill('');
|
||||||
|
await independentTimeConductorInputs.nth(0).fill(startBoundString);
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await independentTimeConductorInputs.nth(1).fill('');
|
||||||
|
await independentTimeConductorInputs.nth(1).fill(endBoundString);
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
|
||||||
|
// Verify that two events are displayed
|
||||||
|
expect(await activityBounds.count()).toEqual(2);
|
||||||
|
|
||||||
|
// Switch to the previous Time Strip and verify that only one event is displayed
|
||||||
|
await page.goto(timestrip.url);
|
||||||
|
expect(await activityBounds.count()).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -28,14 +28,14 @@ test.describe('The Fault Management Plugin using example faults', () => {
|
|||||||
await utils.navigateToFaultManagementWithExample(page);
|
await utils.navigateToFaultManagementWithExample(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Shows a criticality icon for every fault', async ({ page }) => {
|
test('Shows a criticality icon for every fault @unstable', async ({ page }) => {
|
||||||
const faultCount = await page.locator('c-fault-mgmt__list').count();
|
const faultCount = await page.locator('c-fault-mgmt__list').count();
|
||||||
const criticalityIconCount = await page.locator('c-fault-mgmt__list-severity').count();
|
const criticalityIconCount = await page.locator('c-fault-mgmt__list-severity').count();
|
||||||
|
|
||||||
expect.soft(faultCount).toEqual(criticalityIconCount);
|
expect.soft(faultCount).toEqual(criticalityIconCount);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('When selecting a fault, it has an "is-selected" class and it\'s information shows in the inspector', async ({ page }) => {
|
test('When selecting a fault, it has an "is-selected" class and it\'s information shows in the inspector @unstable', async ({ page }) => {
|
||||||
await utils.selectFaultItem(page, 1);
|
await utils.selectFaultItem(page, 1);
|
||||||
|
|
||||||
const selectedFaultName = await page.locator('.c-fault-mgmt__list.is-selected .c-fault-mgmt__list-faultname').textContent();
|
const selectedFaultName = await page.locator('.c-fault-mgmt__list.is-selected .c-fault-mgmt__list-faultname').textContent();
|
||||||
@ -45,7 +45,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
|
|||||||
expect.soft(inspectorFaultNameCount).toEqual(1);
|
expect.soft(inspectorFaultNameCount).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('When selecting multiple faults, no specific fault information is shown in the inspector', async ({ page }) => {
|
test('When selecting multiple faults, no specific fault information is shown in the inspector @unstable', async ({ page }) => {
|
||||||
await utils.selectFaultItem(page, 1);
|
await utils.selectFaultItem(page, 1);
|
||||||
await utils.selectFaultItem(page, 2);
|
await utils.selectFaultItem(page, 2);
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
|
|||||||
expect.soft(secondNameInInspectorCount).toEqual(0);
|
expect.soft(secondNameInInspectorCount).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Allows you to shelve a fault', async ({ page }) => {
|
test('Allows you to shelve a fault @unstable', async ({ page }) => {
|
||||||
const shelvedFaultName = await utils.getFaultName(page, 2);
|
const shelvedFaultName = await utils.getFaultName(page, 2);
|
||||||
const beforeShelvedFault = utils.getFaultByName(page, shelvedFaultName);
|
const beforeShelvedFault = utils.getFaultByName(page, shelvedFaultName);
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
|
|||||||
expect.soft(await shelvedViewFault.count()).toBe(1);
|
expect.soft(await shelvedViewFault.count()).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Allows you to acknowledge a fault', async ({ page }) => {
|
test('Allows you to acknowledge a fault @unstable', async ({ page }) => {
|
||||||
const acknowledgedFaultName = await utils.getFaultName(page, 3);
|
const acknowledgedFaultName = await utils.getFaultName(page, 3);
|
||||||
|
|
||||||
await utils.acknowledgeFault(page, 3);
|
await utils.acknowledgeFault(page, 3);
|
||||||
@ -94,7 +94,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
|
|||||||
expect.soft(acknowledgedFaultName).toEqual(acknowledgedViewFaultName);
|
expect.soft(acknowledgedFaultName).toEqual(acknowledgedViewFaultName);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Allows you to shelve multiple faults', async ({ page }) => {
|
test('Allows you to shelve multiple faults @unstable', async ({ page }) => {
|
||||||
const shelvedFaultNameOne = await utils.getFaultName(page, 1);
|
const shelvedFaultNameOne = await utils.getFaultName(page, 1);
|
||||||
const shelvedFaultNameFour = await utils.getFaultName(page, 4);
|
const shelvedFaultNameFour = await utils.getFaultName(page, 4);
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
|
|||||||
expect.soft(await shelvedViewFaultFour.count()).toBe(1);
|
expect.soft(await shelvedViewFaultFour.count()).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Allows you to acknowledge multiple faults', async ({ page }) => {
|
test('Allows you to acknowledge multiple faults @unstable', async ({ page }) => {
|
||||||
const acknowledgedFaultNameTwo = await utils.getFaultName(page, 2);
|
const acknowledgedFaultNameTwo = await utils.getFaultName(page, 2);
|
||||||
const acknowledgedFaultNameFive = await utils.getFaultName(page, 5);
|
const acknowledgedFaultNameFive = await utils.getFaultName(page, 5);
|
||||||
|
|
||||||
@ -143,7 +143,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
|
|||||||
expect.soft(await acknowledgedViewFaultFive.count()).toBe(1);
|
expect.soft(await acknowledgedViewFaultFive.count()).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Allows you to search faults', async ({ page }) => {
|
test('Allows you to search faults @unstable', async ({ page }) => {
|
||||||
const faultThreeNamespace = await utils.getFaultNamespace(page, 3);
|
const faultThreeNamespace = await utils.getFaultNamespace(page, 3);
|
||||||
const faultTwoName = await utils.getFaultName(page, 2);
|
const faultTwoName = await utils.getFaultName(page, 2);
|
||||||
const faultFiveTriggerTime = await utils.getFaultTriggerTime(page, 5);
|
const faultFiveTriggerTime = await utils.getFaultTriggerTime(page, 5);
|
||||||
@ -184,7 +184,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
|
|||||||
expect.soft(await utils.getFaultTriggerTime(page, 1)).toEqual(faultFiveTriggerTime);
|
expect.soft(await utils.getFaultTriggerTime(page, 1)).toEqual(faultFiveTriggerTime);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Allows you to sort faults', async ({ page }) => {
|
test('Allows you to sort faults @unstable', async ({ page }) => {
|
||||||
const highestSeverity = await utils.getHighestSeverity(page);
|
const highestSeverity = await utils.getHighestSeverity(page);
|
||||||
const lowestSeverity = await utils.getLowestSeverity(page);
|
const lowestSeverity = await utils.getLowestSeverity(page);
|
||||||
const faultOneName = 'Example Fault 1';
|
const faultOneName = 'Example Fault 1';
|
||||||
@ -213,7 +213,7 @@ test.describe('The Fault Management Plugin without using example faults', () =>
|
|||||||
await utils.navigateToFaultManagementWithoutExample(page);
|
await utils.navigateToFaultManagementWithoutExample(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Shows no faults when no faults are provided', async ({ page }) => {
|
test('Shows no faults when no faults are provided @unstable', async ({ page }) => {
|
||||||
const faultCount = await page.locator('c-fault-mgmt__list').count();
|
const faultCount = await page.locator('c-fault-mgmt__list').count();
|
||||||
|
|
||||||
expect.soft(faultCount).toEqual(0);
|
expect.soft(faultCount).toEqual(0);
|
||||||
@ -227,7 +227,7 @@ test.describe('The Fault Management Plugin without using example faults', () =>
|
|||||||
expect.soft(shelvedCount).toEqual(0);
|
expect.soft(shelvedCount).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Will return no faults when searching', async ({ page }) => {
|
test('Will return no faults when searching @unstable', async ({ page }) => {
|
||||||
await utils.enterSearchTerm(page, 'fault');
|
await utils.enterSearchTerm(page, 'fault');
|
||||||
|
|
||||||
const faultCount = await page.locator('c-fault-mgmt__list').count();
|
const faultCount = await page.locator('c-fault-mgmt__list').count();
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"@braintree/sanitize-url": "6.0.0",
|
"@braintree/sanitize-url": "6.0.0",
|
||||||
"@percy/cli": "1.7.2",
|
"@percy/cli": "1.7.2",
|
||||||
"@percy/playwright": "1.0.4",
|
"@percy/playwright": "1.0.4",
|
||||||
"@playwright/test": "1.23.0",
|
"@playwright/test": "1.25.0",
|
||||||
"@types/eventemitter3": "^1.0.0",
|
"@types/eventemitter3": "^1.0.0",
|
||||||
"@types/jasmine": "^4.0.1",
|
"@types/jasmine": "^4.0.1",
|
||||||
"@types/karma": "^6.3.2",
|
"@types/karma": "^6.3.2",
|
||||||
|
@ -171,27 +171,38 @@ class TimeAPI extends GlobalTimeContext {
|
|||||||
* @memberof module:openmct.TimeAPI#
|
* @memberof module:openmct.TimeAPI#
|
||||||
* @method getContextForView
|
* @method getContextForView
|
||||||
*/
|
*/
|
||||||
getContextForView(objectPath = []) {
|
getContextForView(objectPath) {
|
||||||
|
if (!objectPath || !Array.isArray(objectPath)) {
|
||||||
|
throw new Error('No objectPath provided');
|
||||||
|
}
|
||||||
|
|
||||||
const viewKey = objectPath.length && this.openmct.objects.makeKeyString(objectPath[0].identifier);
|
const viewKey = objectPath.length && this.openmct.objects.makeKeyString(objectPath[0].identifier);
|
||||||
|
|
||||||
if (viewKey) {
|
if (!viewKey) {
|
||||||
let viewTimeContext = this.getIndependentContext(viewKey);
|
// Return the global time context
|
||||||
if (viewTimeContext) {
|
|
||||||
this.independentContexts.delete(viewKey);
|
|
||||||
} else {
|
|
||||||
viewTimeContext = new IndependentTimeContext(this.openmct, this, objectPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// return a new IndependentContext in case the objectPath is different
|
|
||||||
this.independentContexts.set(viewKey, viewTimeContext);
|
|
||||||
|
|
||||||
return viewTimeContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
// always follow the global time context
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let viewTimeContext = this.getIndependentContext(viewKey);
|
||||||
|
if (!viewTimeContext) {
|
||||||
|
// If the context doesn't exist yet, create it.
|
||||||
|
viewTimeContext = new IndependentTimeContext(this.openmct, this, objectPath);
|
||||||
|
this.independentContexts.set(viewKey, viewTimeContext);
|
||||||
|
} else {
|
||||||
|
// If it already exists, compare the objectPath to see if it needs to be updated.
|
||||||
|
const currentPath = this.openmct.objects.getRelativePath(viewTimeContext.objectPath);
|
||||||
|
const newPath = this.openmct.objects.getRelativePath(objectPath);
|
||||||
|
|
||||||
|
if (currentPath !== newPath) {
|
||||||
|
// If the path has changed, update the context.
|
||||||
|
this.independentContexts.delete(viewKey);
|
||||||
|
viewTimeContext = new IndependentTimeContext(this.openmct, this, objectPath);
|
||||||
|
this.independentContexts.set(viewKey, viewTimeContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewTimeContext;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TimeAPI;
|
export default TimeAPI;
|
||||||
|
@ -20,17 +20,15 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
function remoteClockRequestInterceptor(openmct, remoteClockIdentifier, waitForBounds) {
|
function remoteClockRequestInterceptor(openmct, _remoteClockIdentifier, waitForBounds) {
|
||||||
let remoteClockLoaded = false;
|
let remoteClockLoaded = false;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
appliesTo: () => {
|
appliesTo: () => {
|
||||||
// Get the activeClock from the Global Time Context
|
// Get the activeClock from the Global Time Context
|
||||||
const { activeClock } = openmct.time.getContextForView();
|
const { activeClock } = openmct.time;
|
||||||
|
|
||||||
return activeClock !== undefined
|
return activeClock?.key === 'remote-clock' && !remoteClockLoaded;
|
||||||
&& activeClock.key === 'remote-clock'
|
|
||||||
&& !remoteClockLoaded;
|
|
||||||
},
|
},
|
||||||
invoke: async (request) => {
|
invoke: async (request) => {
|
||||||
const { start, end } = await waitForBounds();
|
const { start, end } = await waitForBounds();
|
||||||
|
@ -78,6 +78,12 @@ export default {
|
|||||||
default() {
|
default() {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
objectPath: {
|
||||||
|
type: Array,
|
||||||
|
default() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -127,7 +133,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
setTimeContext() {
|
setTimeContext() {
|
||||||
this.stopFollowingTimeContext();
|
this.stopFollowingTimeContext();
|
||||||
this.timeContext = this.openmct.time.getContextForView(this.keyString ? [{identifier: this.keyString}] : []);
|
this.timeContext = this.openmct.time.getContextForView(this.keyString ? this.objectPath : []);
|
||||||
|
|
||||||
this.handleNewBounds(this.timeContext.bounds());
|
this.handleNewBounds(this.timeContext.bounds());
|
||||||
this.timeContext.on('bounds', this.handleNewBounds);
|
this.timeContext.on('bounds', this.handleNewBounds);
|
||||||
|
@ -90,6 +90,12 @@ export default {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
objectPath: {
|
||||||
|
type: Array,
|
||||||
|
default() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
inputBounds: {
|
inputBounds: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default() {
|
default() {
|
||||||
@ -162,7 +168,7 @@ export default {
|
|||||||
},
|
},
|
||||||
setTimeContext() {
|
setTimeContext() {
|
||||||
this.stopFollowingTime();
|
this.stopFollowingTime();
|
||||||
this.timeContext = this.openmct.time.getContextForView(this.keyString ? [{identifier: this.keyString}] : []);
|
this.timeContext = this.openmct.time.getContextForView(this.keyString ? this.objectPath : []);
|
||||||
this.followTime();
|
this.followTime();
|
||||||
},
|
},
|
||||||
handleNewBounds(bounds) {
|
handleNewBounds(bounds) {
|
||||||
|
@ -52,12 +52,14 @@
|
|||||||
<conductor-inputs-fixed
|
<conductor-inputs-fixed
|
||||||
v-if="isFixed"
|
v-if="isFixed"
|
||||||
:key-string="domainObject.identifier.key"
|
:key-string="domainObject.identifier.key"
|
||||||
|
:object-path="objectPath"
|
||||||
@updated="saveFixedOffsets"
|
@updated="saveFixedOffsets"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<conductor-inputs-realtime
|
<conductor-inputs-realtime
|
||||||
v-else
|
v-else
|
||||||
:key-string="domainObject.identifier.key"
|
:key-string="domainObject.identifier.key"
|
||||||
|
:object-path="objectPath"
|
||||||
@updated="saveClockOffsets"
|
@updated="saveClockOffsets"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -85,6 +87,10 @@ export default {
|
|||||||
domainObject: {
|
domainObject: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
objectPath: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -164,7 +170,7 @@ export default {
|
|||||||
},
|
},
|
||||||
setTimeContext() {
|
setTimeContext() {
|
||||||
this.stopFollowingTimeContext();
|
this.stopFollowingTimeContext();
|
||||||
this.timeContext = this.openmct.time.getContextForView([this.domainObject]);
|
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||||
this.timeContext.on('clock', this.setTimeOptions);
|
this.timeContext.on('clock', this.setTimeOptions);
|
||||||
},
|
},
|
||||||
stopFollowingTimeContext() {
|
stopFollowingTimeContext() {
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
>
|
>
|
||||||
<independent-time-conductor
|
<independent-time-conductor
|
||||||
:domain-object="domainObject"
|
:domain-object="domainObject"
|
||||||
|
:object-path="path"
|
||||||
@stateChanged="updateIndependentTimeState"
|
@stateChanged="updateIndependentTimeState"
|
||||||
@updated="saveTimeOptions"
|
@updated="saveTimeOptions"
|
||||||
/>
|
/>
|
||||||
@ -67,6 +68,9 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
path() {
|
||||||
|
return this.domainObject && (this.currentObjectPath || this.objectPath);
|
||||||
|
},
|
||||||
objectFontStyle() {
|
objectFontStyle() {
|
||||||
return this.domainObject && this.domainObject.configuration && this.domainObject.configuration.fontStyle;
|
return this.domainObject && this.domainObject.configuration && this.domainObject.configuration.fontStyle;
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user