Compare commits

..

3 Commits

Author SHA1 Message Date
Charles Hacskaylo
32e85ebb63 Closes #5640
- Center Dial Gauge in its container.
2022-08-11 15:51:22 -07:00
Vitor Henckel
10ff4e1781 fix: removing maelstrom theme from application (#5600) 2022-08-09 13:06:43 -07:00
Michael Rogers
f1c85933c3 Imagery thumbnail regression fixes - 5327 (#5569)
* Add an active class to thumbnail to indicate current focused image

* Differentiate bg color between real-time and fixed

* scrollIntoView inline: center

* Added watcher for bounds change to trigger thumbnail scroll

* Resolve merge conflict with requestHistory change to telemetry collection

* Split thumbnail into sub component

* Monitor isFixed value to unpause playback status
2022-08-02 12:29:49 -05:00
38 changed files with 401 additions and 1141 deletions

View File

@@ -30,37 +30,18 @@
*/
/**
* Defines parameters to be used in the creation of a domain object.
* @typedef {Object} CreateObjectOptions
* @property {string} type the type of domain object to create (e.g.: "Sine Wave Generator").
* @property {string} [name] the desired name of the created domain object.
* @property {string | import('../src/api/objects/ObjectAPI').Identifier} [parent] the Identifier or uuid of the parent object.
*/
/**
* Contains information about the newly created domain object.
* @typedef {Object} CreatedObjectInfo
* @property {string} name the name of the created object
* @property {string} uuid the uuid of the created object
* @property {string} url the relative url to the object (for use with `page.goto()`)
*/
/**
* This common function creates a domain object with the default options. It is the preferred way of creating objects
* This common function creates a `domainObject` with default options. It is the preferred way of creating objects
* in the e2e suite when uninterested in properties of the objects themselves.
*
* @param {import('@playwright/test').Page} page
* @param {CreateObjectOptions} options
* @returns {Promise<CreatedObjectInfo>} An object containing information about the newly created domain object.
* @param {string} type
* @param {string | undefined} name
*/
async function createDomainObjectWithDefaults(page, { type, name, 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`);
async function createDomainObjectWithDefaults(page, type, name) {
// Navigate to focus the 'My Items' folder, and hide the object tree
// This is necessary so that subsequent objects can be created without a parent
// TODO: Ideally this would navigate to a common `e2e` folder
await page.goto('./#/browse/mine?hideTree=true');
await page.waitForLoadState('networkidle');
//Click the Create button
await page.click('button:has-text("Create")');
@@ -69,7 +50,7 @@ async function createDomainObjectWithDefaults(page, { type, name, parent = 'mine
// 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"]');
const nameInput = page.locator('input[type="text"]').nth(2);
await nameInput.fill("");
await nameInput.fill(name);
}
@@ -82,28 +63,12 @@ async function createDomainObjectWithDefaults(page, { type, name, parent = 'mine
page.waitForSelector('.c-message-banner__message')
]);
// Wait until the URL is updated
await page.waitForURL(`**/${parent}/*`);
const uuid = await getFocusedObjectUuid(page);
const objectUrl = await getHashUrlToDomainObject(page, uuid);
if (await _isInEditMode(page, uuid)) {
// Save (exit edit mode)
await page.locator('button[title="Save"]').click();
await page.locator('li[title="Save and Finish Editing"]').click();
}
return {
name: name || `Unnamed ${type}`,
uuid: uuid,
url: objectUrl
};
return name || `Unnamed ${type}`;
}
/**
* Open the given `domainObject`'s context menu from the object tree.
* Expands the 'My Items' folder if it is not already expanded.
*
* @param {import('@playwright/test').Page} page
* @param {string} myItemsFolderName the name of the "My Items" folder
* @param {string} domainObjectName the display name of the `domainObject`
@@ -120,154 +85,8 @@ async function openObjectTreeContextMenu(page, myItemsFolderName, domainObjectNa
});
}
/**
* Gets the UUID of the currently focused object by parsing the current URL
* and returning the last UUID in the path.
* @param {import('@playwright/test').Page} page
* @returns {Promise<string>} the uuid of the focused object
*/
async function getFocusedObjectUuid(page) {
const UUIDv4Regexp = /[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/gi;
const focusedObjectUuid = await page.evaluate((regexp) => {
return window.location.href.match(regexp).at(-1);
}, UUIDv4Regexp);
return focusedObjectUuid;
}
/**
* Returns the hashUrl to the domainObject given its uuid.
* Useful for directly navigating to the given domainObject.
*
* URLs returned will be of the form `'./browse/#/mine/<uuid0>/<uuid1>/...'`
*
* @param {import('@playwright/test').Page} page
* @param {string} uuid the uuid of the object to get the url for
* @returns {Promise<string>} the url of the object
*/
async function getHashUrlToDomainObject(page, uuid) {
const hashUrl = await page.evaluate(async (objectUuid) => {
const path = await window.openmct.objects.getOriginalPath(objectUuid);
let url = './#/browse/' + [...path].reverse()
.map((object) => window.openmct.objects.makeKeyString(object.identifier))
.join('/');
// Drop the vestigial '/ROOT' if it exists
if (url.includes('/ROOT')) {
url = url.split('/ROOT').join('');
}
return url;
}, uuid);
return hashUrl;
}
/**
* Utilizes the OpenMCT API to detect if the given object has an active transaction (is in Edit mode).
* @private
* @param {import('@playwright/test').Page} page
* @param {string | import('../src/api/objects/ObjectAPI').Identifier} identifier
* @return {Promise<boolean>} true if the object has an active transaction, false otherwise
*/
async function _isInEditMode(page, identifier) {
// eslint-disable-next-line no-return-await
return await page.evaluate((objectIdentifier) => window.openmct.objects.isTransactionActive(objectIdentifier), identifier);
}
/**
* Set the time conductor mode to either fixed timespan or realtime mode.
* @param {import('@playwright/test').Page} page
* @param {boolean} [isFixedTimespan=true] true for fixed timespan mode, false for realtime mode; default is true
*/
async function setTimeConductorMode(page, isFixedTimespan = true) {
// Click 'mode' button
await page.locator('.c-mode-button').click();
// Switch time conductor mode
if (isFixedTimespan) {
await page.locator('data-testid=conductor-modeOption-fixed').click();
} else {
await page.locator('data-testid=conductor-modeOption-realtime').click();
}
}
/**
* Set the time conductor to fixed timespan mode
* @param {import('@playwright/test').Page} page
*/
async function setFixedTimeMode(page) {
await setTimeConductorMode(page, true);
}
/**
* Set the time conductor to realtime mode
* @param {import('@playwright/test').Page} page
*/
async function setRealTimeMode(page) {
await setTimeConductorMode(page, false);
}
/**
* @typedef {Object} OffsetValues
* @property {string | undefined} hours
* @property {string | undefined} mins
* @property {string | undefined} secs
*/
/**
* Set the values (hours, mins, secs) for the TimeConductor offsets when in realtime mode
* @param {import('@playwright/test').Page} page
* @param {OffsetValues} offset
* @param {import('@playwright/test').Locator} offsetButton
*/
async function setTimeConductorOffset(page, {hours, mins, secs}, offsetButton) {
await offsetButton.click();
if (hours) {
await page.fill('.pr-time-controls__hrs', hours);
}
if (mins) {
await page.fill('.pr-time-controls__mins', mins);
}
if (secs) {
await page.fill('.pr-time-controls__secs', secs);
}
// Click the check button
await page.locator('.pr-time__buttons .icon-check').click();
}
/**
* Set the values (hours, mins, secs) for the start time offset when in realtime mode
* @param {import('@playwright/test').Page} page
* @param {OffsetValues} offset
*/
async function setStartOffset(page, offset) {
const startOffsetButton = page.locator('data-testid=conductor-start-offset-button');
await setTimeConductorOffset(page, offset, startOffsetButton);
}
/**
* Set the values (hours, mins, secs) for the end time offset when in realtime mode
* @param {import('@playwright/test').Page} page
* @param {OffsetValues} offset
*/
async function setEndOffset(page, offset) {
const endOffsetButton = page.locator('data-testid=conductor-end-offset-button');
await setTimeConductorOffset(page, offset, endOffsetButton);
}
// eslint-disable-next-line no-undef
module.exports = {
createDomainObjectWithDefaults,
openObjectTreeContextMenu,
getHashUrlToDomainObject,
getFocusedObjectUuid,
setFixedTimeMode,
setRealTimeMode,
setStartOffset,
setEndOffset
openObjectTreeContextMenu
};

View File

@@ -23,66 +23,19 @@
const { test, expect } = require('../../baseFixtures.js');
const { createDomainObjectWithDefaults } = require('../../appActions.js');
test.describe('AppActions', () => {
test('createDomainObjectsWithDefaults', async ({ page }) => {
test.describe('appActions tests', () => {
test('createDomainObjectsWithDefaults can create multiple objects in a row', async ({ page }) => {
await page.goto('./', { waitUntil: 'networkidle' });
await createDomainObjectWithDefaults(page, 'Timer', 'Timer Foo');
await createDomainObjectWithDefaults(page, 'Timer', 'Timer Bar');
await createDomainObjectWithDefaults(page, 'Timer', 'Timer Baz');
const e2eFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'e2e folder'
});
// Expand the tree
await page.click('.c-disclosure-triangle');
await test.step('Create multiple flat objects in a row', async () => {
const timer1 = await createDomainObjectWithDefaults(page, {
type: 'Timer',
name: 'Timer Foo',
parent: e2eFolder.uuid
});
const timer2 = await createDomainObjectWithDefaults(page, {
type: 'Timer',
name: 'Timer Bar',
parent: e2eFolder.uuid
});
const timer3 = await createDomainObjectWithDefaults(page, {
type: 'Timer',
name: 'Timer Baz',
parent: e2eFolder.uuid
});
await page.goto(timer1.url, { waitUntil: 'networkidle' });
await expect(page.locator('.l-browse-bar__object-name')).toHaveText('Timer Foo');
await page.goto(timer2.url, { waitUntil: 'networkidle' });
await expect(page.locator('.l-browse-bar__object-name')).toHaveText('Timer Bar');
await page.goto(timer3.url, { waitUntil: 'networkidle' });
await expect(page.locator('.l-browse-bar__object-name')).toHaveText('Timer Baz');
});
await test.step('Create multiple nested objects in a row', async () => {
const folder1 = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Folder Foo',
parent: e2eFolder.uuid
});
const folder2 = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Folder Bar',
parent: folder1.uuid
});
const folder3 = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Folder Baz',
parent: folder2.uuid
});
await page.goto(folder1.url, { waitUntil: 'networkidle' });
await expect(page.locator('.l-browse-bar__object-name')).toHaveText('Folder Foo');
await page.goto(folder2.url, { waitUntil: 'networkidle' });
await expect(page.locator('.l-browse-bar__object-name')).toHaveText('Folder Bar');
await page.goto(folder3.url, { waitUntil: 'networkidle' });
await expect(page.locator('.l-browse-bar__object-name')).toHaveText('Folder Baz');
expect(folder1.url).toBe(`${e2eFolder.url}/${folder1.uuid}`);
expect(folder2.url).toBe(`${e2eFolder.url}/${folder1.uuid}/${folder2.uuid}`);
expect(folder3.url).toBe(`${e2eFolder.url}/${folder1.uuid}/${folder2.uuid}/${folder3.uuid}`);
});
// Verify the objects were created
await expect(page.locator('a :text("Timer Foo")')).toBeVisible();
await expect(page.locator('a :text("Timer Bar")')).toBeVisible();
await expect(page.locator('a :text("Timer Baz")')).toBeVisible();
});
});

View File

@@ -58,7 +58,7 @@ test.describe('Renaming Timer Object', () => {
//Open a browser, navigate to the main page, and wait until all networkevents to resolve
await page.goto('./', { waitUntil: 'networkidle' });
//We provide some helper functions in appActions like createDomainObjectWithDefaults. This example will create a Timer object
await createDomainObjectWithDefaults(page, { type: 'Timer' });
await createDomainObjectWithDefaults(page, 'Timer');
//Assert the object to be created and check it's name in the title
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Timer');
@@ -73,7 +73,7 @@ test.describe('Renaming Timer Object', () => {
//Open a browser, navigate to the main page, and wait until all networkevents to resolve
await page.goto('./', { waitUntil: 'networkidle' });
//We provide some helper functions in appActions like createDomainObjectWithDefaults. This example will create a Timer object
await createDomainObjectWithDefaults(page, { type: 'Timer' });
await createDomainObjectWithDefaults(page, 'Timer');
//Expect the object to be created and check it's name in the title
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Timer');

View File

@@ -31,13 +31,29 @@ TODO: Provide additional validation of object properties as it grows.
*/
const { createDomainObjectWithDefaults } = require('../../appActions.js');
const { test, expect } = require('../../pluginFixtures.js');
test('Generate Visual Test Data @localStorage', async ({ page, context }) => {
test('Generate Visual Test Data @localStorage', async ({ page, context, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
//Go to baseURL
await page.goto('./', { waitUntil: 'networkidle' });
const overlayPlot = await createDomainObjectWithDefaults(page, { type: 'Overlay Plot' });
await page.locator('button:has-text("Create")').click();
// add overlay plot with defaults
await page.locator('li:has-text("Overlay Plot")').click();
await Promise.all([
page.waitForNavigation(),
page.locator('text=OK').click(),
//Wait for Save Banner to appear1
page.waitForSelector('.c-message-banner__message')
]);
// save (exit edit mode)
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
await page.locator('text=Save and Finish Editing').click();
// click create button
await page.locator('button:has-text("Create")').click();
@@ -51,12 +67,16 @@ test('Generate Visual Test Data @localStorage', async ({ page, context }) => {
await Promise.all([
page.waitForNavigation(),
page.locator('text=OK').click(),
//Wait for Save Banner to appear
//Wait for Save Banner to appear1
page.waitForSelector('.c-message-banner__message')
]);
// focus the overlay plot
await page.goto(overlayPlot.url);
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
await Promise.all([
page.waitForNavigation(),
page.locator('text=Unnamed Overlay Plot').first().click()
]);
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Overlay Plot');
//Save localStorage for future test execution

View File

@@ -35,10 +35,7 @@ test.describe('Example Event Generator CRUD Operations', () => {
//Create a name for the object
const newObjectName = 'Test Event Generator';
await createDomainObjectWithDefaults(page, {
type: 'Event Message Generator',
name: newObjectName
});
await createDomainObjectWithDefaults(page, 'Event Message Generator', newObjectName);
//Assertions against newly created object which define standard behavior
await expect(page.waitForURL(/.*&view=table/)).toBeTruthy();

View File

@@ -1,212 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*
This test suite is dedicated to tests which verify the basic operations surrounding moving & linking objects.
*/
const { test, expect } = require('../../pluginFixtures');
const { createDomainObjectWithDefaults } = require('../../appActions');
test.describe('Move & link item tests', () => {
test('Create a basic object and verify that it can be moved to another folder', async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
// Go to Open MCT
await page.goto('./');
const parentFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Parent Folder'
});
const childFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Child Folder',
parent: parentFolder.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Grandchild Folder',
parent: childFolder.uuid
});
// Attempt to move parent to its own grandparent
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
await page.locator('.c-disclosure-triangle >> nth=0').click();
await page.locator(`a:has-text("Parent Folder") >> nth=0`).click({
button: 'right'
});
await page.locator('li.icon-move').click();
await page.locator('form[name="mctForm"] >> .c-disclosure-triangle >> nth=0').click();
await page.locator('form[name="mctForm"] >> text=Parent Folder').click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
await page.locator('form[name="mctForm"] >> .c-disclosure-triangle >> nth=1').click();
await page.locator('form[name="mctForm"] >> text=Child Folder').click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
await page.locator('form[name="mctForm"] >> .c-disclosure-triangle >> nth=2').click();
await page.locator('form[name="mctForm"] >> text=Grandchild Folder').click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
await page.locator('form[name="mctForm"] >> text=Parent Folder').click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
await page.locator('[aria-label="Cancel"]').click();
// Move Child Folder from Parent Folder to My Items
await page.locator('.c-disclosure-triangle >> nth=0').click();
await page.locator('.c-disclosure-triangle >> nth=1').click();
await page.locator(`a:has-text("Child Folder") >> nth=0`).click({
button: 'right'
});
await page.locator('li.icon-move').click();
await page.locator(`form[name="mctForm"] >> text=${myItemsFolderName}`).click();
await page.locator('text=OK').click();
// Expect that Child Folder is in My Items, the root folder
expect(page.locator(`text=${myItemsFolderName} >> nth=0:has(text=Child Folder)`)).toBeTruthy();
});
test('Create a basic object and verify that it cannot be moved to telemetry object without Composition Provider', async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
// Go to Open MCT
await page.goto('./');
// Create Telemetry Table
let telemetryTable = 'Test Telemetry Table';
await page.locator('button:has-text("Create")').click();
await page.locator('li:has-text("Telemetry Table")').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(telemetryTable);
await page.locator('text=OK').click();
// Finish editing and save Telemetry Table
await page.locator('.c-button--menu.c-button--major.icon-save').click();
await page.locator('text=Save and Finish Editing').click();
// Create New Folder Basic Domain Object
let folder = 'Test Folder';
await page.locator('button:has-text("Create")').click();
await page.locator('li:has-text("Folder")').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder);
// See if it's possible to put the folder in the Telemetry object during creation (Soft Assert)
await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click();
let okButton = await page.locator('button.c-button.c-button--major:has-text("OK")');
let okButtonStateDisabled = await okButton.isDisabled();
expect.soft(okButtonStateDisabled).toBeTruthy();
// Continue test regardless of assertion and create it in My Items
await page.locator(`form[name="mctForm"] >> text=${myItemsFolderName}`).click();
await page.locator('text=OK').click();
// Open My Items
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
// Select Folder Object and select Move from context menu
await Promise.all([
page.waitForNavigation(),
page.locator(`a:has-text("${folder}")`).click()
]);
await page.locator('.c-tree__item.is-navigated-object .c-tree__item__label .c-tree__item__type-icon').click({
button: 'right'
});
await page.locator('li.icon-move').click();
// See if it's possible to put the folder in the Telemetry object after creation
await page.locator(`text=Location Open MCT ${myItemsFolderName} >> span`).nth(3).click();
await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click();
let okButton2 = await page.locator('button.c-button.c-button--major:has-text("OK")');
let okButtonStateDisabled2 = await okButton2.isDisabled();
expect(okButtonStateDisabled2).toBeTruthy();
});
test('Create a basic object and verify that it can be linked to another folder', async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
// Go to Open MCT
await page.goto('./');
const parentFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Parent Folder'
});
const childFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Child Folder',
parent: parentFolder.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Grandchild Folder',
parent: childFolder.uuid
});
// Attempt to link parent to its own grandparent
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
await page.locator('.c-disclosure-triangle >> nth=0').click();
await page.locator(`a:has-text("Parent Folder") >> nth=0`).click({
button: 'right'
});
await page.locator('li.icon-link').click();
await page.locator('form[name="mctForm"] >> .c-disclosure-triangle >> nth=0').click();
await page.locator('form[name="mctForm"] >> text=Parent Folder').click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
await page.locator('form[name="mctForm"] >> .c-disclosure-triangle >> nth=1').click();
await page.locator('form[name="mctForm"] >> text=Child Folder').click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
await page.locator('form[name="mctForm"] >> .c-disclosure-triangle >> nth=2').click();
await page.locator('form[name="mctForm"] >> text=Grandchild Folder').click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
await page.locator('form[name="mctForm"] >> text=Parent Folder').click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
await page.locator('[aria-label="Cancel"]').click();
// Link Child Folder from Parent Folder to My Items
await page.locator('.c-disclosure-triangle >> nth=0').click();
await page.locator('.c-disclosure-triangle >> nth=1').click();
await page.locator(`a:has-text("Child Folder") >> nth=0`).click({
button: 'right'
});
await page.locator('li.icon-link').click();
await page.locator(`form[name="mctForm"] >> text=${myItemsFolderName}`).click();
await page.locator('text=OK').click();
// Expect that Child Folder is in My Items, the root folder
expect(page.locator(`text=${myItemsFolderName} >> nth=0:has(text=Child Folder)`)).toBeTruthy();
});
});
test.fixme('Cannot move a previously created domain object to non-peristable object in Move Modal', async ({ page }) => {
//Create a domain object
//Save Domain object
//Move Object and verify that cannot select non-persistable object
//Move Object to My Items
//Verify successful move
});

View File

@@ -0,0 +1,148 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*
This test suite is dedicated to tests which verify the basic operations surrounding moving objects.
*/
const { test, expect } = require('../../pluginFixtures');
test.describe('Move item tests', () => {
test('Create a basic object and verify that it can be moved to another folder', async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
// Go to Open MCT
await page.goto('./');
// Create a new folder in the root my items folder
let folder1 = "Folder1";
await page.locator('button:has-text("Create")').click();
await page.locator('li.icon-folder').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder1);
await Promise.all([
page.waitForNavigation(),
page.locator('text=OK').click(),
page.waitForSelector('.c-message-banner__message')
]);
//Wait until Save Banner is gone
await page.locator('.c-message-banner__close-button').click();
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
// Create another folder with a new name at default location, which is currently inside Folder 1
let folder2 = "Folder2";
await page.locator('button:has-text("Create")').click();
await page.locator('li.icon-folder').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder2);
await Promise.all([
page.waitForNavigation(),
page.locator('text=OK').click(),
page.waitForSelector('.c-message-banner__message')
]);
//Wait until Save Banner is gone
await page.locator('.c-message-banner__close-button').click();
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
// Move Folder 2 from Folder 1 to My Items
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
await page.locator('.c-tree__scrollable div div:nth-child(2) .c-tree__item .c-tree__item__view-control').click();
await page.locator(`a:has-text("${folder2}")`).click({
button: 'right'
});
await page.locator('li.icon-move').click();
await page.locator(`form[name="mctForm"] >> text=${myItemsFolderName}`).click();
await page.locator('text=OK').click();
// Expect that Folder 2 is in My Items, the root folder
expect(page.locator(`text=${myItemsFolderName} >> nth=0:has(text=${folder2})`)).toBeTruthy();
});
test('Create a basic object and verify that it cannot be moved to telemetry object without Composition Provider', async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
// Go to Open MCT
await page.goto('./');
// Create Telemetry Table
let telemetryTable = 'Test Telemetry Table';
await page.locator('button:has-text("Create")').click();
await page.locator('li:has-text("Telemetry Table")').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(telemetryTable);
await page.locator('text=OK').click();
// Finish editing and save Telemetry Table
await page.locator('.c-button--menu.c-button--major.icon-save').click();
await page.locator('text=Save and Finish Editing').click();
// Create New Folder Basic Domain Object
let folder = 'Test Folder';
await page.locator('button:has-text("Create")').click();
await page.locator('li:has-text("Folder")').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder);
// See if it's possible to put the folder in the Telemetry object during creation (Soft Assert)
await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click();
let okButton = await page.locator('button.c-button.c-button--major:has-text("OK")');
let okButtonStateDisabled = await okButton.isDisabled();
expect.soft(okButtonStateDisabled).toBeTruthy();
// Continue test regardless of assertion and create it in My Items
await page.locator(`form[name="mctForm"] >> text=${myItemsFolderName}`).click();
await page.locator('text=OK').click();
// Open My Items
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
// Select Folder Object and select Move from context menu
await Promise.all([
page.waitForNavigation(),
page.locator(`a:has-text("${folder}")`).click()
]);
await page.locator('.c-tree__item.is-navigated-object .c-tree__item__label .c-tree__item__type-icon').click({
button: 'right'
});
await page.locator('li.icon-move').click();
// See if it's possible to put the folder in the Telemetry object after creation
await page.locator(`text=Location Open MCT ${myItemsFolderName} >> span`).nth(3).click();
await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click();
let okButton2 = await page.locator('button.c-button.c-button--major:has-text("OK")');
let okButtonStateDisabled2 = await okButton2.isDisabled();
expect(okButtonStateDisabled2).toBeTruthy();
});
});
test.fixme('Cannot move a previously created domain object to non-peristable object in Move Modal', async ({ page }) => {
//Create a domain object
//Save Domain object
//Move Object and verify that cannot select non-persistable object
//Move Object to My Items
//Verify successful move
});

