Compare commits
5 Commits
2.0.7-demo
...
5211-tests
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b88655373 | ||
|
|
9c54a9f862 | ||
|
|
029605ebdc | ||
|
|
60b59d6bd7 | ||
|
|
9de84df6a9 |
@@ -5,7 +5,6 @@ executors:
|
|||||||
- image: mcr.microsoft.com/playwright:v1.23.0-focal
|
- image: mcr.microsoft.com/playwright:v1.23.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
|
|
||||||
parameters:
|
parameters:
|
||||||
BUST_CACHE:
|
BUST_CACHE:
|
||||||
description: "Set this with the CircleCI UI Trigger Workflow button (boolean = true) to bust the cache!"
|
description: "Set this with the CircleCI UI Trigger Workflow button (boolean = true) to bust the cache!"
|
||||||
@@ -65,8 +64,8 @@ commands:
|
|||||||
suite:
|
suite:
|
||||||
type: string
|
type: string
|
||||||
steps:
|
steps:
|
||||||
- run: npm run cov:e2e:report || true
|
- run: npm run cov:e2e:report
|
||||||
- run: npm run cov:e2e:<<parameters.suite>>:publish
|
- run: npm run cov:e2e:<<parameters.suite>>:publish
|
||||||
orbs:
|
orbs:
|
||||||
node: circleci/node@4.9.0
|
node: circleci/node@4.9.0
|
||||||
browser-tools: circleci/browser-tools@1.3.0
|
browser-tools: circleci/browser-tools@1.3.0
|
||||||
@@ -154,22 +153,6 @@ jobs:
|
|||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: html-test-results
|
path: html-test-results
|
||||||
- generate_and_store_version_and_filesystem_artifacts
|
- generate_and_store_version_and_filesystem_artifacts
|
||||||
visual-test:
|
|
||||||
parameters:
|
|
||||||
node-version:
|
|
||||||
type: string
|
|
||||||
executor: pw-focal-development
|
|
||||||
steps:
|
|
||||||
- build_and_install:
|
|
||||||
node-version: <<parameters.node-version>>
|
|
||||||
- run: npm run test:e2e:visual
|
|
||||||
- store_test_results:
|
|
||||||
path: test-results/results.xml
|
|
||||||
- store_artifacts:
|
|
||||||
path: test-results
|
|
||||||
- store_artifacts:
|
|
||||||
path: html-test-results
|
|
||||||
- generate_and_store_version_and_filesystem_artifacts
|
|
||||||
workflows:
|
workflows:
|
||||||
overall-circleci-commit-status: #These jobs run on every commit
|
overall-circleci-commit-status: #These jobs run on every commit
|
||||||
jobs:
|
jobs:
|
||||||
@@ -185,9 +168,6 @@ workflows:
|
|||||||
suite: stable
|
suite: stable
|
||||||
- perf-test:
|
- perf-test:
|
||||||
node-version: lts/gallium
|
node-version: lts/gallium
|
||||||
- visual-test:
|
|
||||||
node-version: lts/gallium
|
|
||||||
|
|
||||||
the-nightly: #These jobs do not run on PRs, but against master at night
|
the-nightly: #These jobs do not run on PRs, but against master at night
|
||||||
jobs:
|
jobs:
|
||||||
- unit-test:
|
- unit-test:
|
||||||
@@ -205,10 +185,6 @@ workflows:
|
|||||||
name: e2e-full-nightly
|
name: e2e-full-nightly
|
||||||
node-version: lts/gallium
|
node-version: lts/gallium
|
||||||
suite: full
|
suite: full
|
||||||
- perf-test:
|
|
||||||
node-version: lts/gallium
|
|
||||||
- visual-test:
|
|
||||||
node-version: lts/gallium
|
|
||||||
triggers:
|
triggers:
|
||||||
- schedule:
|
- schedule:
|
||||||
cron: "0 0 * * *"
|
cron: "0 0 * * *"
|
||||||
|
|||||||
25
.github/workflows/e2e-visual.yml
vendored
Normal file
25
.github/workflows/e2e-visual.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
name: "e2e-visual"
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- labeled
|
||||||
|
- opened
|
||||||
|
schedule:
|
||||||
|
- cron: '28 21 * * 1-5'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
e2e-visual:
|
||||||
|
if: ${{ github.event.label.name == 'pr:visual' }} || ${{ github.event.workflow_dispatch }} || ${{ github.event.schedule }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: '16'
|
||||||
|
- run: npx playwright@1.23.0 install
|
||||||
|
- run: npm install
|
||||||
|
- name: Run the e2e visual tests
|
||||||
|
run: npm run test:e2e:visual
|
||||||
|
env:
|
||||||
|
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
|
||||||
@@ -148,7 +148,6 @@ Current list of test tags:
|
|||||||
- `@localStorage` - Captures or generates session storage to manipulate browser state. Useful for excluding in tests which require a persistent backend (i.e. CouchDB).
|
- `@localStorage` - Captures or generates session storage to manipulate browser state. Useful for excluding in tests which require a persistent backend (i.e. CouchDB).
|
||||||
- `@snapshot` - Uses Playwright's snapshot functionality to record a copy of the DOM for direct comparison. Must be run inside of the playwright container.
|
- `@snapshot` - Uses Playwright's snapshot functionality to record a copy of the DOM for direct comparison. Must be run inside of the playwright container.
|
||||||
- `@unstable` - A new test or test which is known to be flaky.
|
- `@unstable` - A new test or test which is known to be flaky.
|
||||||
- `@2p` - Indicates that multiple users are involved, or multiple tabs/pages are used. Useful for testing multi-user interactivity.
|
|
||||||
|
|
||||||
### Continuous Integration
|
### Continuous Integration
|
||||||
|
|
||||||
|
|||||||
@@ -30,80 +30,36 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines parameters to be used in the creation of a domain object.
|
* This common function creates a `domainObject` with default options. It is the preferred way of creating objects
|
||||||
* @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
|
|
||||||
* in the e2e suite when uninterested in properties of the objects themselves.
|
* in the e2e suite when uninterested in properties of the objects themselves.
|
||||||
*
|
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
* @param {CreateObjectOptions} options
|
* @param {string} type
|
||||||
* @returns {Promise<CreatedObjectInfo>} An object containing information about the newly created domain object.
|
* @param {string | undefined} name
|
||||||
*/
|
*/
|
||||||
async function createDomainObjectWithDefaults(page, { type, name, parent = 'mine' }) {
|
async function createDomainObjectWithDefaults(page, type, name) {
|
||||||
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`);
|
|
||||||
await page.waitForLoadState('networkidle');
|
|
||||||
|
|
||||||
//Click the Create button
|
//Click the Create button
|
||||||
await page.click('button:has-text("Create")');
|
await page.click('button:has-text("Create")');
|
||||||
|
|
||||||
// Click the object specified by 'type'
|
// Click the object specified by 'type'
|
||||||
await page.click(`li:text("${type}")`);
|
await page.click(`text=${type}`);
|
||||||
|
|
||||||
// Modify the name input field of the domain object to accept 'name'
|
// Modify the name input field of the domain object to accept 'name'
|
||||||
if (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("");
|
||||||
await nameInput.fill(name);
|
await nameInput.fill(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click OK button and wait for Navigate event
|
// Click OK button and wait for Navigate event
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.waitForLoadState(),
|
page.waitForNavigation({waitUntil: 'networkidle'}),
|
||||||
page.click('[aria-label="Save"]'),
|
page.click('text=OK')
|
||||||
// Wait for Save Banner to appear
|
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the given `domainObject`'s context menu from the object tree.
|
* Open the given `domainObject`'s context menu from the object tree.
|
||||||
* Expands the 'My Items' folder if it is not already expanded.
|
* Expands the 'My Items' folder if it is not already expanded.
|
||||||
*
|
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
* @param {string} myItemsFolderName the name of the "My Items" folder
|
* @param {string} myItemsFolderName the name of the "My Items" folder
|
||||||
* @param {string} domainObjectName the display name of the `domainObject`
|
* @param {string} domainObjectName the display name of the `domainObject`
|
||||||
@@ -120,154 +76,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
|
// eslint-disable-next-line no-undef
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createDomainObjectWithDefaults,
|
createDomainObjectWithDefaults,
|
||||||
openObjectTreeContextMenu,
|
openObjectTreeContextMenu
|
||||||
getHashUrlToDomainObject,
|
|
||||||
getFocusedObjectUuid,
|
|
||||||
setFixedTimeMode,
|
|
||||||
setRealTimeMode,
|
|
||||||
setStartOffset,
|
|
||||||
setEndOffset
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,30 +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 should be used to install the Snow theme for Open MCT. Espresso is the default
|
|
||||||
// e.g.
|
|
||||||
// await page.addInitScript({ path: path.join(__dirname, 'useSnowTheme.js') });
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const openmct = window.openmct;
|
|
||||||
openmct.install(openmct.plugins.Snow());
|
|
||||||
});
|
|
||||||
@@ -32,7 +32,6 @@ const config = {
|
|||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'chrome',
|
name: 'chrome',
|
||||||
testMatch: '**/*.e2e.spec.js', // only run e2e tests
|
|
||||||
use: {
|
use: {
|
||||||
browserName: 'chromium'
|
browserName: 'chromium'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,12 @@
|
|||||||
// playwright.config.js
|
// playwright.config.js
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
/** @type {import('@playwright/test').PlaywrightTestConfig<{ theme: string }>} */
|
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
retries: 0, // visual tests should never retry due to snapshot comparison errors
|
retries: 0, // visual tests should never retry due to snapshot comparison errors
|
||||||
testDir: 'tests/visual',
|
testDir: 'tests/visual',
|
||||||
testMatch: '**/*.visual.spec.js', // only run visual tests
|
timeout: 90 * 1000,
|
||||||
timeout: 60 * 1000,
|
workers: 1, // visual tests should never run in parallel due to test pollution
|
||||||
workers: 2, //Limit to 2 for CircleCI Agent
|
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'cross-env NODE_ENV=test npm run start',
|
command: 'cross-env NODE_ENV=test npm run start',
|
||||||
url: 'http://localhost:8080/#',
|
url: 'http://localhost:8080/#',
|
||||||
@@ -16,35 +15,17 @@ const config = {
|
|||||||
reuseExistingServer: !process.env.CI
|
reuseExistingServer: !process.env.CI
|
||||||
},
|
},
|
||||||
use: {
|
use: {
|
||||||
|
browserName: "chromium",
|
||||||
baseURL: 'http://localhost:8080/',
|
baseURL: 'http://localhost:8080/',
|
||||||
headless: true, // this needs to remain headless to avoid visual changes due to GPU rendering in headed browsers
|
headless: true, // this needs to remain headless to avoid visual changes due to GPU
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
screenshot: 'on',
|
screenshot: 'on',
|
||||||
trace: 'on',
|
trace: 'off',
|
||||||
video: 'off'
|
video: 'off'
|
||||||
},
|
},
|
||||||
projects: [
|
|
||||||
{
|
|
||||||
name: 'chrome',
|
|
||||||
use: {
|
|
||||||
browserName: 'chromium'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'chrome-snow-theme',
|
|
||||||
use: {
|
|
||||||
browserName: 'chromium',
|
|
||||||
theme: 'snow'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
reporter: [
|
reporter: [
|
||||||
['list'],
|
['list'],
|
||||||
['junit', { outputFile: 'test-results/results.xml' }],
|
['junit', { outputFile: 'test-results/results.xml' }]
|
||||||
['html', {
|
|
||||||
open: 'on-failure',
|
|
||||||
outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840
|
|
||||||
}]
|
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const { test, expect } = require('./baseFixtures');
|
const { test, expect } = require('./baseFixtures');
|
||||||
// const { createDomainObjectWithDefaults } = require('./appActions');
|
const { createDomainObjectWithDefaults } = require('./appActions');
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} ObjectCreateOptions
|
* @typedef {Object} ObjectCreateOptions
|
||||||
@@ -37,16 +36,12 @@ const path = require('path');
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* **NOTE: This feature is a work-in-progress and should not currently be used.**
|
|
||||||
*
|
|
||||||
* Used to create a new domain object as a part of getOrCreateDomainObject.
|
* Used to create a new domain object as a part of getOrCreateDomainObject.
|
||||||
* @type {Map<string, string>}
|
* @type {Map<string, string>}
|
||||||
*/
|
*/
|
||||||
// const createdObjects = new Map();
|
const createdObjects = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* **NOTE: This feature is a work-in-progress and should not currently be used.**
|
|
||||||
*
|
|
||||||
* This action will create a domain object for the test to reference and return the uuid. If an object
|
* This action will create a domain object for the test to reference and return the uuid. If an object
|
||||||
* of a given name already exists, it will return the uuid of that object to the test instead of creating
|
* of a given name already exists, it will return the uuid of that object to the test instead of creating
|
||||||
* a new file. The intent is to move object creation out of test suites which are not explicitly worried
|
* a new file. The intent is to move object creation out of test suites which are not explicitly worried
|
||||||
@@ -55,29 +50,27 @@ const path = require('path');
|
|||||||
* @param {ObjectCreateOptions} options
|
* @param {ObjectCreateOptions} options
|
||||||
* @returns {Promise<string>} uuid of the domain object
|
* @returns {Promise<string>} uuid of the domain object
|
||||||
*/
|
*/
|
||||||
// async function getOrCreateDomainObject(page, options) {
|
async function getOrCreateDomainObject(page, options) {
|
||||||
// const { type, name } = options;
|
const { type, name } = options;
|
||||||
// const objectName = name ? `${type}:${name}` : type;
|
const objectName = name ? `${type}:${name}` : type;
|
||||||
|
|
||||||
// if (createdObjects.has(objectName)) {
|
if (createdObjects.has(objectName)) {
|
||||||
// return createdObjects.get(objectName);
|
return createdObjects.get(objectName);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// await createDomainObjectWithDefaults(page, type, name);
|
await createDomainObjectWithDefaults(page, type, name);
|
||||||
|
|
||||||
// // Once object is created, get the uuid from the url
|
// Once object is created, get the uuid from the url
|
||||||
// const uuid = await page.evaluate(() => {
|
const uuid = await page.evaluate(() => {
|
||||||
// return window.location.href.match(/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/)[0];
|
return window.location.href.match(/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/)[0];
|
||||||
// });
|
});
|
||||||
|
|
||||||
// createdObjects.set(objectName, uuid);
|
createdObjects.set(objectName, uuid);
|
||||||
|
|
||||||
// return uuid;
|
return uuid;
|
||||||
// }
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* **NOTE: This feature is a work-in-progress and should not currently be used.**
|
|
||||||
*
|
|
||||||
* If provided, these options will be used to get or create the desired domain object before
|
* If provided, these options will be used to get or create the desired domain object before
|
||||||
* any tests or test hooks have run.
|
* any tests or test hooks have run.
|
||||||
* The `uuid` of the `domainObject` will then be available to use within the scoped tests.
|
* The `uuid` of the `domainObject` will then be available to use within the scoped tests.
|
||||||
@@ -94,24 +87,7 @@ const path = require('path');
|
|||||||
* ```
|
* ```
|
||||||
* @type {ObjectCreateOptions}
|
* @type {ObjectCreateOptions}
|
||||||
*/
|
*/
|
||||||
// const objectCreateOptions = null;
|
const objectCreateOptions = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* The default theme for VIPER and Open MCT is the 'espresso' theme. Overriding this value with 'snow' in our playwright config.js
|
|
||||||
* will override the default theme by injecting the 'snow' theme on launch.
|
|
||||||
*
|
|
||||||
* ### Example:
|
|
||||||
* ```js
|
|
||||||
* projects: [
|
|
||||||
* {
|
|
||||||
* name: 'chrome-snow-theme',
|
|
||||||
* use: {
|
|
||||||
* browserName: 'chromium',
|
|
||||||
* theme: 'snow'
|
|
||||||
* ```
|
|
||||||
* @type {'snow' | 'espresso'}
|
|
||||||
*/
|
|
||||||
const theme = 'espresso';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the "My Items" folder in the domain object tree.
|
* The name of the "My Items" folder in the domain object tree.
|
||||||
@@ -123,39 +99,27 @@ const theme = 'espresso';
|
|||||||
const myItemsFolderName = "My Items";
|
const myItemsFolderName = "My Items";
|
||||||
|
|
||||||
exports.test = test.extend({
|
exports.test = test.extend({
|
||||||
// This should follow in the Project's configuration. Can be set to 'snow' in playwright config.js
|
|
||||||
theme: [theme, { option: true }],
|
|
||||||
// eslint-disable-next-line no-shadow
|
|
||||||
page: async ({ page, theme }, use) => {
|
|
||||||
// eslint-disable-next-line playwright/no-conditional-in-test
|
|
||||||
if (theme === 'snow') {
|
|
||||||
//inject snow theme
|
|
||||||
await page.addInitScript({ path: path.join(__dirname, './helper', './useSnowTheme.js') });
|
|
||||||
}
|
|
||||||
|
|
||||||
await use(page);
|
|
||||||
},
|
|
||||||
myItemsFolderName: [myItemsFolderName, { option: true }],
|
myItemsFolderName: [myItemsFolderName, { option: true }],
|
||||||
// eslint-disable-next-line no-shadow
|
// eslint-disable-next-line no-shadow
|
||||||
openmctConfig: async ({ myItemsFolderName }, use) => {
|
openmctConfig: async ({ myItemsFolderName }, use) => {
|
||||||
await use({ myItemsFolderName });
|
await use({ myItemsFolderName });
|
||||||
}
|
},
|
||||||
// objectCreateOptions: [objectCreateOptions, {option: true}],
|
objectCreateOptions: [objectCreateOptions, {option: true}],
|
||||||
// eslint-disable-next-line no-shadow
|
// eslint-disable-next-line no-shadow
|
||||||
// domainObject: [async ({ page, objectCreateOptions }, use) => {
|
domainObject: [async ({ page, objectCreateOptions }, use) => {
|
||||||
// // FIXME: This is a false-positive caused by a bug in the eslint-plugin-playwright rule.
|
// FIXME: This is a false-positive caused by a bug in the eslint-plugin-playwright rule.
|
||||||
// // eslint-disable-next-line playwright/no-conditional-in-test
|
// eslint-disable-next-line playwright/no-conditional-in-test
|
||||||
// if (objectCreateOptions === null) {
|
if (objectCreateOptions === null) {
|
||||||
// await use(page);
|
await use(page);
|
||||||
|
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// //Go to baseURL
|
//Go to baseURL
|
||||||
// await page.goto('./', { waitUntil: 'networkidle' });
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
// const uuid = await getOrCreateDomainObject(page, objectCreateOptions);
|
const uuid = await getOrCreateDomainObject(page, objectCreateOptions);
|
||||||
// await use({ uuid });
|
await use({ uuid });
|
||||||
// }, { auto: true }]
|
}, { auto: true }]
|
||||||
});
|
});
|
||||||
exports.expect = expect;
|
exports.expect = expect;
|
||||||
|
|||||||
@@ -1,88 +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.js');
|
|
||||||
const { createDomainObjectWithDefaults } = require('../../appActions.js');
|
|
||||||
|
|
||||||
test.describe('AppActions', () => {
|
|
||||||
test('createDomainObjectsWithDefaults', async ({ page }) => {
|
|
||||||
await page.goto('./', { waitUntil: 'networkidle' });
|
|
||||||
|
|
||||||
const e2eFolder = await createDomainObjectWithDefaults(page, {
|
|
||||||
type: 'Folder',
|
|
||||||
name: 'e2e folder'
|
|
||||||
});
|
|
||||||
|
|
||||||
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}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -29,7 +29,7 @@ relates to how we've extended it (i.e. ./e2e/baseFixtures.js) and assumptions ma
|
|||||||
const { test } = require('../../baseFixtures.js');
|
const { test } = require('../../baseFixtures.js');
|
||||||
|
|
||||||
test.describe('baseFixtures tests', () => {
|
test.describe('baseFixtures tests', () => {
|
||||||
test('Verify that tests fail if console.error is thrown', async ({ page }) => {
|
test('Verify that tests fail if console.error is thrown @framework', async ({ page }) => {
|
||||||
test.fail();
|
test.fail();
|
||||||
//Go to baseURL
|
//Go to baseURL
|
||||||
await page.goto('./', { waitUntil: 'networkidle' });
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
@@ -41,7 +41,7 @@ test.describe('baseFixtures tests', () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
});
|
});
|
||||||
test('Verify that tests pass if console.warn is thrown', async ({ page }) => {
|
test('Verify that tests pass if console.warn is thrown @framework', async ({ page }) => {
|
||||||
//Go to baseURL
|
//Go to baseURL
|
||||||
await page.goto('./', { waitUntil: 'networkidle' });
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
|||||||
@@ -52,13 +52,13 @@ const { createDomainObjectWithDefaults } = require('../../appActions');
|
|||||||
|
|
||||||
// Structure: Try to keep a single describe block per logical groups of tests. If your test runtime exceeds 5 minutes or 500 lines, it's likely that it will need to be split.
|
// Structure: Try to keep a single describe block per logical groups of tests. If your test runtime exceeds 5 minutes or 500 lines, it's likely that it will need to be split.
|
||||||
// Annotations: Please use the @unstable tag so that our automation can pick it up as a part of our test promotion pipeline.
|
// Annotations: Please use the @unstable tag so that our automation can pick it up as a part of our test promotion pipeline.
|
||||||
test.describe('Renaming Timer Object', () => {
|
test.describe('Renaming Timer Object @unstable', () => {
|
||||||
//Create a testcase name which will be obvious when it fails in CI
|
//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 }) => {
|
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
|
//Open a browser, navigate to the main page, and wait until all networkevents to resolve
|
||||||
await page.goto('./', { waitUntil: 'networkidle' });
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
//We provide some helper functions in appActions like createDomainObjectWithDefaults. This example will create a Timer object
|
//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
|
//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');
|
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Timer');
|
||||||
|
|
||||||
@@ -68,12 +68,13 @@ test.describe('Renaming Timer Object', () => {
|
|||||||
|
|
||||||
//Assert that the name has changed in the browser bar to the value we assigned above
|
//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 expect(page.locator('.l-browse-bar__object-name')).toContainText(newObjectName);
|
||||||
|
|
||||||
});
|
});
|
||||||
test('An existing Timer object can be renamed twice', async ({ page }) => {
|
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
|
//Open a browser, navigate to the main page, and wait until all networkevents to resolve
|
||||||
await page.goto('./', { waitUntil: 'networkidle' });
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
//We provide some helper functions in appActions like createDomainObjectWithDefaults. This example will create a Timer object
|
//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
|
//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');
|
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Timer');
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
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
|
//Go to baseURL
|
||||||
await page.goto('./', { waitUntil: 'networkidle' });
|
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
|
// click create button
|
||||||
await page.locator('button:has-text("Create")').click();
|
await page.locator('button:has-text("Create")').click();
|
||||||
@@ -51,12 +67,16 @@ test('Generate Visual Test Data @localStorage', async ({ page, context }) => {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.waitForNavigation(),
|
page.waitForNavigation(),
|
||||||
page.locator('text=OK').click(),
|
page.locator('text=OK').click(),
|
||||||
//Wait for Save Banner to appear
|
//Wait for Save Banner to appear1
|
||||||
page.waitForSelector('.c-message-banner__message')
|
page.waitForSelector('.c-message-banner__message')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// focus the overlay plot
|
// 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');
|
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Overlay Plot');
|
||||||
//Save localStorage for future test execution
|
//Save localStorage for future test execution
|
||||||
|
|||||||
@@ -25,22 +25,21 @@ This test suite is dedicated to testing our use of our custom fixtures to verify
|
|||||||
that they are working as expected.
|
that they are working as expected.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { test } = require('../../pluginFixtures.js');
|
const { test, expect } = require('../../pluginFixtures.js');
|
||||||
|
|
||||||
// eslint-disable-next-line playwright/no-skipped-test
|
test.describe('pluginFixtures tests', () => {
|
||||||
test.describe.skip('pluginFixtures tests', () => {
|
test.use({ domainObjectName: 'Timer' });
|
||||||
// test.use({ domainObjectName: 'Timer' });
|
let timerUUID;
|
||||||
// let timerUUID;
|
|
||||||
|
|
||||||
// test('Creates a timer object @framework @unstable', ({ domainObject }) => {
|
test('Creates a timer object @framework @unstable', ({ domainObject }) => {
|
||||||
// const { uuid } = domainObject;
|
const { uuid } = domainObject;
|
||||||
// const uuidRegexp = /[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/;
|
const uuidRegexp = /[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/;
|
||||||
// expect(uuid).toMatch(uuidRegexp);
|
expect(uuid).toMatch(uuidRegexp);
|
||||||
// timerUUID = uuid;
|
timerUUID = uuid;
|
||||||
// });
|
});
|
||||||
|
|
||||||
// test('Provides same uuid for subsequent uses of the same object @framework', ({ domainObject }) => {
|
test('Provides same uuid for subsequent uses of the same object @framework', ({ domainObject }) => {
|
||||||
// const { uuid } = domainObject;
|
const { uuid } = domainObject;
|
||||||
// expect(uuid).toEqual(timerUUID);
|
expect(uuid).toEqual(timerUUID);
|
||||||
// });
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ test.describe('Branding tests', () => {
|
|||||||
await expect(page.locator('.c-about__image')).toBeVisible();
|
await expect(page.locator('.c-about__image')).toBeVisible();
|
||||||
|
|
||||||
// Modify the Build information in 'about' Modal
|
// Modify the Build information in 'about' Modal
|
||||||
const versionInformationLocator = page.locator('ul.t-info.l-info.s-info').first();
|
const versionInformationLocator = page.locator('ul.t-info.l-info.s-info');
|
||||||
await expect(versionInformationLocator).toBeEnabled();
|
await expect(versionInformationLocator).toBeEnabled();
|
||||||
await expect.soft(versionInformationLocator).toContainText(/Version: \d/);
|
await expect.soft(versionInformationLocator).toContainText(/Version: \d/);
|
||||||
await expect.soft(versionInformationLocator).toContainText(/Build Date: ((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun))/);
|
await expect.soft(versionInformationLocator).toContainText(/Build Date: ((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun))/);
|
||||||
await expect.soft(versionInformationLocator).toContainText(/Revision: \b[0-9a-f]{5,40}\b/);
|
await expect.soft(versionInformationLocator).toContainText(/Revision: \b[0-9a-f]{5,40}\b/);
|
||||||
await expect.soft(versionInformationLocator).toContainText(/Branch: ./);
|
await expect.soft(versionInformationLocator).toContainText(/Branch: ./);
|
||||||
});
|
});
|
||||||
test('Verify Links in About Modal @2p', async ({ page }) => {
|
test('Verify Links in About Modal', async ({ page }) => {
|
||||||
// Go to baseURL
|
// Go to baseURL
|
||||||
await page.goto('./', { waitUntil: 'networkidle' });
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
|||||||
@@ -35,10 +35,7 @@ test.describe('Example Event Generator CRUD Operations', () => {
|
|||||||
//Create a name for the object
|
//Create a name for the object
|
||||||
const newObjectName = 'Test Event Generator';
|
const newObjectName = 'Test Event Generator';
|
||||||
|
|
||||||
await createDomainObjectWithDefaults(page, {
|
await createDomainObjectWithDefaults(page, 'Event Message Generator', newObjectName);
|
||||||
type: 'Event Message Generator',
|
|
||||||
name: newObjectName
|
|
||||||
});
|
|
||||||
|
|
||||||
//Assertions against newly created object which define standard behavior
|
//Assertions against newly created object which define standard behavior
|
||||||
await expect(page.waitForURL(/.*&view=table/)).toBeTruthy();
|
await expect(page.waitForURL(/.*&view=table/)).toBeTruthy();
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ demonstrate some playwright for test developers. This pattern should not be re-u
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const { test, expect } = require('../../../../pluginFixtures.js');
|
const { test, expect } = require('../../../../pluginFixtures.js');
|
||||||
const { createDomainObjectWithDefaults } = require('../../../../appActions');
|
|
||||||
|
|
||||||
let conditionSetUrl;
|
let conditionSetUrl;
|
||||||
let getConditionSetIdentifierFromUrl;
|
let getConditionSetIdentifierFromUrl;
|
||||||
@@ -179,24 +178,3 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('Basic Condition Set Use', () => {
|
|
||||||
test('Can add a condition', async ({ page }) => {
|
|
||||||
//Navigate to baseURL
|
|
||||||
await page.goto('./', { waitUntil: 'networkidle' });
|
|
||||||
|
|
||||||
// Create a new condition set
|
|
||||||
await createDomainObjectWithDefaults(page, {
|
|
||||||
type: 'Condition Set',
|
|
||||||
name: "Test Condition Set"
|
|
||||||
});
|
|
||||||
// Change the object to edit mode
|
|
||||||
await page.locator('[title="Edit"]').click();
|
|
||||||
|
|
||||||
// Click Add Condition button
|
|
||||||
await page.locator('#addCondition').click();
|
|
||||||
// Check that the new Unnamed Condition section appears
|
|
||||||
const numOfUnnamedConditions = await page.locator('text=Unnamed Condition').count();
|
|
||||||
expect(numOfUnnamedConditions).toEqual(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -28,7 +28,6 @@ but only assume that example imagery is present.
|
|||||||
|
|
||||||
const { waitForAnimations } = require('../../../../baseFixtures');
|
const { waitForAnimations } = require('../../../../baseFixtures');
|
||||||
const { test, expect } = require('../../../../pluginFixtures');
|
const { test, expect } = require('../../../../pluginFixtures');
|
||||||
const { createDomainObjectWithDefaults } = require('../../../../appActions');
|
|
||||||
const backgroundImageSelector = '.c-imagery__main-image__background-image';
|
const backgroundImageSelector = '.c-imagery__main-image__background-image';
|
||||||
const panHotkey = process.platform === 'linux' ? ['Control', 'Alt'] : ['Alt'];
|
const panHotkey = process.platform === 'linux' ? ['Control', 'Alt'] : ['Alt'];
|
||||||
const expectedAltText = process.platform === 'linux' ? 'Ctrl+Alt drag to pan' : 'Alt drag to pan';
|
const expectedAltText = process.platform === 'linux' ? 'Ctrl+Alt drag to pan' : 'Alt drag to pan';
|
||||||
@@ -40,17 +39,26 @@ test.describe('Example Imagery Object', () => {
|
|||||||
//Go to baseURL
|
//Go to baseURL
|
||||||
await page.goto('./', { waitUntil: 'networkidle' });
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
// Create a default 'Example Imagery' object
|
//Click the Create button
|
||||||
createDomainObjectWithDefaults(page, { type: 'Example Imagery' });
|
await page.click('button:has-text("Create")');
|
||||||
|
|
||||||
|
// Click text=Example Imagery
|
||||||
|
await page.click('text=Example Imagery');
|
||||||
|
|
||||||
|
// Click text=OK
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.waitForNavigation(),
|
page.waitForNavigation({waitUntil: 'networkidle'}),
|
||||||
page.locator(backgroundImageSelector).hover({trial: true}),
|
page.click('text=OK'),
|
||||||
// eslint-disable-next-line playwright/missing-playwright-await
|
//Wait for Save Banner to appear
|
||||||
expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery')
|
page.waitForSelector('.c-message-banner__message')
|
||||||
]);
|
]);
|
||||||
|
// Close Banner
|
||||||
|
await page.locator('.c-message-banner__close-button').click();
|
||||||
|
|
||||||
// Verify that the created object is focused
|
//Wait until Save Banner is gone
|
||||||
|
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
||||||
|
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery');
|
||||||
|
await page.locator(backgroundImageSelector).hover({trial: true});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can use Mouse Wheel to zoom in and out of latest image', async ({ page }) => {
|
test('Can use Mouse Wheel to zoom in and out of latest image', async ({ page }) => {
|
||||||
@@ -200,7 +208,7 @@ test.describe('Example Imagery Object', () => {
|
|||||||
const pausePlayButton = page.locator('.c-button.pause-play');
|
const pausePlayButton = page.locator('.c-button.pause-play');
|
||||||
|
|
||||||
// open the time conductor drop down
|
// open the time conductor drop down
|
||||||
await page.locator('.c-mode-button').click();
|
await page.locator('button:has-text("Fixed Timespan")').click();
|
||||||
|
|
||||||
// Click local clock
|
// Click local clock
|
||||||
await page.locator('[data-testid="conductor-modeOption-realtime"]').click();
|
await page.locator('[data-testid="conductor-modeOption-realtime"]').click();
|
||||||
@@ -524,7 +532,7 @@ test.describe('Example Imagery in Flexible layout', () => {
|
|||||||
await page.locator('.c-mode-button').click();
|
await page.locator('.c-mode-button').click();
|
||||||
|
|
||||||
// Select local clock mode
|
// Select local clock mode
|
||||||
await page.locator('[data-testid=conductor-modeOption-realtime]').nth(0).click();
|
await page.locator('[data-testid=conductor-modeOption-realtime]').click();
|
||||||
|
|
||||||
// Zoom in on next image
|
// Zoom in on next image
|
||||||
await mouseZoomIn(page);
|
await mouseZoomIn(page);
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -25,18 +25,25 @@ This test suite is dedicated to tests which verify form functionality.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const { test, expect } = require('../../../../pluginFixtures');
|
const { test, expect } = require('../../../../pluginFixtures');
|
||||||
const { createDomainObjectWithDefaults } = require('../../../../appActions');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a notebook object and adds an entry.
|
* Creates a notebook object and adds an entry.
|
||||||
* @param {import('@playwright/test').Page} - page to load
|
* @param {import('@playwright/test').Page} - page to load
|
||||||
* @param {number} [iterations = 1] - the number of entries to create
|
* @param {number} [iterations = 1] - the number of entries to create
|
||||||
*/
|
*/
|
||||||
async function createNotebookAndEntry(page, iterations = 1) {
|
async function createNotebookAndEntry(page, myItemsFolderName, iterations = 1) {
|
||||||
//Go to baseURL
|
//Go to baseURL
|
||||||
await page.goto('./', { waitUntil: 'networkidle' });
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
createDomainObjectWithDefaults(page, { type: 'Notebook' });
|
// Click button:has-text("Create")
|
||||||
|
await page.locator('button:has-text("Create")').click();
|
||||||
|
await page.locator('[title="Create and save timestamped notes with embedded object snapshots."]').click();
|
||||||
|
// Click button:has-text("OK")
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator(`[name="mctForm"] >> text=${myItemsFolderName}`).click(),
|
||||||
|
page.locator('button:has-text("OK")').click()
|
||||||
|
]);
|
||||||
|
|
||||||
for (let iteration = 0; iteration < iterations; iteration++) {
|
for (let iteration = 0; iteration < iterations; iteration++) {
|
||||||
// Click text=To start a new entry, click here or drag and drop any object
|
// Click text=To start a new entry, click here or drag and drop any object
|
||||||
@@ -45,6 +52,7 @@ async function createNotebookAndEntry(page, iterations = 1) {
|
|||||||
await page.locator(entryLocator).click();
|
await page.locator(entryLocator).click();
|
||||||
await page.locator(entryLocator).fill(`Entry ${iteration}`);
|
await page.locator(entryLocator).fill(`Entry ${iteration}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,8 +60,8 @@ async function createNotebookAndEntry(page, iterations = 1) {
|
|||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
* @param {number} [iterations = 1] - the number of entries (and tags) to create
|
* @param {number} [iterations = 1] - the number of entries (and tags) to create
|
||||||
*/
|
*/
|
||||||
async function createNotebookEntryAndTags(page, iterations = 1) {
|
async function createNotebookEntryAndTags(page, myItemsFolderName, iterations = 1) {
|
||||||
await createNotebookAndEntry(page, iterations);
|
await createNotebookAndEntry(page, myItemsFolderName, iterations);
|
||||||
|
|
||||||
for (let iteration = 0; iteration < iterations; iteration++) {
|
for (let iteration = 0; iteration < iterations; iteration++) {
|
||||||
// Click text=To start a new entry, click here or drag and drop any object
|
// Click text=To start a new entry, click here or drag and drop any object
|
||||||
@@ -73,10 +81,11 @@ async function createNotebookEntryAndTags(page, iterations = 1) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test.describe('Tagging in Notebooks @addInit', () => {
|
test.describe('Tagging in Notebooks', () => {
|
||||||
test('Can load tags', async ({ page }) => {
|
test('Can load tags', async ({ page, openmctConfig }) => {
|
||||||
|
const { myItemsFolderName } = openmctConfig;
|
||||||
|
|
||||||
await createNotebookAndEntry(page);
|
await createNotebookAndEntry(page, myItemsFolderName);
|
||||||
// Click text=To start a new entry, click here or drag and drop any object
|
// Click text=To start a new entry, click here or drag and drop any object
|
||||||
await page.locator('button:has-text("Add Tag")').click();
|
await page.locator('button:has-text("Add Tag")').click();
|
||||||
|
|
||||||
@@ -87,8 +96,10 @@ test.describe('Tagging in Notebooks @addInit', () => {
|
|||||||
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Drilling");
|
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Drilling");
|
||||||
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Driving");
|
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Driving");
|
||||||
});
|
});
|
||||||
test('Can add tags', async ({ page }) => {
|
test('Can add tags', async ({ page, openmctConfig }) => {
|
||||||
await createNotebookEntryAndTags(page);
|
const { myItemsFolderName } = openmctConfig;
|
||||||
|
|
||||||
|
await createNotebookEntryAndTags(page, myItemsFolderName);
|
||||||
|
|
||||||
await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Science");
|
await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Science");
|
||||||
await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Driving");
|
await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Driving");
|
||||||
@@ -102,8 +113,10 @@ test.describe('Tagging in Notebooks @addInit', () => {
|
|||||||
await expect(page.locator('[aria-label="Autocomplete Options"]')).not.toContainText("Driving");
|
await expect(page.locator('[aria-label="Autocomplete Options"]')).not.toContainText("Driving");
|
||||||
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Drilling");
|
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Drilling");
|
||||||
});
|
});
|
||||||
test('Can search for tags', async ({ page }) => {
|
test('Can search for tags', async ({ page, openmctConfig }) => {
|
||||||
await createNotebookEntryAndTags(page);
|
const { myItemsFolderName } = openmctConfig;
|
||||||
|
|
||||||
|
await createNotebookEntryAndTags(page, myItemsFolderName);
|
||||||
// Click [aria-label="OpenMCT Search"] input[type="search"]
|
// Click [aria-label="OpenMCT Search"] input[type="search"]
|
||||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||||
// Fill [aria-label="OpenMCT Search"] input[type="search"]
|
// Fill [aria-label="OpenMCT Search"] input[type="search"]
|
||||||
@@ -126,8 +139,10 @@ test.describe('Tagging in Notebooks @addInit', () => {
|
|||||||
await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible();
|
await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can delete tags', async ({ page }) => {
|
test('Can delete tags', async ({ page, openmctConfig }) => {
|
||||||
await createNotebookEntryAndTags(page);
|
const { myItemsFolderName } = openmctConfig;
|
||||||
|
|
||||||
|
await createNotebookEntryAndTags(page, myItemsFolderName);
|
||||||
await page.locator('[aria-label="Notebook Entries"]').click();
|
await page.locator('[aria-label="Notebook Entries"]').click();
|
||||||
// Delete Driving
|
// Delete Driving
|
||||||
await page.locator('text=Science Driving Add Tag >> button').nth(1).click();
|
await page.locator('text=Science Driving Add Tag >> button').nth(1).click();
|
||||||
@@ -139,31 +154,28 @@ test.describe('Tagging in Notebooks @addInit', () => {
|
|||||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
|
||||||
await expect(page.locator('[aria-label="Search Result"]')).not.toContainText("Driving");
|
await expect(page.locator('[aria-label="Search Result"]')).not.toContainText("Driving");
|
||||||
});
|
});
|
||||||
|
test('Tags persist across reload', async ({ page, openmctConfig }) => {
|
||||||
|
const { myItemsFolderName } = openmctConfig;
|
||||||
|
|
||||||
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
|
//Go to baseURL
|
||||||
await page.goto('./', { waitUntil: 'networkidle' });
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
await createDomainObjectWithDefaults(page, { type: 'Clock' });
|
// Create a clock object we can navigate to
|
||||||
|
await page.click('button:has-text("Create")');
|
||||||
|
|
||||||
|
// Click Clock
|
||||||
|
await page.click('text=Clock');
|
||||||
|
// Click button:has-text("OK")
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator(`[name="mctForm"] >> text=${myItemsFolderName}`).click(),
|
||||||
|
page.locator('button:has-text("OK")').click()
|
||||||
|
]);
|
||||||
|
|
||||||
|
await page.click('.c-disclosure-triangle');
|
||||||
|
|
||||||
const ITERATIONS = 4;
|
const ITERATIONS = 4;
|
||||||
await createNotebookEntryAndTags(page, ITERATIONS);
|
await createNotebookEntryAndTags(page, myItemsFolderName, ITERATIONS);
|
||||||
|
|
||||||
for (let iteration = 0; iteration < ITERATIONS; iteration++) {
|
for (let iteration = 0; iteration < ITERATIONS; iteration++) {
|
||||||
const entryLocator = `[aria-label="Notebook Entry"] >> nth = ${iteration}`;
|
const entryLocator = `[aria-label="Notebook Entry"] >> nth = ${iteration}`;
|
||||||
@@ -171,11 +183,6 @@ test.describe('Tagging in Notebooks @addInit', () => {
|
|||||||
await expect(page.locator(entryLocator)).toContainText("Driving");
|
await expect(page.locator(entryLocator)).toContainText("Driving");
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
page.waitForNavigation(),
|
|
||||||
page.goto('./#/browse/mine?hideTree=false'),
|
|
||||||
page.click('.c-disclosure-triangle')
|
|
||||||
]);
|
|
||||||
// Click Unnamed Clock
|
// Click Unnamed Clock
|
||||||
await page.click('text="Unnamed Clock"');
|
await page.click('text="Unnamed Clock"');
|
||||||
|
|
||||||
|
|||||||
@@ -20,26 +20,55 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
const { createDomainObjectWithDefaults } = require('../../../../appActions');
|
|
||||||
const { test, expect } = require('../../../../pluginFixtures');
|
const { test, expect } = require('../../../../pluginFixtures');
|
||||||
|
|
||||||
test.describe('Telemetry Table', () => {
|
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({
|
test.info().annotations.push({
|
||||||
type: 'issue',
|
type: 'issue',
|
||||||
description: 'https://github.com/nasa/openmct/issues/5113'
|
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' });
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
const table = await createDomainObjectWithDefaults(page, { type: 'Telemetry Table' });
|
// Click create button
|
||||||
await createDomainObjectWithDefaults(page, {
|
await page.locator(createButton).click();
|
||||||
type: 'Sine Wave Generator',
|
await page.locator('li:has-text("Telemetry Table")').click();
|
||||||
parent: table.uuid
|
|
||||||
});
|
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
|
// 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
|
// Click pause button
|
||||||
const pauseButton = page.locator('button.c-button.icon-pause');
|
const pauseButton = page.locator('button.c-button.icon-pause');
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
const { test, expect } = require('../../../../baseFixtures');
|
const { test, expect } = require('../../../../baseFixtures');
|
||||||
const { setFixedTimeMode, setRealTimeMode, setStartOffset, setEndOffset } = require('../../../../appActions');
|
|
||||||
|
|
||||||
test.describe('Time conductor operations', () => {
|
test.describe('Time conductor operations', () => {
|
||||||
test('validate start time does not exceeds end time', async ({ page }) => {
|
test('validate start time does not exceeds end time', async ({ page }) => {
|
||||||
@@ -143,8 +142,93 @@ test.describe('Time conductor input fields real-time mode', () => {
|
|||||||
await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:01');
|
await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:01');
|
||||||
|
|
||||||
// Verify url parameters persist after mode switch
|
// Verify url parameters persist after mode switch
|
||||||
await page.waitForNavigation({ waitUntil: 'networkidle' });
|
await page.waitForNavigation();
|
||||||
expect(page.url()).toContain(`startDelta=${startDelta}`);
|
expect(page.url()).toContain(`startDelta=${startDelta}`);
|
||||||
expect(page.url()).toContain(`endDelta=${endDelta}`);
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,14 +21,14 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
const { test, expect } = require('../../../../pluginFixtures');
|
const { test, expect } = require('../../../../pluginFixtures');
|
||||||
const { openObjectTreeContextMenu, createDomainObjectWithDefaults } = require('../../../../appActions');
|
const { openObjectTreeContextMenu } = require('../../../../appActions');
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
type: 'Timer'
|
||||||
|
};
|
||||||
|
|
||||||
test.describe('Timer', () => {
|
test.describe('Timer', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.use({ objectCreateOptions: options });
|
||||||
await page.goto('./', { waitUntil: 'networkidle' });
|
|
||||||
await createDomainObjectWithDefaults(page, { type: 'timer' });
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Can perform actions on the Timer', async ({ page, openmctConfig }) => {
|
test('Can perform actions on the Timer', async ({ page, openmctConfig }) => {
|
||||||
test.info().annotations.push({
|
test.info().annotations.push({
|
||||||
type: 'issue',
|
type: 'issue',
|
||||||
|
|||||||
@@ -107,9 +107,6 @@ test.describe("Search Tests @unstable", () => {
|
|||||||
|
|
||||||
// Verify that no results are found
|
// Verify that no results are found
|
||||||
expect(await searchResults.count()).toBe(0);
|
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 }) => {
|
test('Validate single object in search result', async ({ page }) => {
|
||||||
|
|||||||
@@ -32,31 +32,39 @@ Note: Larger testsuite sizes are OK due to the setup time associated with these
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const { test, expect } = require('../../pluginFixtures');
|
const { test, expect } = require('../../baseFixtures.js');
|
||||||
const { createDomainObjectWithDefaults } = require('../../appActions');
|
|
||||||
const percySnapshot = require('@percy/playwright');
|
const percySnapshot = require('@percy/playwright');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
const VISUAL_GRACE_PERIOD = 5 * 1000; //Lets the application "simmer" before the snapshot is taken
|
||||||
|
|
||||||
const CUSTOM_NAME = 'CUSTOM_NAME';
|
const CUSTOM_NAME = 'CUSTOM_NAME';
|
||||||
|
|
||||||
test.describe('Visual - addInit', () => {
|
test.describe('Visual - addInit', () => {
|
||||||
test.use({
|
test.use({
|
||||||
clockOptions: {
|
clockOptions: {
|
||||||
now: 0, //Set browser clock to UNIX Epoch
|
shouldAdvanceTime: true
|
||||||
shouldAdvanceTime: false //Don't advance the clock
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Restricted Notebook is visually correct @addInit @unstable', async ({ page, theme }) => {
|
test('Restricted Notebook is visually correct @addInit', async ({ page }) => {
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
await page.addInitScript({ path: path.join(__dirname, '../../helper', './addInitRestrictedNotebook.js') });
|
await page.addInitScript({ path: path.join(__dirname, '../../helper', './addInitRestrictedNotebook.js') });
|
||||||
//Go to baseURL
|
//Go to baseURL
|
||||||
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
|
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
|
||||||
|
//Click the Create button
|
||||||
await createDomainObjectWithDefaults(page, { type: CUSTOM_NAME });
|
await page.click('button:has-text("Create")');
|
||||||
|
// Click text=CUSTOM_NAME
|
||||||
|
await page.click(`text=${CUSTOM_NAME}`);
|
||||||
|
// Click text=OK
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation({waitUntil: 'networkidle'}),
|
||||||
|
page.click('text=OK')
|
||||||
|
]);
|
||||||
|
|
||||||
// Take a snapshot of the newly created CUSTOM_NAME notebook
|
// Take a snapshot of the newly created CUSTOM_NAME notebook
|
||||||
await percySnapshot(page, `Restricted Notebook with CUSTOM_NAME (theme: '${theme}')`);
|
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||||
|
await percySnapshot(page, 'Restricted Notebook with CUSTOM_NAME');
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,21 +25,27 @@ Collection of Visual Tests set to run in a default context. The tests within thi
|
|||||||
are only meant to run against openmct's app.js started by `npm run start` within the
|
are only meant to run against openmct's app.js started by `npm run start` within the
|
||||||
`./e2e/playwright-visual.config.js` file.
|
`./e2e/playwright-visual.config.js` file.
|
||||||
|
|
||||||
|
These should only use functional expect statements to verify assumptions about the state
|
||||||
|
in a test and not for functional verification of correctness. Visual tests are not supposed
|
||||||
|
to "fail" on assertions. Instead, they should be used to detect changes between builds or branches.
|
||||||
|
|
||||||
|
Note: Larger testsuite sizes are OK due to the setup time associated with these tests.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { test, expect } = require('../../pluginFixtures');
|
const { test, expect } = require('../../baseFixtures.js');
|
||||||
const percySnapshot = require('@percy/playwright');
|
const percySnapshot = require('@percy/playwright');
|
||||||
|
|
||||||
test.describe('Visual - Controlled Clock @localStorage', () => {
|
test.describe('Visual - Controlled Clock', () => {
|
||||||
test.use({
|
test.use({
|
||||||
storageState: './e2e/test-data/VisualTestData_storage.json',
|
storageState: './e2e/test-data/VisualTestData_storage.json',
|
||||||
clockOptions: {
|
clockOptions: {
|
||||||
now: 0, //Set browser clock to UNIX Epoch
|
now: 0, //Set browser clock to UNIX Epoch
|
||||||
shouldAdvanceTime: false //Don't advance the clock
|
shouldAdvanceTime: false, //Don't advance the clock
|
||||||
|
toFake: ["setTimeout", "nextTick"]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Overlay Plot Loading Indicator @localStorage', async ({ page, theme }) => {
|
test('Overlay Plot Loading Indicator @localstorage', async ({ page }) => {
|
||||||
// Go to baseURL
|
// Go to baseURL
|
||||||
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
|
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
@@ -51,6 +57,6 @@ test.describe('Visual - Controlled Clock @localStorage', () => {
|
|||||||
await page.locator('canvas >> nth=1').hover({trial: true});
|
await page.locator('canvas >> nth=1').hover({trial: true});
|
||||||
|
|
||||||
//Take snapshot of Sine Wave Generator within Overlay Plot
|
//Take snapshot of Sine Wave Generator within Overlay Plot
|
||||||
await percySnapshot(page, `SineWaveInOverlayPlot (theme: '${theme}')`);
|
await percySnapshot(page, 'SineWaveInOverlayPlot');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,62 +32,85 @@ to "fail" on assertions. Instead, they should be used to detect changes between
|
|||||||
Note: Larger testsuite sizes are OK due to the setup time associated with these tests.
|
Note: Larger testsuite sizes are OK due to the setup time associated with these tests.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { test, expect } = require('../../pluginFixtures');
|
const { test, expect } = require('../../baseFixtures.js');
|
||||||
const percySnapshot = require('@percy/playwright');
|
const percySnapshot = require('@percy/playwright');
|
||||||
const { createDomainObjectWithDefaults } = require('../../appActions');
|
const VISUAL_GRACE_PERIOD = 5 * 1000; //Lets the application "simmer" before the snapshot is taken
|
||||||
|
|
||||||
test.describe('Visual - Default', () => {
|
test.describe('Visual - Default', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
//Go to baseURL and Hide Tree
|
|
||||||
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
|
|
||||||
});
|
|
||||||
test.use({
|
test.use({
|
||||||
clockOptions: {
|
clockOptions: {
|
||||||
now: 0, //Set browser clock to UNIX Epoch
|
now: 0,
|
||||||
shouldAdvanceTime: false //Don't advance the clock
|
shouldAdvanceTime: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Visual - Root and About', async ({ page, theme }) => {
|
test('Visual - Root and About', async ({ page }) => {
|
||||||
|
// Go to baseURL
|
||||||
|
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
// Verify that Create button is actionable
|
// Verify that Create button is actionable
|
||||||
await expect(page.locator('button:has-text("Create")')).toBeEnabled();
|
await expect(page.locator('button:has-text("Create")')).toBeEnabled();
|
||||||
|
|
||||||
// Take a snapshot of the Dashboard
|
// Take a snapshot of the Dashboard
|
||||||
await percySnapshot(page, `Root (theme: '${theme}')`);
|
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||||
|
await percySnapshot(page, 'Root');
|
||||||
|
|
||||||
// Click About button
|
// Click About button
|
||||||
await page.click('.l-shell__app-logo');
|
await page.click('.l-shell__app-logo');
|
||||||
|
|
||||||
// Modify the Build information in 'about' to be consistent run-over-run
|
// Modify the Build information in 'about' to be consistent run-over-run
|
||||||
const versionInformationLocator = page.locator('ul.t-info.l-info.s-info').first();
|
const versionInformationLocator = page.locator('ul.t-info.l-info.s-info');
|
||||||
await expect(versionInformationLocator).toBeEnabled();
|
await expect(versionInformationLocator).toBeEnabled();
|
||||||
await versionInformationLocator.evaluate(node => node.innerHTML = '<li>Version: visual-snapshot</li> <li>Build Date: Mon Nov 15 2021 08:07:51 GMT-0800 (Pacific Standard Time)</li> <li>Revision: 93049cdbc6c047697ca204893db9603b864b8c9f</li> <li>Branch: master</li>');
|
await versionInformationLocator.evaluate(node => node.innerHTML = '<li>Version: visual-snapshot</li> <li>Build Date: Mon Nov 15 2021 08:07:51 GMT-0800 (Pacific Standard Time)</li> <li>Revision: 93049cdbc6c047697ca204893db9603b864b8c9f</li> <li>Branch: master</li>');
|
||||||
|
|
||||||
// Take a snapshot of the About modal
|
// Take a snapshot of the About modal
|
||||||
await percySnapshot(page, `About (theme: '${theme}')`);
|
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||||
|
await percySnapshot(page, 'About');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.fixme('Visual - Default Condition Set', async ({ page, theme }) => {
|
test('Visual - Default Condition Set', async ({ page }) => {
|
||||||
|
//Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
await createDomainObjectWithDefaults(page, { type: 'Condition Set' });
|
//Click the Create button
|
||||||
|
await page.click('button:has-text("Create")');
|
||||||
|
|
||||||
|
// Click text=Condition Set
|
||||||
|
await page.click('text=Condition Set');
|
||||||
|
|
||||||
|
// Click text=OK
|
||||||
|
await page.click('text=OK');
|
||||||
|
|
||||||
// Take a snapshot of the newly created Condition Set object
|
// Take a snapshot of the newly created Condition Set object
|
||||||
await percySnapshot(page, `Default Condition Set (theme: '${theme}')`);
|
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||||
|
await percySnapshot(page, 'Default Condition Set');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.fixme('Visual - Default Condition Widget', async ({ page, theme }) => {
|
test.fixme('Visual - Default Condition Widget', async ({ page }) => {
|
||||||
test.info().annotations.push({
|
test.info().annotations.push({
|
||||||
type: 'issue',
|
type: 'issue',
|
||||||
description: 'https://github.com/nasa/openmct/issues/5349'
|
description: 'https://github.com/nasa/openmct/issues/5349'
|
||||||
});
|
});
|
||||||
|
//Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
await createDomainObjectWithDefaults(page, { type: 'Condition Widget' });
|
//Click the Create button
|
||||||
|
await page.click('button:has-text("Create")');
|
||||||
|
|
||||||
|
// Click text=Condition Widget
|
||||||
|
await page.click('text=Condition Widget');
|
||||||
|
|
||||||
|
// Click text=OK
|
||||||
|
await page.click('text=OK');
|
||||||
|
|
||||||
// Take a snapshot of the newly created Condition Widget object
|
// Take a snapshot of the newly created Condition Widget object
|
||||||
await percySnapshot(page, `Default Condition Widget (theme: '${theme}')`);
|
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||||
|
await percySnapshot(page, 'Default Condition Widget');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Visual - Time Conductor start time is less than end time', async ({ page, theme }) => {
|
test('Visual - Time Conductor start time is less than end time', async ({ page }) => {
|
||||||
|
//Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
const year = new Date().getFullYear();
|
const year = new Date().getFullYear();
|
||||||
|
|
||||||
let startDate = 'xxxx-01-01 01:00:00.000Z';
|
let startDate = 'xxxx-01-01 01:00:00.000Z';
|
||||||
@@ -100,14 +123,16 @@ test.describe('Visual - Default', () => {
|
|||||||
await page.locator('input[type="text"]').first().fill(startDate.toString());
|
await page.locator('input[type="text"]').first().fill(startDate.toString());
|
||||||
|
|
||||||
// verify no error msg
|
// verify no error msg
|
||||||
await percySnapshot(page, `Default Time conductor (theme: '${theme}')`);
|
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||||
|
await percySnapshot(page, 'Default Time conductor');
|
||||||
|
|
||||||
startDate = (year + 1) + startDate.substring(4);
|
startDate = (year + 1) + startDate.substring(4);
|
||||||
await page.locator('input[type="text"]').first().fill(startDate.toString());
|
await page.locator('input[type="text"]').first().fill(startDate.toString());
|
||||||
await page.locator('input[type="text"]').nth(1).click();
|
await page.locator('input[type="text"]').nth(1).click();
|
||||||
|
|
||||||
// verify error msg for start time (unable to capture snapshot of popup)
|
// verify error msg for start time (unable to capture snapshot of popup)
|
||||||
await percySnapshot(page, `Start time error (theme: '${theme}')`);
|
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||||
|
await percySnapshot(page, 'Start time error');
|
||||||
|
|
||||||
startDate = (year - 1) + startDate.substring(4);
|
startDate = (year - 1) + startDate.substring(4);
|
||||||
await page.locator('input[type="text"]').first().fill(startDate.toString());
|
await page.locator('input[type="text"]').first().fill(startDate.toString());
|
||||||
@@ -118,51 +143,79 @@ test.describe('Visual - Default', () => {
|
|||||||
await page.locator('input[type="text"]').first().click();
|
await page.locator('input[type="text"]').first().click();
|
||||||
|
|
||||||
// verify error msg for end time (unable to capture snapshot of popup)
|
// verify error msg for end time (unable to capture snapshot of popup)
|
||||||
await percySnapshot(page, `End time error (theme: '${theme}')`);
|
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||||
|
await percySnapshot(page, 'End time error');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Visual - Sine Wave Generator Form', async ({ page, theme }) => {
|
test('Visual - Sine Wave Generator Form', async ({ page }) => {
|
||||||
|
//Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
//Click the Create button
|
//Click the Create button
|
||||||
await page.click('button:has-text("Create")');
|
await page.click('button:has-text("Create")');
|
||||||
|
|
||||||
// Click text=Sine Wave Generator
|
// Click text=Sine Wave Generator
|
||||||
await page.click('text=Sine Wave Generator');
|
await page.click('text=Sine Wave Generator');
|
||||||
|
|
||||||
await percySnapshot(page, `Default Sine Wave Generator Form (theme: '${theme}')`);
|
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||||
|
await percySnapshot(page, 'Default Sine Wave Generator Form');
|
||||||
|
|
||||||
await page.locator('.field.control.l-input-sm input').first().click();
|
await page.locator('.field.control.l-input-sm input').first().click();
|
||||||
await page.locator('.field.control.l-input-sm input').first().fill('');
|
await page.locator('.field.control.l-input-sm input').first().fill('');
|
||||||
|
|
||||||
// Validate red x mark
|
// Validate red x mark
|
||||||
await percySnapshot(page, `removed amplitude property value (theme: '${theme}')`);
|
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||||
|
await percySnapshot(page, 'removed amplitude property value');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.fixme('Visual - Save Successful Banner', async ({ page, theme }) => {
|
test('Visual - Save Successful Banner', async ({ page }) => {
|
||||||
await createDomainObjectWithDefaults(page, { type: 'Timer' });
|
//Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
//Click the Create button
|
||||||
|
await page.click('button:has-text("Create")');
|
||||||
|
|
||||||
|
//NOTE Something other than example imagery
|
||||||
|
await page.click('text=Timer');
|
||||||
|
|
||||||
|
// Click text=OK
|
||||||
|
await page.click('text=OK');
|
||||||
await page.locator('.c-message-banner__message').hover({ trial: true });
|
await page.locator('.c-message-banner__message').hover({ trial: true });
|
||||||
await percySnapshot(page, `Banner message shown (theme: '${theme}')`);
|
await percySnapshot(page, 'Banner message shown');
|
||||||
|
|
||||||
//Wait until Save Banner is gone
|
//Wait until Save Banner is gone
|
||||||
await page.locator('.c-message-banner__close-button').click();
|
|
||||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
||||||
await percySnapshot(page, `Banner message gone (theme: '${theme}')`);
|
await percySnapshot(page, 'Banner message gone');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Visual - Display Layout Icon is correct', async ({ page, theme }) => {
|
test('Visual - Display Layout Icon is correct', async ({ page }) => {
|
||||||
|
//Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
//Click the Create button
|
//Click the Create button
|
||||||
await page.click('button:has-text("Create")');
|
await page.click('button:has-text("Create")');
|
||||||
|
|
||||||
//Hover on Display Layout option.
|
//Hover on Display Layout option.
|
||||||
await page.locator('text=Display Layout').hover();
|
await page.locator('text=Display Layout').hover();
|
||||||
await percySnapshot(page, `Display Layout Create Menu (theme: '${theme}')`);
|
await percySnapshot(page, 'Display Layout Create Menu');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test.fixme('Visual - Default Gauge is correct', async ({ page, theme }) => {
|
test('Visual - Default Gauge is correct', async ({ page }) => {
|
||||||
await createDomainObjectWithDefaults(page, { type: 'Gauge' });
|
|
||||||
|
//Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
//Click the Create button
|
||||||
|
await page.click('button:has-text("Create")');
|
||||||
|
|
||||||
|
await page.click('text=Gauge');
|
||||||
|
|
||||||
|
await page.click('text=OK');
|
||||||
|
|
||||||
// Take a snapshot of the newly created Gauge object
|
// Take a snapshot of the newly created Gauge object
|
||||||
await percySnapshot(page, `Default Gauge (theme: '${theme}')`);
|
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||||
|
await percySnapshot(page, 'Default Gauge');
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,61 +24,81 @@
|
|||||||
This test suite is dedicated to tests which verify search functionality.
|
This test suite is dedicated to tests which verify search functionality.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { test, expect } = require('../../pluginFixtures');
|
const { test, expect } = require('../../baseFixtures.js');
|
||||||
const { createDomainObjectWithDefaults } = require('../../appActions');
|
|
||||||
|
|
||||||
const percySnapshot = require('@percy/playwright');
|
const percySnapshot = require('@percy/playwright');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a notebook object and adds an entry.
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
*/
|
||||||
|
async function createClockAndDisplayLayout(page) {
|
||||||
|
//Go to baseURL
|
||||||
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
// Click button:has-text("Create")
|
||||||
|
await page.locator('button:has-text("Create")').click();
|
||||||
|
// Click li:has-text("Notebook")
|
||||||
|
await page.locator('li:has-text("Clock")').click();
|
||||||
|
// Click button:has-text("OK")
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator('button:has-text("OK")').click()
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Click a:has-text("My Items")
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator('a:has-text("My Items") >> nth=0').click()
|
||||||
|
]);
|
||||||
|
// Click button:has-text("Create")
|
||||||
|
await page.locator('button:has-text("Create")').click();
|
||||||
|
// Click li:has-text("Notebook")
|
||||||
|
await page.locator('li:has-text("Display Layout")').click();
|
||||||
|
// Click button:has-text("OK")
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator('button:has-text("OK")').click()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
test.describe('Grand Search', () => {
|
test.describe('Grand Search', () => {
|
||||||
test.beforeEach(async ({ page, theme }) => {
|
test('Can search for objects, and subsequent search dropdown behaves properly', async ({ page }) => {
|
||||||
//Go to baseURL and Hide Tree
|
await createClockAndDisplayLayout(page);
|
||||||
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
|
|
||||||
});
|
|
||||||
test.use({
|
|
||||||
clockOptions: {
|
|
||||||
now: 0, //Set browser clock to UNIX Epoch
|
|
||||||
shouldAdvanceTime: false //Don't advance the clock
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//This needs to be rewritten to use a non clock or non display layout object
|
|
||||||
test('Can search for objects, and subsequent search dropdown behaves properly @unstable', async ({ page, theme }) => {
|
|
||||||
// await createDomainObjectWithDefaults(page, 'Display Layout');
|
|
||||||
// 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
|
|
||||||
});
|
|
||||||
|
|
||||||
// Click [aria-label="OpenMCT Search"] input[type="search"]
|
// Click [aria-label="OpenMCT Search"] input[type="search"]
|
||||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||||
// Fill [aria-label="OpenMCT Search"] input[type="search"]
|
// Fill [aria-label="OpenMCT Search"] input[type="search"]
|
||||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill(folder1);
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Cl');
|
||||||
await expect(page.locator('[aria-label="Search Result"]')).toContainText(folder1);
|
await expect(page.locator('[aria-label="Search Result"]')).toContainText('Clock');
|
||||||
await percySnapshot(page, 'Searching for Folder Object');
|
await percySnapshot(page, 'Searching for Clocks');
|
||||||
|
// Click text=Elements >> nth=0
|
||||||
|
await page.locator('text=Elements').first().click();
|
||||||
|
await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible();
|
||||||
|
|
||||||
|
// Click [aria-label="OpenMCT Search"] [aria-label="Search Input"]
|
||||||
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click();
|
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click();
|
||||||
|
// Click [aria-label="Unnamed Clock clock result"] >> text=Unnamed Clock
|
||||||
await page.locator('[aria-label="Unnamed Clock clock result"] >> text=Unnamed Clock').click();
|
await page.locator('[aria-label="Unnamed Clock clock result"] >> text=Unnamed Clock').click();
|
||||||
await percySnapshot(page, 'Preview for clock should display when editing enabled and search item clicked');
|
await percySnapshot(page, 'Preview for clock should display when editing enabled and search item clicked');
|
||||||
|
|
||||||
|
// Click [aria-label="Close"]
|
||||||
await page.locator('[aria-label="Close"]').click();
|
await page.locator('[aria-label="Close"]').click();
|
||||||
await percySnapshot(page, 'Search should still be showing after preview closed');
|
await percySnapshot(page, 'Search should still be showing after preview closed');
|
||||||
|
|
||||||
|
// Click text=Snapshot Save and Finish Editing Save and Continue Editing >> button >> nth=1
|
||||||
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
||||||
|
// Click text=Save and Finish Editing
|
||||||
await page.locator('text=Save and Finish Editing').click();
|
await page.locator('text=Save and Finish Editing').click();
|
||||||
|
// Click [aria-label="OpenMCT Search"] [aria-label="Search Input"]
|
||||||
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click();
|
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click();
|
||||||
|
// Fill [aria-label="OpenMCT Search"] [aria-label="Search Input"]
|
||||||
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').fill('Cl');
|
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').fill('Cl');
|
||||||
|
// Click text=Unnamed Clock
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.waitForNavigation(),
|
page.waitForNavigation(),
|
||||||
page.locator('text=Unnamed Clock').click()
|
page.locator('text=Unnamed Clock').click()
|
||||||
]);
|
]);
|
||||||
await percySnapshot(page, `Clicking on search results should navigate to them if not editing (theme: '${theme}')`);
|
await percySnapshot(page, 'Clicking on search results should navigate to them if not editing');
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
12
package.json
12
package.json
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "openmct",
|
"name": "openmct",
|
||||||
"version": "2.0.7",
|
"version": "2.1.0-SNAPSHOT",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "7.18.9",
|
"@babel/eslint-parser": "7.18.2",
|
||||||
"@braintree/sanitize-url": "6.0.0",
|
"@braintree/sanitize-url": "6.0.0",
|
||||||
"@percy/cli": "1.7.2",
|
"@percy/cli": "1.2.1",
|
||||||
"@percy/playwright": "1.0.4",
|
"@percy/playwright": "1.0.4",
|
||||||
"@playwright/test": "1.23.0",
|
"@playwright/test": "1.23.0",
|
||||||
"@types/eventemitter3": "^1.0.0",
|
"@types/eventemitter3": "^1.0.0",
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"eslint": "8.18.0",
|
"eslint": "8.18.0",
|
||||||
"eslint-plugin-compat": "4.0.2",
|
"eslint-plugin-compat": "4.0.2",
|
||||||
"eslint-plugin-playwright": "0.10.0",
|
"eslint-plugin-playwright": "0.10.0",
|
||||||
"eslint-plugin-vue": "9.3.0",
|
"eslint-plugin-vue": "9.1.1",
|
||||||
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
|
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
|
||||||
"eventemitter3": "1.2.0",
|
"eventemitter3": "1.2.0",
|
||||||
"express": "4.13.1",
|
"express": "4.13.1",
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
"git-rev-sync": "3.0.2",
|
"git-rev-sync": "3.0.2",
|
||||||
"html2canvas": "1.4.1",
|
"html2canvas": "1.4.1",
|
||||||
"imports-loader": "0.8.0",
|
"imports-loader": "0.8.0",
|
||||||
"jasmine-core": "4.3.0",
|
"jasmine-core": "4.2.0",
|
||||||
"jsdoc": "3.6.11",
|
"jsdoc": "3.6.11",
|
||||||
"karma": "6.3.20",
|
"karma": "6.3.20",
|
||||||
"karma-chrome-launcher": "3.1.1",
|
"karma-chrome-launcher": "3.1.1",
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
"test:e2e:unstable": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @unstable",
|
"test:e2e:unstable": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @unstable",
|
||||||
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome",
|
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome",
|
||||||
"test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @snapshot --update-snapshots",
|
"test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @snapshot --update-snapshots",
|
||||||
"test:e2e:visual": "percy exec --config ./e2e/.percy.yml -- npx playwright test --config=e2e/playwright-visual.config.js --grep-invert @unstable",
|
"test:e2e:visual": "percy exec --config ./e2e/.percy.yml -- npx playwright test --config=e2e/playwright-visual.config.js",
|
||||||
"test:e2e:full": "npx playwright test --config=e2e/playwright-ci.config.js",
|
"test:e2e:full": "npx playwright test --config=e2e/playwright-ci.config.js",
|
||||||
"test:perf": "npx playwright test --config=e2e/playwright-performance.config.js",
|
"test:perf": "npx playwright test --config=e2e/playwright-performance.config.js",
|
||||||
"test:watch": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --no-single-run",
|
"test:watch": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --no-single-run",
|
||||||
|
|||||||
@@ -40,8 +40,6 @@ const ANNOTATION_TYPES = Object.freeze({
|
|||||||
PLOT_SPATIAL: 'PLOT_SPATIAL'
|
PLOT_SPATIAL: 'PLOT_SPATIAL'
|
||||||
});
|
});
|
||||||
|
|
||||||
const ANNOTATION_TYPE = 'annotation';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} Tag
|
* @typedef {Object} Tag
|
||||||
* @property {String} key a unique identifier for the 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.ANNOTATION_TYPES = ANNOTATION_TYPES;
|
||||||
|
|
||||||
this.openmct.types.addType(ANNOTATION_TYPE, {
|
this.openmct.types.addType('annotation', {
|
||||||
name: 'Annotation',
|
name: 'Annotation',
|
||||||
description: 'A user created note or comment about time ranges, pixel space, and geospatial features.',
|
description: 'A user created note or comment about time ranges, pixel space, and geospatial features.',
|
||||||
creatable: false,
|
creatable: false,
|
||||||
@@ -138,10 +136,6 @@ export default class AnnotationAPI extends EventEmitter {
|
|||||||
this.availableTags[tagKey] = tagsDefinition;
|
this.availableTags[tagKey] = tagsDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
isAnnotation(domainObject) {
|
|
||||||
return domainObject && (domainObject.type === ANNOTATION_TYPE);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAvailableTags() {
|
getAvailableTags() {
|
||||||
if (this.availableTags) {
|
if (this.availableTags) {
|
||||||
const rearrangedToArray = Object.keys(this.availableTags).map(tagKey => {
|
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 searchResults = (await Promise.all(this.openmct.objects.search(matchingTagKeys, abortController, this.openmct.objects.SEARCH_TYPES.TAGS))).flat();
|
||||||
const appliedTagSearchResults = this.#addTagMetaInformationToResults(searchResults, matchingTagKeys);
|
const appliedTagSearchResults = this.#addTagMetaInformationToResults(searchResults, matchingTagKeys);
|
||||||
const appliedTargetsModels = await this.#addTargetModelsToResults(appliedTagSearchResults);
|
const appliedTargetsModels = await this.#addTargetModelsToResults(appliedTagSearchResults);
|
||||||
const resultsWithValidPath = appliedTargetsModels.filter(result => {
|
|
||||||
return this.openmct.objects.isReachable(result.targetModels?.[0]?.originalPath);
|
|
||||||
});
|
|
||||||
|
|
||||||
return resultsWithValidPath;
|
return appliedTargetsModels;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,26 +27,15 @@ describe("The Annotation API", () => {
|
|||||||
let openmct;
|
let openmct;
|
||||||
let mockObjectProvider;
|
let mockObjectProvider;
|
||||||
let mockDomainObject;
|
let mockDomainObject;
|
||||||
let mockFolderObject;
|
|
||||||
let mockAnnotationObject;
|
let mockAnnotationObject;
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
openmct.install(new ExampleTagsPlugin());
|
openmct.install(new ExampleTagsPlugin());
|
||||||
const availableTags = openmct.annotation.getAvailableTags();
|
const availableTags = openmct.annotation.getAvailableTags();
|
||||||
mockFolderObject = {
|
|
||||||
type: 'root',
|
|
||||||
name: 'folderFoo',
|
|
||||||
location: '',
|
|
||||||
identifier: {
|
|
||||||
key: 'someParent',
|
|
||||||
namespace: 'fooNameSpace'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mockDomainObject = {
|
mockDomainObject = {
|
||||||
type: 'notebook',
|
type: 'notebook',
|
||||||
name: 'fooRabbitNotebook',
|
name: 'fooRabbitNotebook',
|
||||||
location: 'fooNameSpace:someParent',
|
|
||||||
identifier: {
|
identifier: {
|
||||||
key: 'some-object',
|
key: 'some-object',
|
||||||
namespace: 'fooNameSpace'
|
namespace: 'fooNameSpace'
|
||||||
@@ -79,8 +68,6 @@ describe("The Annotation API", () => {
|
|||||||
return mockDomainObject;
|
return mockDomainObject;
|
||||||
} else if (identifier.key === mockAnnotationObject.identifier.key) {
|
} else if (identifier.key === mockAnnotationObject.identifier.key) {
|
||||||
return mockAnnotationObject;
|
return mockAnnotationObject;
|
||||||
} else if (identifier.key === mockFolderObject.identifier.key) {
|
|
||||||
return mockFolderObject;
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -163,7 +150,6 @@ describe("The Annotation API", () => {
|
|||||||
// use local worker
|
// use local worker
|
||||||
sharedWorkerToRestore = openmct.objects.inMemorySearchProvider.worker;
|
sharedWorkerToRestore = openmct.objects.inMemorySearchProvider.worker;
|
||||||
openmct.objects.inMemorySearchProvider.worker = null;
|
openmct.objects.inMemorySearchProvider.worker = null;
|
||||||
await openmct.objects.inMemorySearchProvider.index(mockFolderObject);
|
|
||||||
await openmct.objects.inMemorySearchProvider.index(mockDomainObject);
|
await openmct.objects.inMemorySearchProvider.index(mockDomainObject);
|
||||||
await openmct.objects.inMemorySearchProvider.index(mockAnnotationObject);
|
await openmct.objects.inMemorySearchProvider.index(mockAnnotationObject);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,11 +34,11 @@ import InMemorySearchProvider from './InMemorySearchProvider';
|
|||||||
* Uniquely identifies a domain object.
|
* Uniquely identifies a domain object.
|
||||||
*
|
*
|
||||||
* @typedef Identifier
|
* @typedef Identifier
|
||||||
|
* @memberof module:openmct.ObjectAPI~
|
||||||
* @property {string} namespace the namespace to/from which this domain
|
* @property {string} namespace the namespace to/from which this domain
|
||||||
* object should be loaded/stored.
|
* object should be loaded/stored.
|
||||||
* @property {string} key a unique identifier for the domain object
|
* @property {string} key a unique identifier for the domain object
|
||||||
* within that namespace
|
* within that namespace
|
||||||
* @memberof module:openmct.ObjectAPI~
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,7 +88,7 @@ export default class ObjectAPI {
|
|||||||
this.cache = {};
|
this.cache = {};
|
||||||
this.interceptorRegistry = new InterceptorRegistry();
|
this.interceptorRegistry = new InterceptorRegistry();
|
||||||
|
|
||||||
this.SYNCHRONIZED_OBJECT_TYPES = ['notebook', 'plan', 'annotation'];
|
this.SYNCHRONIZED_OBJECT_TYPES = ['notebook', 'plan'];
|
||||||
|
|
||||||
this.errors = {
|
this.errors = {
|
||||||
Conflict: ConflictError
|
Conflict: ConflictError
|
||||||
@@ -230,7 +230,6 @@ export default class ObjectAPI {
|
|||||||
return result;
|
return result;
|
||||||
}).catch((result) => {
|
}).catch((result) => {
|
||||||
console.warn(`Failed to retrieve ${keystring}:`, result);
|
console.warn(`Failed to retrieve ${keystring}:`, result);
|
||||||
this.openmct.notifications.error(`Failed to retrieve object ${keystring}`);
|
|
||||||
|
|
||||||
delete this.cache[keystring];
|
delete this.cache[keystring];
|
||||||
|
|
||||||
@@ -388,13 +387,7 @@ export default class ObjectAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.catch((error) => {
|
return result;
|
||||||
if (error instanceof this.errors.Conflict) {
|
|
||||||
this.openmct.notifications.error(`Conflict detected while saving ${this.makeKeyString(domainObject.identifier)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -615,60 +608,27 @@ export default class ObjectAPI {
|
|||||||
* @param {module:openmct.ObjectAPI~Identifier[]} identifiers
|
* @param {module:openmct.ObjectAPI~Identifier[]} identifiers
|
||||||
*/
|
*/
|
||||||
areIdsEqual(...identifiers) {
|
areIdsEqual(...identifiers) {
|
||||||
const firstIdentifier = utils.parseKeyString(identifiers[0]);
|
|
||||||
|
|
||||||
return identifiers.map(utils.parseKeyString)
|
return identifiers.map(utils.parseKeyString)
|
||||||
.every(identifier => {
|
.every(identifier => {
|
||||||
return identifier === firstIdentifier
|
return identifier === identifiers[0]
|
||||||
|| (identifier.namespace === firstIdentifier.namespace
|
|| (identifier.namespace === identifiers[0].namespace
|
||||||
&& identifier.key === firstIdentifier.key);
|
&& identifier.key === identifiers[0].key);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
getOriginalPath(identifier, path = []) {
|
||||||
* Given an original path check if the path is reachable via root
|
return this.get(identifier).then((domainObject) => {
|
||||||
* @param {Array<Object>} originalPath an array of path objects to check
|
path.push(domainObject);
|
||||||
* @returns {boolean} whether the domain object is reachable
|
let location = domainObject.location;
|
||||||
*/
|
|
||||||
isReachable(originalPath) {
|
|
||||||
if (originalPath && originalPath.length) {
|
|
||||||
return (originalPath[originalPath.length - 1].type === 'root');
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
if (location) {
|
||||||
}
|
return this.getOriginalPath(utils.parseKeyString(location), path);
|
||||||
|
} else {
|
||||||
#pathContainsDomainObject(keyStringToCheck, path) {
|
return path;
|
||||||
if (!keyStringToCheck) {
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return path.some(pathElement => {
|
|
||||||
const identifierToCheck = utils.parseKeyString(keyStringToCheck);
|
|
||||||
|
|
||||||
return this.areIdsEqual(identifierToCheck, pathElement.identifier);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
isObjectPathToALink(domainObject, objectPath) {
|
||||||
return objectPath !== undefined
|
return objectPath !== undefined
|
||||||
&& objectPath.length > 1
|
&& objectPath.length > 1
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ describe("The Object API", () => {
|
|||||||
let openmct = {};
|
let openmct = {};
|
||||||
let mockDomainObject;
|
let mockDomainObject;
|
||||||
const TEST_NAMESPACE = "test-namespace";
|
const TEST_NAMESPACE = "test-namespace";
|
||||||
const TEST_KEY = "test-key";
|
|
||||||
const FIFTEEN_MINUTES = 15 * 60 * 1000;
|
const FIFTEEN_MINUTES = 15 * 60 * 1000;
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
@@ -23,7 +22,7 @@ describe("The Object API", () => {
|
|||||||
mockDomainObject = {
|
mockDomainObject = {
|
||||||
identifier: {
|
identifier: {
|
||||||
namespace: TEST_NAMESPACE,
|
namespace: TEST_NAMESPACE,
|
||||||
key: TEST_KEY
|
key: "test-key"
|
||||||
},
|
},
|
||||||
name: "test object",
|
name: "test object",
|
||||||
type: "test-type"
|
type: "test-type"
|
||||||
@@ -85,31 +84,6 @@ describe("The Object API", () => {
|
|||||||
expect(mockProvider.create).not.toHaveBeenCalled();
|
expect(mockProvider.create).not.toHaveBeenCalled();
|
||||||
expect(mockProvider.update).not.toHaveBeenCalled();
|
expect(mockProvider.update).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Shows a notification on persistence conflict", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
openmct.notifications.error = jasmine.createSpy('error');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("on create", () => {
|
|
||||||
mockProvider.create.and.returnValue(Promise.reject(new openmct.objects.errors.Conflict("Test Conflict error")));
|
|
||||||
|
|
||||||
return objectAPI.save(mockDomainObject).catch(() => {
|
|
||||||
expect(openmct.notifications.error).toHaveBeenCalledWith(`Conflict detected while saving ${TEST_NAMESPACE}:${TEST_KEY}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("on update", () => {
|
|
||||||
mockProvider.update.and.returnValue(Promise.reject(new openmct.objects.errors.Conflict("Test Conflict error")));
|
|
||||||
mockDomainObject.persisted = Date.now() - FIFTEEN_MINUTES;
|
|
||||||
mockDomainObject.modified = Date.now();
|
|
||||||
|
|
||||||
return objectAPI.save(mockDomainObject).catch(() => {
|
|
||||||
expect(openmct.notifications.error).toHaveBeenCalledWith(`Conflict detected while saving ${TEST_NAMESPACE}:${TEST_KEY}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -164,33 +138,21 @@ describe("The Object API", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Caches multiple requests for the same object", () => {
|
it("Caches multiple requests for the same object", () => {
|
||||||
const promises = [];
|
|
||||||
expect(mockProvider.get.calls.count()).toBe(0);
|
expect(mockProvider.get.calls.count()).toBe(0);
|
||||||
promises.push(objectAPI.get(mockDomainObject.identifier));
|
objectAPI.get(mockDomainObject.identifier);
|
||||||
expect(mockProvider.get.calls.count()).toBe(1);
|
expect(mockProvider.get.calls.count()).toBe(1);
|
||||||
promises.push(objectAPI.get(mockDomainObject.identifier));
|
objectAPI.get(mockDomainObject.identifier);
|
||||||
expect(mockProvider.get.calls.count()).toBe(1);
|
expect(mockProvider.get.calls.count()).toBe(1);
|
||||||
|
|
||||||
return Promise.all(promises);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies any applicable interceptors", () => {
|
it("applies any applicable interceptors", () => {
|
||||||
expect(mockDomainObject.changed).toBeUndefined();
|
expect(mockDomainObject.changed).toBeUndefined();
|
||||||
|
objectAPI.get(mockDomainObject.identifier).then((object) => {
|
||||||
return objectAPI.get(mockDomainObject.identifier).then((object) => {
|
|
||||||
expect(object.changed).toBeTrue();
|
expect(object.changed).toBeTrue();
|
||||||
expect(object.alsoChanged).toBeTrue();
|
expect(object.alsoChanged).toBeTrue();
|
||||||
expect(object.shouldNotBeChanged).toBeUndefined();
|
expect(object.shouldNotBeChanged).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("displays a notification in the event of an error", () => {
|
|
||||||
mockProvider.get.and.returnValue(Promise.reject());
|
|
||||||
|
|
||||||
return objectAPI.get(mockDomainObject.identifier).catch(() => {
|
|
||||||
expect(openmct.notifications.error).toHaveBeenCalledWith(`Failed to retrieve object ${TEST_NAMESPACE}:${TEST_KEY}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -206,7 +168,7 @@ describe("The Object API", () => {
|
|||||||
testObject = {
|
testObject = {
|
||||||
identifier: {
|
identifier: {
|
||||||
namespace: TEST_NAMESPACE,
|
namespace: TEST_NAMESPACE,
|
||||||
key: TEST_KEY
|
key: 'test-key'
|
||||||
},
|
},
|
||||||
name: 'test object',
|
name: 'test object',
|
||||||
type: 'notebook',
|
type: 'notebook',
|
||||||
@@ -233,8 +195,6 @@ describe("The Object API", () => {
|
|||||||
"observeObjectChanges"
|
"observeObjectChanges"
|
||||||
]);
|
]);
|
||||||
mockProvider.get.and.returnValue(Promise.resolve(testObject));
|
mockProvider.get.and.returnValue(Promise.resolve(testObject));
|
||||||
mockProvider.create.and.returnValue(Promise.resolve(true));
|
|
||||||
mockProvider.update.and.returnValue(Promise.resolve(true));
|
|
||||||
mockProvider.observeObjectChanges.and.callFake(() => {
|
mockProvider.observeObjectChanges.and.callFake(() => {
|
||||||
callbacks[0](updatedTestObject);
|
callbacks[0](updatedTestObject);
|
||||||
callbacks.splice(0, 1);
|
callbacks.splice(0, 1);
|
||||||
@@ -377,73 +337,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", () => {
|
describe("transactions", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(openmct.editor, 'isEditing').and.returnValue(true);
|
spyOn(openmct.editor, 'isEditing').and.returnValue(true);
|
||||||
|
|||||||
@@ -91,10 +91,6 @@ define([
|
|||||||
* @returns keyString
|
* @returns keyString
|
||||||
*/
|
*/
|
||||||
function makeKeyString(identifier) {
|
function makeKeyString(identifier) {
|
||||||
if (!identifier) {
|
|
||||||
throw new Error("Cannot make key string from null identifier");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isKeyString(identifier)) {
|
if (isKeyString(identifier)) {
|
||||||
return identifier;
|
return identifier;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,11 +83,9 @@ class UserAPI extends EventEmitter {
|
|||||||
* @throws Will throw an error if no user provider is set
|
* @throws Will throw an error if no user provider is set
|
||||||
*/
|
*/
|
||||||
getCurrentUser() {
|
getCurrentUser() {
|
||||||
if (!this.hasProvider()) {
|
this.noProviderCheck();
|
||||||
return Promise.resolve(undefined);
|
|
||||||
} else {
|
return this._provider.getCurrentUser();
|
||||||
return this._provider.getCurrentUser();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -51,11 +51,7 @@ export default class TelemetryCriterion extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
this.telemetryObjectIdAsString = "";
|
this.telemetryObjectIdAsString = this.openmct.objects.makeKeyString(this.telemetryDomainObjectDefinition.telemetry);
|
||||||
if (![undefined, null, ""].includes(this.telemetryDomainObjectDefinition?.telemetry)) {
|
|
||||||
this.telemetryObjectIdAsString = this.openmct.objects.makeKeyString(this.telemetryDomainObjectDefinition.telemetry);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateTelemetryObjects(this.telemetryDomainObjectDefinition.telemetryObjects);
|
this.updateTelemetryObjects(this.telemetryDomainObjectDefinition.telemetryObjects);
|
||||||
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
|
if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) {
|
||||||
this.subscribeForStaleData();
|
this.subscribeForStaleData();
|
||||||
|
|||||||
@@ -1,68 +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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="c-imagery__thumb c-thumb"
|
|
||||||
:class="{
|
|
||||||
'active': active,
|
|
||||||
'selected': selected,
|
|
||||||
'real-time': realTime
|
|
||||||
}"
|
|
||||||
:title="image.formattedTime"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href=""
|
|
||||||
:download="image.imageDownloadName"
|
|
||||||
@click.prevent
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
class="c-thumb__image"
|
|
||||||
:src="image.url"
|
|
||||||
>
|
|
||||||
</a>
|
|
||||||
<div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
image: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
active: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
selected: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
realTime: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -166,15 +166,26 @@
|
|||||||
class="c-imagery__thumbs-scroll-area"
|
class="c-imagery__thumbs-scroll-area"
|
||||||
@scroll="handleScroll"
|
@scroll="handleScroll"
|
||||||
>
|
>
|
||||||
<ImageThumbnail
|
<div
|
||||||
v-for="(image, index) in imageHistory"
|
v-for="(image, index) in imageHistory"
|
||||||
:key="image.url + image.time"
|
:key="image.url + image.time"
|
||||||
:image="image"
|
class="c-imagery__thumb c-thumb"
|
||||||
:active="focusedImageIndex === index"
|
:class="{ selected: focusedImageIndex === index && isPaused }"
|
||||||
:selected="focusedImageIndex === index && isPaused"
|
:title="image.formattedTime"
|
||||||
:real-time="!isFixed"
|
@click="thumbnailClicked(index)"
|
||||||
@click.native="thumbnailClicked(index)"
|
>
|
||||||
/>
|
<a
|
||||||
|
href=""
|
||||||
|
:download="image.imageDownloadName"
|
||||||
|
@click.prevent
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="c-thumb__image"
|
||||||
|
:src="image.url"
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
<div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -194,7 +205,6 @@ import moment from 'moment';
|
|||||||
import RelatedTelemetry from './RelatedTelemetry/RelatedTelemetry';
|
import RelatedTelemetry from './RelatedTelemetry/RelatedTelemetry';
|
||||||
import Compass from './Compass/Compass.vue';
|
import Compass from './Compass/Compass.vue';
|
||||||
import ImageControls from './ImageControls.vue';
|
import ImageControls from './ImageControls.vue';
|
||||||
import ImageThumbnail from './ImageThumbnail.vue';
|
|
||||||
import imageryData from "../../imagery/mixins/imageryData";
|
import imageryData from "../../imagery/mixins/imageryData";
|
||||||
|
|
||||||
const REFRESH_CSS_MS = 500;
|
const REFRESH_CSS_MS = 500;
|
||||||
@@ -219,11 +229,9 @@ const SHOW_THUMBS_THRESHOLD_HEIGHT = 200;
|
|||||||
const SHOW_THUMBS_FULLSIZE_THRESHOLD_HEIGHT = 600;
|
const SHOW_THUMBS_FULLSIZE_THRESHOLD_HEIGHT = 600;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ImageryView',
|
|
||||||
components: {
|
components: {
|
||||||
Compass,
|
Compass,
|
||||||
ImageControls,
|
ImageControls
|
||||||
ImageThumbnail
|
|
||||||
},
|
},
|
||||||
mixins: [imageryData],
|
mixins: [imageryData],
|
||||||
inject: ['openmct', 'domainObject', 'objectPath', 'currentView', 'imageFreshnessOptions'],
|
inject: ['openmct', 'domainObject', 'objectPath', 'currentView', 'imageFreshnessOptions'],
|
||||||
@@ -246,7 +254,6 @@ export default {
|
|||||||
visibleLayers: [],
|
visibleLayers: [],
|
||||||
durationFormatter: undefined,
|
durationFormatter: undefined,
|
||||||
imageHistory: [],
|
imageHistory: [],
|
||||||
bounds: {},
|
|
||||||
timeSystem: timeSystem,
|
timeSystem: timeSystem,
|
||||||
keyString: undefined,
|
keyString: undefined,
|
||||||
autoScroll: true,
|
autoScroll: true,
|
||||||
@@ -562,16 +569,6 @@ export default {
|
|||||||
this.resetAgeCSS();
|
this.resetAgeCSS();
|
||||||
this.updateRelatedTelemetryForFocusedImage();
|
this.updateRelatedTelemetryForFocusedImage();
|
||||||
this.getImageNaturalDimensions();
|
this.getImageNaturalDimensions();
|
||||||
},
|
|
||||||
bounds() {
|
|
||||||
this.scrollToFocused();
|
|
||||||
},
|
|
||||||
isFixed(newValue) {
|
|
||||||
const isRealTime = !newValue;
|
|
||||||
// if realtime unpause which will focus on latest image
|
|
||||||
if (isRealTime) {
|
|
||||||
this.paused(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
@@ -613,7 +610,6 @@ export default {
|
|||||||
this.handleScroll = _.debounce(this.handleScroll, SCROLL_LATENCY);
|
this.handleScroll = _.debounce(this.handleScroll, SCROLL_LATENCY);
|
||||||
this.handleThumbWindowResizeEnded = _.debounce(this.handleThumbWindowResizeEnded, SCROLL_LATENCY);
|
this.handleThumbWindowResizeEnded = _.debounce(this.handleThumbWindowResizeEnded, SCROLL_LATENCY);
|
||||||
this.handleThumbWindowResizeStart = _.debounce(this.handleThumbWindowResizeStart, SCROLL_LATENCY);
|
this.handleThumbWindowResizeStart = _.debounce(this.handleThumbWindowResizeStart, SCROLL_LATENCY);
|
||||||
this.scrollToFocused = _.debounce(this.scrollToFocused, 400);
|
|
||||||
|
|
||||||
if (this.$refs.thumbsWrapper) {
|
if (this.$refs.thumbsWrapper) {
|
||||||
this.thumbWrapperResizeObserver = new ResizeObserver(this.handleThumbWindowResizeStart);
|
this.thumbWrapperResizeObserver = new ResizeObserver(this.handleThumbWindowResizeStart);
|
||||||
@@ -849,8 +845,7 @@ export default {
|
|||||||
if (domThumb) {
|
if (domThumb) {
|
||||||
domThumb.scrollIntoView({
|
domThumb.scrollIntoView({
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
block: 'center',
|
block: 'center'
|
||||||
inline: 'center'
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -258,22 +258,13 @@
|
|||||||
min-width: $w;
|
min-width: $w;
|
||||||
width: $w;
|
width: $w;
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: $colorSelectedBg;
|
|
||||||
color: $colorSelectedFg;
|
|
||||||
}
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $colorThumbHoverBg;
|
background: $colorThumbHoverBg;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
// fixed time - selected bg will match active bg color
|
background: $colorPausedBg !important;
|
||||||
background: $colorSelectedBg;
|
color: $colorPausedFg !important;
|
||||||
color: $colorSelectedFg;
|
|
||||||
&.real-time {
|
|
||||||
// real time - bg orange when selected
|
|
||||||
background: $colorPausedBg !important;
|
|
||||||
color: $colorPausedFg !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__image {
|
&__image {
|
||||||
|
|||||||
@@ -139,7 +139,6 @@ export default {
|
|||||||
// forcibly reset the imageContainer size to prevent an aspect ratio distortion
|
// forcibly reset the imageContainer size to prevent an aspect ratio distortion
|
||||||
delete this.imageContainerWidth;
|
delete this.imageContainerWidth;
|
||||||
delete this.imageContainerHeight;
|
delete this.imageContainerHeight;
|
||||||
this.bounds = bounds; // setting bounds for ImageryView watcher
|
|
||||||
},
|
},
|
||||||
timeSystemChange() {
|
timeSystemChange() {
|
||||||
this.timeSystem = this.timeContext.timeSystem();
|
this.timeSystem = this.timeContext.timeSystem();
|
||||||
|
|||||||
@@ -296,17 +296,12 @@ export default {
|
|||||||
window.addEventListener('orientationchange', this.formatSidebar);
|
window.addEventListener('orientationchange', this.formatSidebar);
|
||||||
window.addEventListener('hashchange', this.setSectionAndPageFromUrl);
|
window.addEventListener('hashchange', this.setSectionAndPageFromUrl);
|
||||||
this.filterAndSortEntries();
|
this.filterAndSortEntries();
|
||||||
this.unobserveEntries = this.openmct.objects.observe(this.domainObject, '*', this.filterAndSortEntries);
|
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (this.unlisten) {
|
if (this.unlisten) {
|
||||||
this.unlisten();
|
this.unlisten();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.unobserveEntries) {
|
|
||||||
this.unobserveEntries();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.removeEventListener('orientationchange', this.formatSidebar);
|
window.removeEventListener('orientationchange', this.formatSidebar);
|
||||||
window.removeEventListener('hashchange', this.setSectionAndPageFromUrl);
|
window.removeEventListener('hashchange', this.setSectionAndPageFromUrl);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -88,7 +88,6 @@
|
|||||||
:annotation-type="openmct.annotation.ANNOTATION_TYPES.NOTEBOOK"
|
:annotation-type="openmct.annotation.ANNOTATION_TYPES.NOTEBOOK"
|
||||||
:annotation-search-type="openmct.objects.SEARCH_TYPES.NOTEBOOK_ANNOTATIONS"
|
:annotation-search-type="openmct.objects.SEARCH_TYPES.NOTEBOOK_ANNOTATIONS"
|
||||||
:target-specific-details="{entryId: entry.id}"
|
:target-specific-details="{entryId: entry.id}"
|
||||||
@tags-updated="timestampAndUpdate"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="c-snapshots c-ne__embeds">
|
<div class="c-snapshots c-ne__embeds">
|
||||||
@@ -147,8 +146,6 @@ import { saveNotebookImageDomainObject, updateNamespaceOfDomainObject } from '..
|
|||||||
|
|
||||||
import Moment from 'moment';
|
import Moment from 'moment';
|
||||||
|
|
||||||
const UNKNOWN_USER = 'Unknown';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
NotebookEmbed,
|
NotebookEmbed,
|
||||||
@@ -209,8 +206,7 @@ export default {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
targetKeyString,
|
targetKeyString,
|
||||||
entryId: this.entry.id,
|
entryId: this.entry.id
|
||||||
modified: this.entry.modified
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
createdOnTime() {
|
createdOnTime() {
|
||||||
@@ -287,7 +283,7 @@ export default {
|
|||||||
await this.addNewEmbed(objectPath);
|
await this.addNewEmbed(objectPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.timestampAndUpdate();
|
this.$emit('updateEntry', this.entry);
|
||||||
},
|
},
|
||||||
findPositionInArray(array, id) {
|
findPositionInArray(array, id) {
|
||||||
let position = -1;
|
let position = -1;
|
||||||
@@ -325,7 +321,7 @@ export default {
|
|||||||
// TODO: remove notebook snapshot object using object remove API
|
// TODO: remove notebook snapshot object using object remove API
|
||||||
this.entry.embeds.splice(embedPosition, 1);
|
this.entry.embeds.splice(embedPosition, 1);
|
||||||
|
|
||||||
this.timestampAndUpdate();
|
this.$emit('updateEntry', this.entry);
|
||||||
},
|
},
|
||||||
updateEmbed(newEmbed) {
|
updateEmbed(newEmbed) {
|
||||||
this.entry.embeds.some(e => {
|
this.entry.embeds.some(e => {
|
||||||
@@ -337,17 +333,6 @@ export default {
|
|||||||
return found;
|
return found;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.timestampAndUpdate();
|
|
||||||
},
|
|
||||||
async timestampAndUpdate() {
|
|
||||||
const user = await this.openmct.user.getCurrentUser();
|
|
||||||
|
|
||||||
if (user === undefined) {
|
|
||||||
this.entry.modifiedBy = UNKNOWN_USER;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.entry.modified = Date.now();
|
|
||||||
|
|
||||||
this.$emit('updateEntry', this.entry);
|
this.$emit('updateEntry', this.entry);
|
||||||
},
|
},
|
||||||
editingEntry() {
|
editingEntry() {
|
||||||
@@ -357,7 +342,7 @@ export default {
|
|||||||
const value = $event.target.innerText;
|
const value = $event.target.innerText;
|
||||||
if (value !== this.entry.text && value.match(/\S/)) {
|
if (value !== this.entry.text && value.match(/\S/)) {
|
||||||
this.entry.text = value;
|
this.entry.text = value;
|
||||||
this.timestampAndUpdate();
|
this.$emit('updateEntry', this.entry);
|
||||||
} else {
|
} else {
|
||||||
this.$emit('cancelEdit');
|
this.$emit('cancelEdit');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,17 +211,10 @@ describe("Notebook plugin:", () => {
|
|||||||
|
|
||||||
describe("synchronization", () => {
|
describe("synchronization", () => {
|
||||||
|
|
||||||
let objectCloneToSyncFrom;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
objectCloneToSyncFrom = structuredClone(notebookViewObject);
|
|
||||||
objectCloneToSyncFrom.persisted = notebookViewObject.modified + 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates an entry when another user modifies it", () => {
|
it("updates an entry when another user modifies it", () => {
|
||||||
expect(getEntryText(0).innerText).toBe("First Test Entry");
|
expect(getEntryText(0).innerText).toBe("First Test Entry");
|
||||||
objectCloneToSyncFrom.configuration.entries["test-section-1"]["test-page-1"][0].text = "Modified entry text";
|
notebookViewObject.configuration.entries["test-section-1"]["test-page-1"][0].text = "Modified entry text";
|
||||||
objectProviderObserver(objectCloneToSyncFrom);
|
objectProviderObserver(notebookViewObject);
|
||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
return Vue.nextTick().then(() => {
|
||||||
expect(getEntryText(0).innerText).toBe("Modified entry text");
|
expect(getEntryText(0).innerText).toBe("Modified entry text");
|
||||||
@@ -230,13 +223,13 @@ describe("Notebook plugin:", () => {
|
|||||||
|
|
||||||
it("shows new entry when another user adds one", () => {
|
it("shows new entry when another user adds one", () => {
|
||||||
expect(allNotebookEntryElements().length).toBe(2);
|
expect(allNotebookEntryElements().length).toBe(2);
|
||||||
objectCloneToSyncFrom.configuration.entries["test-section-1"]["test-page-1"].push({
|
notebookViewObject.configuration.entries["test-section-1"]["test-page-1"].push({
|
||||||
"id": "entry-3",
|
"id": "entry-3",
|
||||||
"createdOn": 0,
|
"createdOn": 0,
|
||||||
"text": "Third Test Entry",
|
"text": "Third Test Entry",
|
||||||
"embeds": []
|
"embeds": []
|
||||||
});
|
});
|
||||||
objectProviderObserver(objectCloneToSyncFrom);
|
objectProviderObserver(notebookViewObject);
|
||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
return Vue.nextTick().then(() => {
|
||||||
expect(allNotebookEntryElements().length).toBe(3);
|
expect(allNotebookEntryElements().length).toBe(3);
|
||||||
@@ -244,9 +237,9 @@ describe("Notebook plugin:", () => {
|
|||||||
});
|
});
|
||||||
it("removes an entry when another user removes one", () => {
|
it("removes an entry when another user removes one", () => {
|
||||||
expect(allNotebookEntryElements().length).toBe(2);
|
expect(allNotebookEntryElements().length).toBe(2);
|
||||||
let entries = objectCloneToSyncFrom.configuration.entries["test-section-1"]["test-page-1"];
|
let entries = notebookViewObject.configuration.entries["test-section-1"]["test-page-1"];
|
||||||
objectCloneToSyncFrom.configuration.entries["test-section-1"]["test-page-1"] = entries.splice(0, 1);
|
notebookViewObject.configuration.entries["test-section-1"]["test-page-1"] = entries.splice(0, 1);
|
||||||
objectProviderObserver(objectCloneToSyncFrom);
|
objectProviderObserver(notebookViewObject);
|
||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
return Vue.nextTick().then(() => {
|
||||||
expect(allNotebookEntryElements().length).toBe(1);
|
expect(allNotebookEntryElements().length).toBe(1);
|
||||||
@@ -263,8 +256,8 @@ describe("Notebook plugin:", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
expect(allNotebookPageElements().length).toBe(2);
|
expect(allNotebookPageElements().length).toBe(2);
|
||||||
objectCloneToSyncFrom.configuration.sections[0].pages.push(newPage);
|
notebookViewObject.configuration.sections[0].pages.push(newPage);
|
||||||
objectProviderObserver(objectCloneToSyncFrom);
|
objectProviderObserver(notebookViewObject);
|
||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
return Vue.nextTick().then(() => {
|
||||||
expect(allNotebookPageElements().length).toBe(3);
|
expect(allNotebookPageElements().length).toBe(3);
|
||||||
@@ -274,8 +267,8 @@ describe("Notebook plugin:", () => {
|
|||||||
|
|
||||||
it("updates the notebook when a user removes a page", () => {
|
it("updates the notebook when a user removes a page", () => {
|
||||||
expect(allNotebookPageElements().length).toBe(2);
|
expect(allNotebookPageElements().length).toBe(2);
|
||||||
objectCloneToSyncFrom.configuration.sections[0].pages.splice(0, 1);
|
notebookViewObject.configuration.sections[0].pages.splice(0, 1);
|
||||||
objectProviderObserver(objectCloneToSyncFrom);
|
objectProviderObserver(notebookViewObject);
|
||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
return Vue.nextTick().then(() => {
|
||||||
expect(allNotebookPageElements().length).toBe(1);
|
expect(allNotebookPageElements().length).toBe(1);
|
||||||
@@ -298,8 +291,8 @@ describe("Notebook plugin:", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
expect(allNotebookSectionElements().length).toBe(2);
|
expect(allNotebookSectionElements().length).toBe(2);
|
||||||
objectCloneToSyncFrom.configuration.sections.push(newSection);
|
notebookViewObject.configuration.sections.push(newSection);
|
||||||
objectProviderObserver(objectCloneToSyncFrom);
|
objectProviderObserver(notebookViewObject);
|
||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
return Vue.nextTick().then(() => {
|
||||||
expect(allNotebookSectionElements().length).toBe(3);
|
expect(allNotebookSectionElements().length).toBe(3);
|
||||||
@@ -308,8 +301,8 @@ describe("Notebook plugin:", () => {
|
|||||||
|
|
||||||
it("updates the notebook when a user removes a section", () => {
|
it("updates the notebook when a user removes a section", () => {
|
||||||
expect(allNotebookSectionElements().length).toBe(2);
|
expect(allNotebookSectionElements().length).toBe(2);
|
||||||
objectCloneToSyncFrom.configuration.sections.splice(0, 1);
|
notebookViewObject.configuration.sections.splice(0, 1);
|
||||||
objectProviderObserver(objectCloneToSyncFrom);
|
objectProviderObserver(notebookViewObject);
|
||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
return Vue.nextTick().then(() => {
|
||||||
expect(allNotebookSectionElements().length).toBe(1);
|
expect(allNotebookSectionElements().length).toBe(1);
|
||||||
|
|||||||
@@ -217,11 +217,9 @@ class CouchObjectProvider {
|
|||||||
this.indicator.setIndicatorToState(DISCONNECTED);
|
this.indicator.setIndicatorToState(DISCONNECTED);
|
||||||
console.error(error.message);
|
console.error(error.message);
|
||||||
throw new Error(`CouchDB Error - No response"`);
|
throw new Error(`CouchDB Error - No response"`);
|
||||||
} else {
|
|
||||||
console.error(error.message);
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.error(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,7 +287,7 @@ class CouchObjectProvider {
|
|||||||
this.objectQueue[key] = new CouchObjectQueue(undefined, response[REV]);
|
this.objectQueue[key] = new CouchObjectQueue(undefined, response[REV]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNotebookType(object) || object.type === 'annotation') {
|
if (isNotebookType(object)) {
|
||||||
//Temporary measure until object sync is supported for all object types
|
//Temporary measure until object sync is supported for all object types
|
||||||
//Always update notebook revision number because we have realtime sync, so always assume it's the latest.
|
//Always update notebook revision number because we have realtime sync, so always assume it's the latest.
|
||||||
this.objectQueue[key].updateRevision(response[REV]);
|
this.objectQueue[key].updateRevision(response[REV]);
|
||||||
@@ -655,6 +653,7 @@ class CouchObjectProvider {
|
|||||||
let document = new CouchDocument(key, queued.model);
|
let document = new CouchDocument(key, queued.model);
|
||||||
document.metadata.created = Date.now();
|
document.metadata.created = Date.now();
|
||||||
this.request(key, "PUT", document).then((response) => {
|
this.request(key, "PUT", document).then((response) => {
|
||||||
|
console.log('create check response', key);
|
||||||
this.#checkResponse(response, queued.intermediateResponse, key);
|
this.#checkResponse(response, queued.intermediateResponse, key);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
queued.intermediateResponse.reject(error);
|
queued.intermediateResponse.reject(error);
|
||||||
|
|||||||
@@ -188,8 +188,7 @@ export default {
|
|||||||
if (domainObject.type === 'plan') {
|
if (domainObject.type === 'plan') {
|
||||||
this.getPlanDataAndSetConfig({
|
this.getPlanDataAndSetConfig({
|
||||||
...this.domainObject,
|
...this.domainObject,
|
||||||
selectFile: domainObject.selectFile,
|
selectFile: domainObject.selectFile
|
||||||
sourceMap: domainObject.sourceMap
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,14 +25,13 @@
|
|||||||
/******************************************************** CONTROL-SPECIFIC MIXINS */
|
/******************************************************** CONTROL-SPECIFIC MIXINS */
|
||||||
@mixin menuOuter() {
|
@mixin menuOuter() {
|
||||||
border-radius: $basicCr;
|
border-radius: $basicCr;
|
||||||
box-shadow: $shdwMenu;
|
box-shadow: $shdwMenuInner, $shdwMenu;
|
||||||
@if $shdwMenuInner != none {
|
|
||||||
box-shadow: $shdwMenuInner, $shdwMenu;
|
|
||||||
}
|
|
||||||
background: $colorMenuBg;
|
background: $colorMenuBg;
|
||||||
color: $colorMenuFg;
|
color: $colorMenuFg;
|
||||||
|
//filter: $filterMenu; // 2022: causing all kinds of weird visual bugs in Chrome
|
||||||
text-shadow: $shdwMenuText;
|
text-shadow: $shdwMenuText;
|
||||||
padding: $interiorMarginSm;
|
padding: $interiorMarginSm;
|
||||||
|
//box-shadow: $shdwMenu;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -61,13 +60,14 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: nth($menuItemPad, 1) nth($menuItemPad, 2);
|
padding: nth($menuItemPad, 1) nth($menuItemPad, 2);
|
||||||
|
transition: $transIn;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
@include hover {
|
@include hover {
|
||||||
background: $colorMenuHovBg;
|
background: $colorMenuHovBg;
|
||||||
color: $colorMenuHovFg;
|
color: $colorMenuHovFg;
|
||||||
&:before {
|
&:before {
|
||||||
color: $colorMenuHovIc !important;
|
color: $colorMenuHovIc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,17 +97,14 @@ export default {
|
|||||||
this.tagsChanged(this.annotation.tags);
|
this.tagsChanged(this.annotation.tags);
|
||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
},
|
|
||||||
annotationQuery: {
|
|
||||||
handler() {
|
|
||||||
this.unloadAnnotation();
|
|
||||||
this.loadAnnotation();
|
|
||||||
},
|
|
||||||
deep: true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
async mounted() {
|
||||||
this.loadAnnotation();
|
this.annotation = await this.openmct.annotation.getAnnotation(this.annotationQuery, this.annotationSearchType);
|
||||||
|
this.addAnnotationListener(this.annotation);
|
||||||
|
if (this.annotation && this.annotation.tags) {
|
||||||
|
this.tagsChanged(this.annotation.tags);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
if (this.removeTagsListener) {
|
if (this.removeTagsListener) {
|
||||||
@@ -117,23 +114,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
addAnnotationListener(annotation) {
|
addAnnotationListener(annotation) {
|
||||||
if (annotation && !this.removeTagsListener) {
|
if (annotation && !this.removeTagsListener) {
|
||||||
this.removeTagsListener = this.openmct.objects.observe(annotation, '*', (newAnnotation) => {
|
this.removeTagsListener = this.openmct.objects.observe(annotation, 'tags', this.tagsChanged);
|
||||||
this.tagsChanged(newAnnotation.tags);
|
|
||||||
this.annotation = newAnnotation;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async loadAnnotation() {
|
|
||||||
this.annotation = await this.openmct.annotation.getAnnotation(this.annotationQuery, this.annotationSearchType);
|
|
||||||
this.addAnnotationListener(this.annotation);
|
|
||||||
if (this.annotation && this.annotation.tags) {
|
|
||||||
this.tagsChanged(this.annotation.tags);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
unloadAnnotation() {
|
|
||||||
if (this.removeTagsListener) {
|
|
||||||
this.removeTagsListener();
|
|
||||||
this.removeTagsListener = undefined;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tagsChanged(newTags) {
|
tagsChanged(newTags) {
|
||||||
@@ -152,11 +133,8 @@ export default {
|
|||||||
this.addedTags.push(newTagValue);
|
this.addedTags.push(newTagValue);
|
||||||
this.userAddingTag = true;
|
this.userAddingTag = true;
|
||||||
},
|
},
|
||||||
async tagRemoved(tagToRemove) {
|
tagRemoved(tagToRemove) {
|
||||||
const result = await this.openmct.annotation.removeAnnotationTag(this.annotation, tagToRemove);
|
return this.openmct.annotation.removeAnnotationTag(this.annotation, tagToRemove);
|
||||||
this.$emit('tags-updated');
|
|
||||||
|
|
||||||
return result;
|
|
||||||
},
|
},
|
||||||
async tagAdded(newTag) {
|
async tagAdded(newTag) {
|
||||||
const annotationWasCreated = this.annotation === null || this.annotation === undefined;
|
const annotationWasCreated = this.annotation === null || this.annotation === undefined;
|
||||||
@@ -168,8 +146,6 @@ export default {
|
|||||||
|
|
||||||
this.tagsChanged(this.annotation.tags);
|
this.tagsChanged(this.annotation.tags);
|
||||||
this.userAddingTag = false;
|
this.userAddingTag = false;
|
||||||
|
|
||||||
this.$emit('tags-updated');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.searchValue = value;
|
this.searchValue = value;
|
||||||
|
this.searchLoading = true;
|
||||||
// clear any previous search results
|
// clear any previous search results
|
||||||
this.annotationSearchResults = [];
|
this.annotationSearchResults = [];
|
||||||
this.objectSearchResults = [];
|
this.objectSearchResults = [];
|
||||||
@@ -84,13 +85,8 @@ export default {
|
|||||||
if (this.searchValue) {
|
if (this.searchValue) {
|
||||||
await this.getSearchResults();
|
await this.getSearchResults();
|
||||||
} else {
|
} else {
|
||||||
const dropdownOptions = {
|
this.searchLoading = false;
|
||||||
searchLoading: this.searchLoading,
|
this.$refs.searchResultsDropDown.showResults(this.annotationSearchResults, this.objectSearchResults);
|
||||||
searchValue: this.searchValue,
|
|
||||||
annotationSearchResults: this.annotationSearchResults,
|
|
||||||
objectSearchResults: this.objectSearchResults
|
|
||||||
};
|
|
||||||
this.$refs.searchResultsDropDown.showResults(dropdownOptions);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getPathsForObjects(objectsNeedingPaths) {
|
getPathsForObjects(objectsNeedingPaths) {
|
||||||
@@ -107,8 +103,6 @@ export default {
|
|||||||
async getSearchResults() {
|
async getSearchResults() {
|
||||||
// an abort controller will be passed in that will be used
|
// an abort controller will be passed in that will be used
|
||||||
// to cancel an active searches if necessary
|
// to cancel an active searches if necessary
|
||||||
this.searchLoading = true;
|
|
||||||
this.$refs.searchResultsDropDown.showSearchStarted();
|
|
||||||
this.abortSearchController = new AbortController();
|
this.abortSearchController = new AbortController();
|
||||||
const abortSignal = this.abortSearchController.signal;
|
const abortSignal = this.abortSearchController.signal;
|
||||||
try {
|
try {
|
||||||
@@ -116,15 +110,10 @@ export default {
|
|||||||
const fullObjectSearchResults = await Promise.all(this.openmct.objects.search(this.searchValue, abortSignal));
|
const fullObjectSearchResults = await Promise.all(this.openmct.objects.search(this.searchValue, abortSignal));
|
||||||
const aggregatedObjectSearchResults = fullObjectSearchResults.flat();
|
const aggregatedObjectSearchResults = fullObjectSearchResults.flat();
|
||||||
const aggregatedObjectSearchResultsWithPaths = await this.getPathsForObjects(aggregatedObjectSearchResults);
|
const aggregatedObjectSearchResultsWithPaths = await this.getPathsForObjects(aggregatedObjectSearchResults);
|
||||||
const filterAnnotationsAndValidPaths = aggregatedObjectSearchResultsWithPaths.filter(result => {
|
const filterAnnotations = aggregatedObjectSearchResultsWithPaths.filter(result => {
|
||||||
if (this.openmct.annotation.isAnnotation(result)) {
|
return result.type !== 'annotation';
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.openmct.objects.isReachable(result?.originalPath);
|
|
||||||
});
|
});
|
||||||
this.objectSearchResults = filterAnnotationsAndValidPaths;
|
this.objectSearchResults = filterAnnotations;
|
||||||
this.searchLoading = false;
|
|
||||||
this.showSearchResults();
|
this.showSearchResults();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`😞 Error searching`, error);
|
console.error(`😞 Error searching`, error);
|
||||||
@@ -136,13 +125,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
showSearchResults() {
|
showSearchResults() {
|
||||||
const dropdownOptions = {
|
this.$refs.searchResultsDropDown.showResults(this.annotationSearchResults, this.objectSearchResults);
|
||||||
searchLoading: this.searchLoading,
|
|
||||||
searchValue: this.searchValue,
|
|
||||||
annotationSearchResults: this.annotationSearchResults,
|
|
||||||
objectSearchResults: this.objectSearchResults
|
|
||||||
};
|
|
||||||
this.$refs.searchResultsDropDown.showResults(dropdownOptions);
|
|
||||||
document.body.addEventListener('click', this.handleOutsideClick);
|
document.body.addEventListener('click', this.handleOutsideClick);
|
||||||
},
|
},
|
||||||
handleOutsideClick(event) {
|
handleOutsideClick(event) {
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ describe("GrandSearch", () => {
|
|||||||
let mockAnnotationObject;
|
let mockAnnotationObject;
|
||||||
let mockDisplayLayout;
|
let mockDisplayLayout;
|
||||||
let mockFolderObject;
|
let mockFolderObject;
|
||||||
let mockAnotherFolderObject;
|
|
||||||
let mockTopObject;
|
|
||||||
let originalRouterPath;
|
let originalRouterPath;
|
||||||
|
|
||||||
beforeEach((done) => {
|
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 = {
|
mockFolderObject = {
|
||||||
type: 'folder',
|
type: 'folder',
|
||||||
name: 'Test Folder',
|
name: 'Test Folder',
|
||||||
location: 'fooNameSpace:someParent',
|
|
||||||
identifier: {
|
identifier: {
|
||||||
key: 'someFolder',
|
key: 'some-folder',
|
||||||
namespace: 'fooNameSpace'
|
namespace: 'fooNameSpace'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -142,10 +122,6 @@ describe("GrandSearch", () => {
|
|||||||
return mockDisplayLayout;
|
return mockDisplayLayout;
|
||||||
} else if (identifier.key === mockFolderObject.identifier.key) {
|
} else if (identifier.key === mockFolderObject.identifier.key) {
|
||||||
return mockFolderObject;
|
return mockFolderObject;
|
||||||
} else if (identifier.key === mockAnotherFolderObject.identifier.key) {
|
|
||||||
return mockAnotherFolderObject;
|
|
||||||
} else if (identifier.key === mockTopObject.identifier.key) {
|
|
||||||
return mockTopObject;
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
v-if="(annotationResults && annotationResults.length) ||
|
||||||
|
(objectResults && objectResults.length)"
|
||||||
class="c-gsearch__dropdown"
|
class="c-gsearch__dropdown"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -56,40 +58,25 @@
|
|||||||
@click.native="selectedResult"
|
@click.native="selectedResult"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div></template>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import AnnotationSearchResult from './AnnotationSearchResult.vue';
|
import AnnotationSearchResult from './AnnotationSearchResult.vue';
|
||||||
import ObjectSearchResult from './ObjectSearchResult.vue';
|
import ObjectSearchResult from './ObjectSearchResult.vue';
|
||||||
import ProgressBar from '@/ui/components/ProgressBar.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SearchResultsDropDown',
|
name: 'SearchResultsDropDown',
|
||||||
components: {
|
components: {
|
||||||
AnnotationSearchResult,
|
AnnotationSearchResult,
|
||||||
ObjectSearchResult,
|
ObjectSearchResult
|
||||||
ProgressBar
|
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
resultsShown: false,
|
resultsShown: false,
|
||||||
searchLoading: false,
|
|
||||||
annotationResults: [],
|
annotationResults: [],
|
||||||
objectResults: [],
|
objectResults: [],
|
||||||
previewVisible: false
|
previewVisible: false
|
||||||
@@ -104,18 +91,12 @@ export default {
|
|||||||
previewChanged(changedPreviewState) {
|
previewChanged(changedPreviewState) {
|
||||||
this.previewVisible = changedPreviewState;
|
this.previewVisible = changedPreviewState;
|
||||||
},
|
},
|
||||||
showSearchStarted() {
|
showResults(passedAnnotationResults, passedObjectResults) {
|
||||||
this.searchLoading = true;
|
if ((passedAnnotationResults && passedAnnotationResults.length)
|
||||||
this.resultsShown = true;
|
|| (passedObjectResults && passedObjectResults.length)) {
|
||||||
this.annotationResults = [];
|
|
||||||
this.objectResults = [];
|
|
||||||
},
|
|
||||||
showResults({searchLoading, searchValue, annotationSearchResults, objectSearchResults}) {
|
|
||||||
this.searchLoading = searchLoading;
|
|
||||||
this.annotationResults = annotationSearchResults;
|
|
||||||
this.objectResults = objectSearchResults;
|
|
||||||
if (searchValue?.length) {
|
|
||||||
this.resultsShown = true;
|
this.resultsShown = true;
|
||||||
|
this.annotationResults = passedAnnotationResults;
|
||||||
|
this.objectResults = passedObjectResults;
|
||||||
} else {
|
} else {
|
||||||
this.resultsShown = false;
|
this.resultsShown = false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user