View File

@@ -1,98 +0,0 @@
/*****************************************************************************
* 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('../../baseFixtures');
const { createDomainObjectWithDefaults } = require('../../appActions');
test.describe('Renaming Timer Object', () => {
//Create a testcase name which will be obvious when it fails in CI
test('Can create a new Timer object and rename it from actions Menu', async ({ page }) => {
//Open a browser, navigate to the main page, and wait until all networkevents to resolve
await page.goto('./', { waitUntil: 'networkidle' });
//We provide some helper functions in appActions like createDomainObjectWithDefaults. This example will create a Timer object
await createDomainObjectWithDefaults(page, { type: 'Timer' });
//Assert the object to be created and check it's name in the title
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Timer');
const newObjectName = "Renamed Timer";
//We've created an example of a shared function which pases the page and newObjectName values
await renameObjectFrom3DotMenu(page, newObjectName);
//Assert that the name has changed in the browser bar to the value we assigned above
await expect(page.locator('.l-browse-bar__object-name')).toContainText(newObjectName);
});
test('An existing Timer object can be renamed twice', async ({ page }) => {
//Open a browser, navigate to the main page, and wait until all networkevents to resolve
await page.goto('./', { waitUntil: 'networkidle' });
//We provide some helper functions in appActions like createDomainObjectWithDefaults. This example will create a Timer object
await createDomainObjectWithDefaults(page, { type: 'Timer' });
//Expect the object to be created and check it's name in the title
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Timer');
const newObjectName = "Renamed Timer";
const newObjectName2 = "Re-Renamed Timer";
//We've created an example of a shared function which pases the page and newObjectName values
await renameObjectFrom3DotMenu(page, newObjectName);
//Assert that the name has changed in the browser bar to the value we assigned above
await expect(page.locator('.l-browse-bar__object-name')).toContainText(newObjectName);
await renameObjectFrom3DotMenu(page, newObjectName2);
//Assert that the name has changed in the browser bar to the second value
await expect(page.locator('.l-browse-bar__object-name')).toContainText(newObjectName2);
});
//If you run out of time to write new tests, please stub in the missing tests in place with a test.fixme and BDD-style test steps. Someone will carry the baton!
test.fixme('Can Rename Timer Object from Tree', async ({ page }) => {
//Create a new object
//Copy this object
//Delete first object
//Expect copied object to persist
});
});
//Structure: custom functions should be declared last. We are leaning on JSDoc pretty heavily to describe functionality. It is not required, but heavily recommended.
/**
* This is an example of a function which is shared between testcases in this test suite. When refactoring, we'll be looking
* for common functionality which makes sense to generalize for the entire test framework.
* @param {import('@playwright/test').Page} page
* @param {string} newNameForTimer New Name for object
*/
async function renameObjectFrom3DotMenu(page, newNameForTimer) {
// Click on 3 Dot Menu
await page.locator('button[title="More options"]').click();
// Click text=Edit Properties...
await page.locator('text=Edit Properties...').click();
// Rename the object with newNameForTimer variable which is passed into this function
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(newNameForTimer);
// Click Ok button to Save
await page.locator('text=OK').click();
}

View File

@@ -1,122 +0,0 @@
/*****************************************************************************
* 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, setStartOffset, setFixedTimeMode, setRealTimeMode } = require('../../../../appActions');
test.describe('Testing Display Layout @unstable', () => {
let sineWaveObject;
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'networkidle' });
await setRealTimeMode(page);
// Create Sine Wave Generator
sineWaveObject = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: "Test Sine Wave Generator"
});
});
test('alpha-numeric widget telemetry value exactly matches latest telemetry value received in real time', async ({ page }) => {
// Create a Display Layout
await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
name: "Test Display Layout"
});
// Edit Display Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the Display Layout and save changes
await page.dragAndDrop('text=Test Sine Wave Generator', '.l-layout__grid-holder');
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
// Subscribe to the Sine Wave Generator data
// On getting data, check if the value found in the Display Layout is the most recent value
// from the Sine Wave Generator
const getTelemValuePromise = await subscribeToTelemetry(page, sineWaveObject.uuid);
const formattedTelemetryValue = await getTelemValuePromise;
const displayLayoutValuePromise = await page.waitForSelector(`text="${formattedTelemetryValue}"`);
const displayLayoutValue = await displayLayoutValuePromise.textContent();
const trimmedDisplayValue = displayLayoutValue.trim();
await expect(trimmedDisplayValue).toBe(formattedTelemetryValue);
});
test('alpha-numeric widget telemetry value exactly matches latest telemetry value received in fixed time', async ({ page }) => {
// Create a Display Layout
await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
name: "Test Display Layout"
});
// Edit Display Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the Display Layout and save changes
await page.dragAndDrop('text=Test Sine Wave Generator', '.l-layout__grid-holder');
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
// Subscribe to the Sine Wave Generator data
const getTelemValuePromise = await subscribeToTelemetry(page, sineWaveObject.uuid);
// Set an offset of 1 minute and then change the time mode to fixed to set a 1 minute historical window
await setStartOffset(page, { mins: '1' });
await setFixedTimeMode(page);
// On getting data, check if the value found in the Display Layout is the most recent value
// from the Sine Wave Generator
const formattedTelemetryValue = await getTelemValuePromise;
const displayLayoutValuePromise = await page.waitForSelector(`text="${formattedTelemetryValue}"`);
const displayLayoutValue = await displayLayoutValuePromise.textContent();
const trimmedDisplayValue = displayLayoutValue.trim();
await expect(trimmedDisplayValue).toBe(formattedTelemetryValue);
});
});
/**
* Util for subscribing to a telemetry object by object identifier
* Limitations: Currently only works to return telemetry once to the node scope
* To Do: See if there's a way to await this multiple times to allow for multiple
* values to be returned over time
* @param {import('@playwright/test').Page} page
* @param {string} objectIdentifier identifier for object
* @returns {Promise<string>} the formatted sin telemetry value
*/
async function subscribeToTelemetry(page, objectIdentifier) {
const getTelemValuePromise = new Promise(resolve => page.exposeFunction('getTelemValue', resolve));
await page.evaluate(async (telemetryIdentifier) => {
const telemetryObject = await window.openmct.objects.get(telemetryIdentifier);
const metadata = window.openmct.telemetry.getMetadata(telemetryObject);
const formats = await window.openmct.telemetry.getFormatMap(metadata);
window.openmct.telemetry.subscribe(telemetryObject, (obj) => {
const sinVal = obj.sin;
const formattedSinVal = formats.sin.format(sinVal);
window.getTelemValue(formattedSinVal);
});
}, objectIdentifier);
return getTelemValuePromise;
}

View File

@@ -41,7 +41,7 @@ test.describe('Example Imagery Object', () => {
await page.goto('./', { waitUntil: 'networkidle' });
// Create a default 'Example Imagery' object
createDomainObjectWithDefaults(page, { type: 'Example Imagery' });
createDomainObjectWithDefaults(page, 'Example Imagery');
await Promise.all([
page.waitForNavigation(),

View File

@@ -1,120 +0,0 @@
/*****************************************************************************
* 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, setStartOffset, setFixedTimeMode, setRealTimeMode } = require('../../../../appActions');
test.describe('Testing LAD table @unstable', () => {
let sineWaveObject;
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'networkidle' });
await setRealTimeMode(page);
// Create Sine Wave Generator
sineWaveObject = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: "Test Sine Wave Generator"
});
});
test('telemetry value exactly matches latest telemetry value received in real time', async ({ page }) => {
// Create LAD table
await createDomainObjectWithDefaults(page, {
type: 'LAD Table',
name: "Test LAD Table"
});
// Edit LAD table
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the LAD table and save changes
await page.dragAndDrop('text=Test Sine Wave Generator', '.c-lad-table-wrapper');
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
// Subscribe to the Sine Wave Generator data
// On getting data, check if the value found in the LAD table is the most recent value
// from the Sine Wave Generator
const getTelemValuePromise = await subscribeToTelemetry(page, sineWaveObject.uuid);
const subscribeTelemValue = await getTelemValuePromise;
const ladTableValuePromise = await page.waitForSelector(`text="${subscribeTelemValue}"`);
const ladTableValue = await ladTableValuePromise.textContent();
expect(ladTableValue).toBe(subscribeTelemValue);
});
test('telemetry value exactly matches latest telemetry value received in fixed time', async ({ page }) => {
// Create LAD table
await createDomainObjectWithDefaults(page, {
type: 'LAD Table',
name: "Test LAD Table"
});
// Edit LAD table
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the LAD table and save changes
await page.dragAndDrop('text=Test Sine Wave Generator', '.c-lad-table-wrapper');
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
// Subscribe to the Sine Wave Generator data
const getTelemValuePromise = await subscribeToTelemetry(page, sineWaveObject.uuid);
// Set an offset of 1 minute and then change the time mode to fixed to set a 1 minute historical window
await setStartOffset(page, { mins: '1' });
await setFixedTimeMode(page);
// On getting data, check if the value found in the LAD table is the most recent value
// from the Sine Wave Generator
const subscribeTelemValue = await getTelemValuePromise;
const ladTableValuePromise = await page.waitForSelector(`text="${subscribeTelemValue}"`);
const ladTableValue = await ladTableValuePromise.textContent();
expect(ladTableValue).toBe(subscribeTelemValue);
});
});
/**
* Util for subscribing to a telemetry object by object identifier
* Limitations: Currently only works to return telemetry once to the node scope
* To Do: See if there's a way to await this multiple times to allow for multiple
* values to be returned over time
* @param {import('@playwright/test').Page} page
* @param {string} objectIdentifier identifier for object
* @returns {Promise<string>} the formatted sin telemetry value
*/
async function subscribeToTelemetry(page, objectIdentifier) {
const getTelemValuePromise = new Promise(resolve => page.exposeFunction('getTelemValue', resolve));
await page.evaluate(async (telemetryIdentifier) => {
const telemetryObject = await window.openmct.objects.get(telemetryIdentifier);
const metadata = window.openmct.telemetry.getMetadata(telemetryObject);
const formats = await window.openmct.telemetry.getFormatMap(metadata);
window.openmct.telemetry.subscribe(telemetryObject, (obj) => {
const sinVal = obj.sin;
const formattedSinVal = formats.sin.format(sinVal);
window.getTelemValue(formattedSinVal);
});
}, objectIdentifier);
return getTelemValuePromise;
}

View File

@@ -36,7 +36,7 @@ async function createNotebookAndEntry(page, iterations = 1) {
//Go to baseURL
await page.goto('./', { waitUntil: 'networkidle' });
createDomainObjectWithDefaults(page, { type: 'Notebook' });
createDomainObjectWithDefaults(page, 'Notebook');
for (let iteration = 0; iteration < iterations; iteration++) {
// Click text=To start a new entry, click here or drag and drop any object
@@ -139,28 +139,11 @@ test.describe('Tagging in Notebooks @addInit', () => {
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
await expect(page.locator('[aria-label="Search Result"]')).not.toContainText("Driving");
});
test('Can delete objects with tags and neither return in search', async ({ page }) => {
await createNotebookEntryAndTags(page);
// Delete Notebook
await page.locator('button[title="More options"]').click();
await page.locator('li[title="Remove this object from its containing object."]').click();
await page.locator('button:has-text("OK")').click();
await page.goto('./', { waitUntil: 'networkidle' });
// Fill [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Unnamed');
await expect(page.locator('text=No matching results.')).toBeVisible();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sci');
await expect(page.locator('text=No matching results.')).toBeVisible();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('dri');
await expect(page.locator('text=No matching results.')).toBeVisible();
});
test('Tags persist across reload', async ({ page }) => {
//Go to baseURL
await page.goto('./', { waitUntil: 'networkidle' });
await createDomainObjectWithDefaults(page, { type: 'Clock' });
await createDomainObjectWithDefaults(page, 'Clock');
const ITERATIONS = 4;
await createNotebookEntryAndTags(page, ITERATIONS);

View File

@@ -20,26 +20,55 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
const { createDomainObjectWithDefaults } = require('../../../../appActions');
const { test, expect } = require('../../../../pluginFixtures');
test.describe('Telemetry Table', () => {
test('unpauses and filters data when paused by button and user changes bounds', async ({ page }) => {
test('unpauses and filters data when paused by button and user changes bounds', async ({ page, openmctConfig }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5113'
});
const { myItemsFolderName } = openmctConfig;
const bannerMessage = '.c-message-banner__message';
const createButton = 'button:has-text("Create")';
await page.goto('./', { waitUntil: 'networkidle' });
const table = await createDomainObjectWithDefaults(page, { type: 'Telemetry Table' });
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
parent: table.uuid
});
// Click create button
await page.locator(createButton).click();
await page.locator('li:has-text("Telemetry Table")').click();
await Promise.all([
page.waitForNavigation(),
page.locator('text=OK').click(),
// Wait for Save Banner to appear
page.waitForSelector(bannerMessage)
]);
// Save (exit edit mode)
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(3).click();
await page.locator('text=Save and Finish Editing').click();
// Click create button
await page.locator(createButton).click();
// add Sine Wave Generator with defaults
await page.locator('li:has-text("Sine Wave Generator")').click();
await Promise.all([
page.waitForNavigation(),
page.locator('text=OK').click(),
// Wait for Save Banner to appear
page.waitForSelector(bannerMessage)
]);
// focus the Telemetry Table
page.goto(table.url);
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
await Promise.all([
page.waitForNavigation(),
page.locator('text=Unnamed Telemetry Table').first().click()
]);
// Click pause button
const pauseButton = page.locator('button.c-button.icon-pause');

View File

@@ -21,7 +21,6 @@
*****************************************************************************/
const { test, expect } = require('../../../../baseFixtures');
const { setFixedTimeMode, setRealTimeMode, setStartOffset, setEndOffset } = require('../../../../appActions');
test.describe('Time conductor operations', () => {
test('validate start time does not exceeds end time', async ({ page }) => {
@@ -148,3 +147,88 @@ test.describe('Time conductor input fields real-time mode', () => {
expect(page.url()).toContain(`endDelta=${endDelta}`);
});
});
/**
* @typedef {Object} OffsetValues
* @property {string | undefined} hours
* @property {string | undefined} mins
* @property {string | undefined} secs
*/
/**
* Set the values (hours, mins, secs) for the start time offset when in realtime mode
* @param {import('@playwright/test').Page} page
* @param {OffsetValues} offset
*/
async function setStartOffset(page, offset) {
const startOffsetButton = page.locator('data-testid=conductor-start-offset-button');
await setTimeConductorOffset(page, offset, startOffsetButton);
}
/**
* Set the values (hours, mins, secs) for the end time offset when in realtime mode
* @param {import('@playwright/test').Page} page
* @param {OffsetValues} offset
*/
async function setEndOffset(page, offset) {
const endOffsetButton = page.locator('data-testid=conductor-end-offset-button');
await setTimeConductorOffset(page, offset, endOffsetButton);
}
/**
* Set the time conductor to fixed timespan mode
* @param {import('@playwright/test').Page} page
*/
async function setFixedTimeMode(page) {
await setTimeConductorMode(page, true);
}
/**
* Set the time conductor to realtime mode
* @param {import('@playwright/test').Page} page
*/
async function setRealTimeMode(page) {
await setTimeConductorMode(page, false);
}
/**
* Set the values (hours, mins, secs) for the TimeConductor offsets when in realtime mode
* @param {import('@playwright/test').Page} page
* @param {OffsetValues} offset
* @param {import('@playwright/test').Locator} offsetButton
*/
async function setTimeConductorOffset(page, {hours, mins, secs}, offsetButton) {
await offsetButton.click();
if (hours) {
await page.fill('.pr-time-controls__hrs', hours);
}
if (mins) {
await page.fill('.pr-time-controls__mins', mins);
}
if (secs) {
await page.fill('.pr-time-controls__secs', secs);
}
// Click the check button
await page.locator('.icon-check').click();
}
/**
* Set the time conductor mode to either fixed timespan or realtime mode.
* @param {import('@playwright/test').Page} page
* @param {boolean} [isFixedTimespan=true] true for fixed timespan mode, false for realtime mode; default is true
*/
async function setTimeConductorMode(page, isFixedTimespan = true) {
// Click 'mode' button
await page.locator('.c-mode-button').click();
// Switch time conductor mode
if (isFixedTimespan) {
await page.locator('data-testid=conductor-modeOption-fixed').click();
} else {
await page.locator('data-testid=conductor-modeOption-realtime').click();
}
}

View File

@@ -26,7 +26,7 @@ const { openObjectTreeContextMenu, createDomainObjectWithDefaults } = require('.
test.describe('Timer', () => {
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'networkidle' });
await createDomainObjectWithDefaults(page, { type: 'timer' });
await createDomainObjectWithDefaults(page, 'timer');
});
test('Can perform actions on the Timer', async ({ page, openmctConfig }) => {

View File

@@ -107,9 +107,6 @@ test.describe("Search Tests @unstable", () => {
// Verify that no results are found
expect(await searchResults.count()).toBe(0);
// Verify proper message appears
await expect(page.locator('text=No matching results.')).toBeVisible();
});
test('Validate single object in search result', async ({ page }) => {

View File

@@ -53,7 +53,7 @@ test.describe('Visual - addInit', () => {
//Go to baseURL
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
await createDomainObjectWithDefaults(page, { type: CUSTOM_NAME });
await createDomainObjectWithDefaults(page, CUSTOM_NAME);
// Take a snapshot of the newly created CUSTOM_NAME notebook
await percySnapshot(page, `Restricted Notebook with CUSTOM_NAME (theme: '${theme}')`);

View File

@@ -67,9 +67,9 @@ test.describe('Visual - Default', () => {
await percySnapshot(page, `About (theme: '${theme}')`);
});
test.fixme('Visual - Default Condition Set', async ({ page, theme }) => {
test('Visual - Default Condition Set', async ({ page, theme }) => {
await createDomainObjectWithDefaults(page, { type: 'Condition Set' });
await createDomainObjectWithDefaults(page, 'Condition Set');
// Take a snapshot of the newly created Condition Set object
await percySnapshot(page, `Default Condition Set (theme: '${theme}')`);
@@ -81,7 +81,7 @@ test.describe('Visual - Default', () => {
description: 'https://github.com/nasa/openmct/issues/5349'
});
await createDomainObjectWithDefaults(page, { type: 'Condition Widget' });
await createDomainObjectWithDefaults(page, 'Condition Widget');
// Take a snapshot of the newly created Condition Widget object
await percySnapshot(page, `Default Condition Widget (theme: '${theme}')`);
@@ -137,8 +137,8 @@ test.describe('Visual - Default', () => {
await percySnapshot(page, `removed amplitude property value (theme: '${theme}')`);
});
test.fixme('Visual - Save Successful Banner', async ({ page, theme }) => {
await createDomainObjectWithDefaults(page, { type: 'Timer' });
test('Visual - Save Successful Banner', async ({ page, theme }) => {
await createDomainObjectWithDefaults(page, 'Timer');
await page.locator('.c-message-banner__message').hover({ trial: true });
await percySnapshot(page, `Banner message shown (theme: '${theme}')`);
@@ -159,8 +159,8 @@ test.describe('Visual - Default', () => {
});
test.fixme('Visual - Default Gauge is correct', async ({ page, theme }) => {
await createDomainObjectWithDefaults(page, { type: 'Gauge' });
test('Visual - Default Gauge is correct', async ({ page, theme }) => {
await createDomainObjectWithDefaults(page, 'Gauge');
// Take a snapshot of the newly created Gauge object
await percySnapshot(page, `Default Gauge (theme: '${theme}')`);

View File

@@ -46,10 +46,7 @@ test.describe('Grand Search', () => {
// await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
// await page.locator('text=Save and Finish Editing').click();
const folder1 = 'Folder1';
await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: folder1
});
await createDomainObjectWithDefaults(page, 'Folder', folder1);
// Click [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();

View File

@@ -1,6 +1,6 @@
{
"name": "openmct",
"version": "2.0.8",
"version": "2.1.0-SNAPSHOT",
"description": "The Open MCT core platform",
"devDependencies": {
"@babel/eslint-parser": "7.18.9",

View File

@@ -40,8 +40,6 @@ const ANNOTATION_TYPES = Object.freeze({
PLOT_SPATIAL: 'PLOT_SPATIAL'
});
const ANNOTATION_TYPE = 'annotation';
/**
* @typedef {Object} Tag
* @property {String} key a unique identifier for the tag
@@ -56,7 +54,7 @@ export default class AnnotationAPI extends EventEmitter {
this.ANNOTATION_TYPES = ANNOTATION_TYPES;
this.openmct.types.addType(ANNOTATION_TYPE, {
this.openmct.types.addType('annotation', {
name: 'Annotation',
description: 'A user created note or comment about time ranges, pixel space, and geospatial features.',
creatable: false,
@@ -138,10 +136,6 @@ export default class AnnotationAPI extends EventEmitter {
this.availableTags[tagKey] = tagsDefinition;
}
isAnnotation(domainObject) {
return domainObject && (domainObject.type === ANNOTATION_TYPE);
}
getAvailableTags() {
if (this.availableTags) {
const rearrangedToArray = Object.keys(this.availableTags).map(tagKey => {
@@ -277,10 +271,7 @@ export default class AnnotationAPI extends EventEmitter {
const searchResults = (await Promise.all(this.openmct.objects.search(matchingTagKeys, abortController, this.openmct.objects.SEARCH_TYPES.TAGS))).flat();
const appliedTagSearchResults = this.#addTagMetaInformationToResults(searchResults, matchingTagKeys);
const appliedTargetsModels = await this.#addTargetModelsToResults(appliedTagSearchResults);
const resultsWithValidPath = appliedTargetsModels.filter(result => {
return this.openmct.objects.isReachable(result.targetModels?.[0]?.originalPath);
});
return resultsWithValidPath;
return appliedTargetsModels;
}
}

View File

@@ -27,26 +27,15 @@ describe("The Annotation API", () => {
let openmct;
let mockObjectProvider;
let mockDomainObject;
let mockFolderObject;
let mockAnnotationObject;
beforeEach((done) => {
openmct = createOpenMct();
openmct.install(new ExampleTagsPlugin());
const availableTags = openmct.annotation.getAvailableTags();
mockFolderObject = {
type: 'root',
name: 'folderFoo',
location: '',
identifier: {
key: 'someParent',
namespace: 'fooNameSpace'
}
};
mockDomainObject = {
type: 'notebook',
name: 'fooRabbitNotebook',
location: 'fooNameSpace:someParent',
identifier: {
key: 'some-object',
namespace: 'fooNameSpace'
@@ -79,8 +68,6 @@ describe("The Annotation API", () => {
return mockDomainObject;
} else if (identifier.key === mockAnnotationObject.identifier.key) {
return mockAnnotationObject;
} else if (identifier.key === mockFolderObject.identifier.key) {
return mockFolderObject;
} else {
return null;
}
@@ -163,7 +150,6 @@ describe("The Annotation API", () => {
// use local worker
sharedWorkerToRestore = openmct.objects.inMemorySearchProvider.worker;
openmct.objects.inMemorySearchProvider.worker = null;
await openmct.objects.inMemorySearchProvider.index(mockFolderObject);
await openmct.objects.inMemorySearchProvider.index(mockDomainObject);
await openmct.objects.inMemorySearchProvider.index(mockAnnotationObject);
});

View File

@@ -34,11 +34,11 @@ import InMemorySearchProvider from './InMemorySearchProvider';
* Uniquely identifies a domain object.
*
* @typedef Identifier
* @memberof module:openmct.ObjectAPI~
* @property {string} namespace the namespace to/from which this domain
* object should be loaded/stored.
* @property {string} key a unique identifier for the domain object
* within that namespace
* @memberof module:openmct.ObjectAPI~
*/
/**
@@ -615,60 +615,27 @@ export default class ObjectAPI {
* @param {module:openmct.ObjectAPI~Identifier[]} identifiers
*/
areIdsEqual(...identifiers) {
const firstIdentifier = utils.parseKeyString(identifiers[0]);
return identifiers.map(utils.parseKeyString)
.every(identifier => {
return identifier === firstIdentifier
|| (identifier.namespace === firstIdentifier.namespace
&& identifier.key === firstIdentifier.key);
return identifier === identifiers[0]
|| (identifier.namespace === identifiers[0].namespace
&& identifier.key === identifiers[0].key);
});
}
/**
* Given an original path check if the path is reachable via root
* @param {Array<Object>} originalPath an array of path objects to check
* @returns {boolean} whether the domain object is reachable
*/
isReachable(originalPath) {
if (originalPath && originalPath.length) {
return (originalPath[originalPath.length - 1].type === 'root');
}
getOriginalPath(identifier, path = []) {
return this.get(identifier).then((domainObject) => {
path.push(domainObject);
let location = domainObject.location;
return false;
}
#pathContainsDomainObject(keyStringToCheck, path) {
if (!keyStringToCheck) {
return false;
}
return path.some(pathElement => {
const identifierToCheck = utils.parseKeyString(keyStringToCheck);
return this.areIdsEqual(identifierToCheck, pathElement.identifier);
if (location) {
return this.getOriginalPath(utils.parseKeyString(location), path);
} else {
return path;
}
});
}
/**
* Given an identifier, constructs the original path by walking up its parents
* @param {module:openmct.ObjectAPI~Identifier} identifier
* @param {Array<module:openmct.DomainObject>} path an array of path objects
* @returns {Promise<Array<module:openmct.DomainObject>>} a promise containing an array of domain objects
*/
async getOriginalPath(identifier, path = []) {
const domainObject = await this.get(identifier);
path.push(domainObject);
const { location } = domainObject;
if (location && (!this.#pathContainsDomainObject(location, path))) {
// if we have a location, and we don't already have this in our constructed path,
// then keep walking up the path
return this.getOriginalPath(utils.parseKeyString(location), path);
} else {
return path;
}
}
isObjectPathToALink(domainObject, objectPath) {
return objectPath !== undefined
&& objectPath.length > 1

View File

@@ -377,73 +377,6 @@ describe("The Object API", () => {
});
});
describe("getOriginalPath", () => {
let mockGrandParentObject;
let mockParentObject;
let mockChildObject;
beforeEach(() => {
const mockObjectProvider = jasmine.createSpyObj("mock object provider", [
"create",
"update",
"get"
]);
mockGrandParentObject = {
type: 'folder',
name: 'Grand Parent Folder',
location: 'fooNameSpace:child',
identifier: {
key: 'grandParent',
namespace: 'fooNameSpace'
}
};
mockParentObject = {
type: 'folder',
name: 'Parent Folder',
location: 'fooNameSpace:grandParent',
identifier: {
key: 'parent',
namespace: 'fooNameSpace'
}
};
mockChildObject = {
type: 'folder',
name: 'Child Folder',
location: 'fooNameSpace:parent',
identifier: {
key: 'child',
namespace: 'fooNameSpace'
}
};
// eslint-disable-next-line require-await
mockObjectProvider.get = async (identifier) => {
if (identifier.key === mockGrandParentObject.identifier.key) {
return mockGrandParentObject;
} else if (identifier.key === mockParentObject.identifier.key) {
return mockParentObject;
} else if (identifier.key === mockChildObject.identifier.key) {
return mockChildObject;
} else {
return null;
}
};
openmct.objects.addProvider('fooNameSpace', mockObjectProvider);
mockObjectProvider.create.and.returnValue(Promise.resolve(true));
mockObjectProvider.update.and.returnValue(Promise.resolve(true));
openmct.objects.addProvider('fooNameSpace', mockObjectProvider);
});
it('can construct paths even with cycles', async () => {
const objectPath = await objectAPI.getOriginalPath(mockChildObject.identifier);
expect(objectPath.length).toEqual(3);
});
});
describe("transactions", () => {
beforeEach(() => {
spyOn(openmct.editor, 'isEditing').and.returnValue(true);

View File

@@ -91,10 +91,6 @@ define([
* @returns keyString
*/
function makeKeyString(identifier) {
if (!identifier) {
throw new Error("Cannot make key string from null identifier");
}
if (isKeyString(identifier)) {
return identifier;
}

View File

@@ -52,6 +52,8 @@ $meterNeedleBorderRadius: 5px;
.c-dial {
max-height: 100%;
max-width: 100%;
display: block;
margin: auto; // Centers SVG in container while allowing scaling
&__bg {
fill: $colorGaugeBg;

View File

@@ -83,6 +83,7 @@ export default class LinkAction {
}
]
};
this.openmct.forms.showForm(formStructure)
.then(this.onSave.bind(this));
}
@@ -90,8 +91,8 @@ export default class LinkAction {
validate(currentParent) {
return (data) => {
// default current parent to ROOT, if it's null, then it's a root level item
if (!currentParent) {
// default current parent to ROOT, if it's undefined, then it's a root level item
if (currentParent === undefined) {
currentParent = {
identifier: {
key: 'ROOT',
@@ -100,23 +101,24 @@ export default class LinkAction {
};
}
const parentCandidatePath = data.value;
const parentCandidate = parentCandidatePath[0];
const parentCandidate = data.value[0];
const currentParentKeystring = this.openmct.objects.makeKeyString(currentParent.identifier);
const parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.identifier);
const objectKeystring = this.openmct.objects.makeKeyString(this.object.identifier);
if (!this.openmct.objects.isPersistable(parentCandidate.identifier)) {
return false;
}
// check if moving to same place
if (this.openmct.objects.areIdsEqual(parentCandidate.identifier, currentParent.identifier)) {
if (!parentCandidateKeystring || !currentParentKeystring) {
return false;
}
// check if moving to a child
if (parentCandidatePath.some(candidatePath => {
return this.openmct.objects.areIdsEqual(candidatePath.identifier, this.object.identifier);
})) {
if (parentCandidateKeystring === currentParentKeystring) {
return false;
}
if (parentCandidateKeystring === objectKeystring) {
return false;
}

View File

@@ -145,24 +145,26 @@ export default class MoveAction {
const parentCandidatePath = data.value;
const parentCandidate = parentCandidatePath[0];
// check if moving to same place
if (this.openmct.objects.areIdsEqual(parentCandidate.identifier, currentParent.identifier)) {
return false;
}
// check if moving to a child
if (parentCandidatePath.some(candidatePath => {
return this.openmct.objects.areIdsEqual(candidatePath.identifier, this.object.identifier);
})) {
return false;
}
if (!this.openmct.objects.isPersistable(parentCandidate.identifier)) {
return false;
}
let currentParentKeystring = this.openmct.objects.makeKeyString(currentParent.identifier);
let parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.identifier);
let objectKeystring = this.openmct.objects.makeKeyString(this.object.identifier);
if (!parentCandidateKeystring || !currentParentKeystring) {
return false;
}
if (parentCandidateKeystring === currentParentKeystring) {
return false;
}
if (parentCandidateKeystring === objectKeystring) {
return false;
}
const parentCandidateComposition = parentCandidate.composition;
if (parentCandidateComposition && parentCandidateComposition.indexOf(objectKeystring) !== -1) {
return false;

View File

@@ -58,7 +58,6 @@ define([
'./condition/plugin',
'./conditionWidget/plugin',
'./themes/espresso',
'./themes/maelstrom',
'./themes/snow',
'./URLTimeSettingsSynchronizer/plugin',
'./notificationIndicator/plugin',
@@ -122,7 +121,6 @@ define([
ConditionPlugin,
ConditionWidgetPlugin,
Espresso,
Maelstrom,
Snow,
URLTimeSettingsSynchronizer,
NotificationIndicator,
@@ -207,7 +205,6 @@ define([
plugins.ClearData = ClearData;
plugins.WebPage = WebPagePlugin.default;
plugins.Espresso = Espresso.default;
plugins.Maelstrom = Maelstrom.default;
plugins.Snow = Snow.default;
plugins.Condition = ConditionPlugin.default;
plugins.ConditionWidget = ConditionWidgetPlugin.default;

View File

@@ -1,22 +0,0 @@
@import "../../styles/vendor/normalize-min";
@import "../../styles/constants";
@import "../../styles/constants-mobile.scss";
@import "../../styles/constants-maelstrom";
@import "../../styles/mixins";
@import "../../styles/animations";
@import "../../styles/about";
@import "../../styles/glyphs";
@import "../../styles/global";
@import "../../styles/status";
@import "../../styles/limits";
@import "../../styles/controls";
@import "../../styles/forms";
@import "../../styles/table";
@import "../../styles/legacy";
@import "../../styles/legacy-plots";
@import "../../styles/plotly";
@import "../../styles/legacy-messages";
@import "../../styles/vue-styles.scss";

View File

@@ -1,7 +0,0 @@
import { installTheme } from './installTheme';
export default function plugin() {
return function install(openmct) {
installTheme(openmct, 'maelstrom');
};
}

View File

@@ -188,8 +188,7 @@ export default {
if (domainObject.type === 'plan') {
this.getPlanDataAndSetConfig({
...this.domainObject,
selectFile: domainObject.selectFile,
sourceMap: domainObject.sourceMap
selectFile: domainObject.selectFile
});
}
},

View File

@@ -25,14 +25,13 @@
/******************************************************** CONTROL-SPECIFIC MIXINS */
@mixin menuOuter() {
border-radius: $basicCr;
box-shadow: $shdwMenu;
@if $shdwMenuInner != none {
box-shadow: $shdwMenuInner, $shdwMenu;
}
box-shadow: $shdwMenuInner, $shdwMenu;
background: $colorMenuBg;
color: $colorMenuFg;
//filter: $filterMenu; // 2022: causing all kinds of weird visual bugs in Chrome
text-shadow: $shdwMenuText;
padding: $interiorMarginSm;
//box-shadow: $shdwMenu;
display: flex;
flex-direction: column;
position: absolute;
@@ -61,13 +60,14 @@
cursor: pointer;
display: flex;
padding: nth($menuItemPad, 1) nth($menuItemPad, 2);
transition: $transIn;
white-space: nowrap;
@include hover {
background: $colorMenuHovBg;
color: $colorMenuHovFg;
&:before {
color: $colorMenuHovIc !important;
color: $colorMenuHovIc;
}
}

View File

@@ -77,6 +77,7 @@ export default {
}
this.searchValue = value;
this.searchLoading = true;
// clear any previous search results
this.annotationSearchResults = [];
this.objectSearchResults = [];
@@ -84,13 +85,8 @@ export default {
if (this.searchValue) {
await this.getSearchResults();
} else {
const dropdownOptions = {
searchLoading: this.searchLoading,
searchValue: this.searchValue,
annotationSearchResults: this.annotationSearchResults,
objectSearchResults: this.objectSearchResults
};
this.$refs.searchResultsDropDown.showResults(dropdownOptions);
this.searchLoading = false;
this.$refs.searchResultsDropDown.showResults(this.annotationSearchResults, this.objectSearchResults);
}
},
getPathsForObjects(objectsNeedingPaths) {
@@ -107,8 +103,6 @@ export default {
async getSearchResults() {
// an abort controller will be passed in that will be used
// to cancel an active searches if necessary
this.searchLoading = true;
this.$refs.searchResultsDropDown.showSearchStarted();
this.abortSearchController = new AbortController();
const abortSignal = this.abortSearchController.signal;
try {
@@ -116,15 +110,10 @@ export default {
const fullObjectSearchResults = await Promise.all(this.openmct.objects.search(this.searchValue, abortSignal));
const aggregatedObjectSearchResults = fullObjectSearchResults.flat();
const aggregatedObjectSearchResultsWithPaths = await this.getPathsForObjects(aggregatedObjectSearchResults);
const filterAnnotationsAndValidPaths = aggregatedObjectSearchResultsWithPaths.filter(result => {
if (this.openmct.annotation.isAnnotation(result)) {
return false;
}
return this.openmct.objects.isReachable(result?.originalPath);
const filterAnnotations = aggregatedObjectSearchResultsWithPaths.filter(result => {
return result.type !== 'annotation';
});
this.objectSearchResults = filterAnnotationsAndValidPaths;
this.searchLoading = false;
this.objectSearchResults = filterAnnotations;
this.showSearchResults();
} catch (error) {
console.error(`😞 Error searching`, error);
@@ -136,13 +125,7 @@ export default {
}
},
showSearchResults() {
const dropdownOptions = {
searchLoading: this.searchLoading,
searchValue: this.searchValue,
annotationSearchResults: this.annotationSearchResults,
objectSearchResults: this.objectSearchResults
};
this.$refs.searchResultsDropDown.showResults(dropdownOptions);
this.$refs.searchResultsDropDown.showResults(this.annotationSearchResults, this.objectSearchResults);
document.body.addEventListener('click', this.handleOutsideClick);
},
handleOutsideClick(event) {

View File

@@ -39,8 +39,6 @@ describe("GrandSearch", () => {
let mockAnnotationObject;
let mockDisplayLayout;
let mockFolderObject;
let mockAnotherFolderObject;
let mockTopObject;
let originalRouterPath;
beforeEach((done) => {
@@ -72,29 +70,11 @@ describe("GrandSearch", () => {
}
}
};
mockTopObject = {
type: 'root',
name: 'Top Folder',
identifier: {
key: 'topObject',
namespace: 'fooNameSpace'
}
};
mockAnotherFolderObject = {
type: 'folder',
name: 'Another Test Folder',
location: 'fooNameSpace:topObject',
identifier: {
key: 'someParent',
namespace: 'fooNameSpace'
}
};
mockFolderObject = {
type: 'folder',
name: 'Test Folder',
location: 'fooNameSpace:someParent',
identifier: {
key: 'someFolder',
key: 'some-folder',
namespace: 'fooNameSpace'
}
};
@@ -142,10 +122,6 @@ describe("GrandSearch", () => {
return mockDisplayLayout;
} else if (identifier.key === mockFolderObject.identifier.key) {
return mockFolderObject;
} else if (identifier.key === mockAnotherFolderObject.identifier.key) {
return mockAnotherFolderObject;
} else if (identifier.key === mockTopObject.identifier.key) {
return mockTopObject;
} else {
return null;
}

View File

@@ -22,6 +22,8 @@
<template>
<div
v-if="(annotationResults && annotationResults.length) ||
(objectResults && objectResults.length)"
class="c-gsearch__dropdown"
>
<div
@@ -56,40 +58,25 @@
@click.native="selectedResult"
/>
</div>
<div
v-if="searchLoading"
> <progress-bar
:model="{progressText: 'Searching...',
progressPerc: undefined
}"
/>
</div>
<div
v-if="!searchLoading && (!annotationResults || !annotationResults.length) &&
(!objectResults || !objectResults.length)"
>No matching results.
</div>
</div>
</div>
</div></template>
</div>
</template>
<script>
import AnnotationSearchResult from './AnnotationSearchResult.vue';
import ObjectSearchResult from './ObjectSearchResult.vue';
import ProgressBar from '@/ui/components/ProgressBar.vue';
export default {
name: 'SearchResultsDropDown',
components: {
AnnotationSearchResult,
ObjectSearchResult,
ProgressBar
ObjectSearchResult
},
inject: ['openmct'],
data() {
return {
resultsShown: false,
searchLoading: false,
annotationResults: [],
objectResults: [],
previewVisible: false
@@ -104,18 +91,12 @@ export default {
previewChanged(changedPreviewState) {
this.previewVisible = changedPreviewState;
},
showSearchStarted() {
this.searchLoading = true;
this.resultsShown = true;
this.annotationResults = [];
this.objectResults = [];
},
showResults({searchLoading, searchValue, annotationSearchResults, objectSearchResults}) {
this.searchLoading = searchLoading;
this.annotationResults = annotationSearchResults;
this.objectResults = objectSearchResults;
if (searchValue?.length) {
showResults(passedAnnotationResults, passedObjectResults) {
if ((passedAnnotationResults && passedAnnotationResults.length)
|| (passedObjectResults && passedObjectResults.length)) {
this.resultsShown = true;
this.annotationResults = passedAnnotationResults;
this.objectResults = passedObjectResults;
} else {
this.resultsShown = false;
}

View File

@@ -30,7 +30,6 @@ const config = {
inMemorySearchWorker: './src/api/objects/InMemorySearchWorker.js',
espressoTheme: './src/plugins/themes/espresso-theme.scss',
snowTheme: './src/plugins/themes/snow-theme.scss',
maelstromTheme: './src/plugins/themes/maelstrom-theme.scss'
},
output: {
globalObject: 'this',