Compare commits
104 Commits
test-form-
...
fix-gauge
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a73719e809 | ||
|
|
fcfa95c2df | ||
|
|
d673cc6bcc | ||
|
|
b6d8d794be | ||
|
|
b53cc810f5 | ||
|
|
5386ceb94c | ||
|
|
affb7a5311 | ||
|
|
f92bc0122c | ||
|
|
313390f1fe | ||
|
|
e7804dd25e | ||
|
|
ec1de92d4b | ||
|
|
06b321588e | ||
|
|
909901b0f3 | ||
|
|
865f95c0b6 | ||
|
|
cb1e04b0d6 | ||
|
|
b0c2e66613 | ||
|
|
d162b5dbd8 | ||
|
|
64565b1bbb | ||
|
|
f721980bf0 | ||
|
|
b47712a0f4 | ||
|
|
57f3d4eba0 | ||
|
|
064a865c9b | ||
|
|
61bf60783c | ||
|
|
5dc718b78d | ||
|
|
115912da31 | ||
|
|
41f8cb404d | ||
|
|
c6c58af12c | ||
|
|
15a0a87251 | ||
|
|
59a8614f1c | ||
|
|
7cf11e177c | ||
|
|
1a44652470 | ||
|
|
51d16f812a | ||
|
|
25de5653e8 | ||
|
|
cb6014d69f | ||
|
|
36736eb8a0 | ||
|
|
a13a6002c5 | ||
|
|
01aac89be0 | ||
|
|
3c7ecc8561 | ||
|
|
dee92d893c | ||
|
|
0e707150e0 | ||
|
|
2540d96617 | ||
|
|
8ca0f13cd9 | ||
|
|
8ec7fbb74c | ||
|
|
143fb2dcdc | ||
|
|
1c8784fec5 | ||
|
|
a692245644 | ||
|
|
2943d2b6ec | ||
|
|
4246a597a9 | ||
|
|
0af7965021 | ||
|
|
d6bd2793d7 | ||
|
|
e9c0909415 | ||
|
|
0f0a3dc48f | ||
|
|
4c82680b87 | ||
|
|
450d3a5575 | ||
|
|
4f28e3bdf1 | ||
|
|
e6d25b22c1 | ||
|
|
acc60f5e4e | ||
|
|
9911d9ed6a | ||
|
|
c4734b8ad6 | ||
|
|
9786ff5de4 | ||
|
|
437154a5c0 | ||
|
|
2bd38dab9f | ||
|
|
063df721ae | ||
|
|
a09db30b32 | ||
|
|
9d89bdd6d3 | ||
|
|
ed9ca2829b | ||
|
|
eacbac6aad | ||
|
|
69153fe8f0 | ||
|
|
51196530fd | ||
|
|
fefa46ce7e | ||
|
|
82e685d4df | ||
|
|
e08ab8ef24 | ||
|
|
0060a6e20b | ||
|
|
7011877e64 | ||
|
|
8890cd9b22 | ||
|
|
34ecc08238 | ||
|
|
a07c043a29 | ||
|
|
2999a5135e | ||
|
|
2766452b38 | ||
|
|
f3cdf69288 | ||
|
|
a040bb30c2 | ||
|
|
0a2e0a4e65 | ||
|
|
e8df2bd437 | ||
|
|
ccd2a8b64c | ||
|
|
2bd35bb2a5 | ||
|
|
28dbd724d6 | ||
|
|
5a1c329c66 | ||
|
|
00a5cbd2fd | ||
|
|
a2d698d5c1 | ||
|
|
5685a5b393 | ||
|
|
164f39695e | ||
|
|
c384cf67da | ||
|
|
417b225505 | ||
|
|
e5e93f311c | ||
|
|
39e6d9c90c | ||
|
|
60d021ef82 | ||
|
|
59880955a2 | ||
|
|
b51ed7e844 | ||
|
|
7bbaec4006 | ||
|
|
c0f24b3925 | ||
|
|
4e79725897 | ||
|
|
0674c9fc33 | ||
|
|
de1b877954 | ||
|
|
4db2f547d9 |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
161
e2e/tests/plugins/plot/missingPlotObj.e2e.spec.js
Normal file
161
e2e/tests/plugins/plot/missingPlotObj.e2e.spec.js
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Tests to verify log plot functionality when objects are missing
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { test } = require('../../../fixtures.js');
|
||||||
|
const { expect } = require('@playwright/test');
|
||||||
|
|
||||||
|
test.describe('Handle missing object for plots', () => {
|
||||||
|
test('Displays empty div for missing stacked plot item', async ({ page }) => {
|
||||||
|
const errorLogs = [];
|
||||||
|
|
||||||
|
page.on("console", (message) => {
|
||||||
|
if (message.type() === 'warning') {
|
||||||
|
errorLogs.push(message.text());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//Make stacked plot
|
||||||
|
await makeStackedPlot(page);
|
||||||
|
|
||||||
|
//Gets local storage and deletes the last sine wave generator in the stacked plot
|
||||||
|
const localStorage = await page.evaluate(() => window.localStorage);
|
||||||
|
const parsedData = JSON.parse(localStorage.mct);
|
||||||
|
const keys = Object.keys(parsedData);
|
||||||
|
const lastKey = keys[keys.length - 1];
|
||||||
|
|
||||||
|
delete parsedData[lastKey];
|
||||||
|
|
||||||
|
//Sets local storage with missing object
|
||||||
|
await page.evaluate(
|
||||||
|
`window.localStorage.setItem('mct', '${JSON.stringify(parsedData)}')`
|
||||||
|
);
|
||||||
|
|
||||||
|
//Reloads page and clicks on stacked plot
|
||||||
|
await Promise.all([
|
||||||
|
page.reload(),
|
||||||
|
page.waitForLoadState('networkidle')
|
||||||
|
]);
|
||||||
|
|
||||||
|
//Verify Main section is there on load
|
||||||
|
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Stacked Plot');
|
||||||
|
|
||||||
|
await page.locator('text=Open MCT My Items >> span').nth(3).click();
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator('text=Unnamed Stacked Plot').first().click()
|
||||||
|
]);
|
||||||
|
|
||||||
|
//Check that there is only one stacked item plot with a plot, the missing one will be empty
|
||||||
|
await expect(page.locator(".c-plot--stacked-container:has(.gl-plot)")).toHaveCount(1);
|
||||||
|
//Verify that console.warn is thrown
|
||||||
|
await expect(errorLogs).toHaveLength(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used the create a stacked plot object
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async function makeStackedPlot(page) {
|
||||||
|
// fresh page with time range from 2022-03-29 22:00:00.000Z to 2022-03-29 22:00:30.000Z
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
// create stacked plot
|
||||||
|
await page.locator('button.c-create-button').click();
|
||||||
|
await page.locator('li:has-text("Stacked Plot")').click();
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation({ waitUntil: 'networkidle'}),
|
||||||
|
page.locator('text=OK').click(),
|
||||||
|
//Wait for Save Banner to appear
|
||||||
|
page.waitForSelector('.c-message-banner__message')
|
||||||
|
]);
|
||||||
|
|
||||||
|
//Wait until Save Banner is gone
|
||||||
|
await page.locator('.c-message-banner__close-button').click();
|
||||||
|
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
||||||
|
|
||||||
|
// save the stacked plot
|
||||||
|
await saveStackedPlot(page);
|
||||||
|
|
||||||
|
// create a sinewave generator
|
||||||
|
await createSineWaveGenerator(page);
|
||||||
|
|
||||||
|
// click on stacked plot
|
||||||
|
await page.locator('text=Open MCT My Items >> span').nth(3).click();
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator('text=Unnamed Stacked Plot').first().click()
|
||||||
|
]);
|
||||||
|
|
||||||
|
// create a second sinewave generator
|
||||||
|
await createSineWaveGenerator(page);
|
||||||
|
|
||||||
|
// click on stacked plot
|
||||||
|
await page.locator('text=Open MCT My Items >> span').nth(3).click();
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator('text=Unnamed Stacked Plot').first().click()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used to save a stacked plot object
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async function saveStackedPlot(page) {
|
||||||
|
// save stacked plot
|
||||||
|
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
page.locator('text=Save and Finish Editing').click(),
|
||||||
|
//Wait for Save Banner to appear
|
||||||
|
page.waitForSelector('.c-message-banner__message')
|
||||||
|
]);
|
||||||
|
//Wait until Save Banner is gone
|
||||||
|
await page.locator('.c-message-banner__close-button').click();
|
||||||
|
await page.waitForSelector('.c-message-banner__message', { state: 'detached' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used to create a sine wave generator object
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async function createSineWaveGenerator(page) {
|
||||||
|
//Create sine wave generator
|
||||||
|
await page.locator('button.c-create-button').click();
|
||||||
|
await page.locator('li:has-text("Sine Wave Generator")').click();
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation({ waitUntil: 'networkidle'}),
|
||||||
|
page.locator('text=OK').click(),
|
||||||
|
//Wait for Save Banner to appear
|
||||||
|
page.waitForSelector('.c-message-banner__message')
|
||||||
|
]);
|
||||||
|
//Wait until Save Banner is gone
|
||||||
|
await page.locator('.c-message-banner__close-button').click();
|
||||||
|
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
||||||
|
}
|
||||||
41
e2e/tests/plugins/remoteClock/remoteClock.e2e.spec.js
Normal file
41
e2e/tests/plugins/remoteClock/remoteClock.e2e.spec.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 } = require('../../../fixtures.js');
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const { expect } = require('@playwright/test');
|
||||||
|
|
||||||
|
test.describe('Remote Clock', () => {
|
||||||
|
// eslint-disable-next-line require-await
|
||||||
|
test.fixme('blocks historical requests until first tick is received', async ({ page }) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/5221'
|
||||||
|
});
|
||||||
|
// addInitScript to with remote clock
|
||||||
|
// Switch time conductor mode to 'remote clock'
|
||||||
|
// Navigate to telemetry
|
||||||
|
// Verify that the plot renders historical data within the correct bounds
|
||||||
|
// Refresh the page
|
||||||
|
// Verify again that the plot renders historical data within the correct bounds
|
||||||
|
});
|
||||||
|
});
|
||||||
184
e2e/tests/plugins/timer/timer.e2e.spec.js
Normal file
184
e2e/tests/plugins/timer/timer.e2e.spec.js
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 } = require('../../../fixtures.js');
|
||||||
|
const { expect } = require('@playwright/test');
|
||||||
|
|
||||||
|
test.describe('Timer', () => {
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
//Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
//Click the Create button
|
||||||
|
await page.click('button:has-text("Create")');
|
||||||
|
|
||||||
|
// Click 'Timer'
|
||||||
|
await page.click('text=Timer');
|
||||||
|
|
||||||
|
// Click text=OK
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation({waitUntil: 'networkidle'}),
|
||||||
|
page.click('text=OK')
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Timer');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Can perform actions on the Timer', async ({ page }) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/4313'
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step("From the tree context menu", async () => {
|
||||||
|
await triggerTimerContextMenuAction(page, 'Start');
|
||||||
|
await triggerTimerContextMenuAction(page, 'Pause');
|
||||||
|
await triggerTimerContextMenuAction(page, 'Restart at 0');
|
||||||
|
await triggerTimerContextMenuAction(page, 'Stop');
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step("From the 3dot menu", async () => {
|
||||||
|
await triggerTimer3dotMenuAction(page, 'Start');
|
||||||
|
await triggerTimer3dotMenuAction(page, 'Pause');
|
||||||
|
await triggerTimer3dotMenuAction(page, 'Restart at 0');
|
||||||
|
await triggerTimer3dotMenuAction(page, 'Stop');
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step("From the object view", async () => {
|
||||||
|
await triggerTimerViewAction(page, 'Start');
|
||||||
|
await triggerTimerViewAction(page, 'Pause');
|
||||||
|
await triggerTimerViewAction(page, 'Restart at 0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions that can be performed on a timer from context menus.
|
||||||
|
* @typedef {'Start' | 'Stop' | 'Pause' | 'Restart at 0'} TimerAction
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions that can be performed on a timer from the object view.
|
||||||
|
* @typedef {'Start' | 'Pause' | 'Restart at 0'} TimerViewAction
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the timer context menu from the object tree.
|
||||||
|
* Expands the 'My Items' folder if it is not already expanded.
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
*/
|
||||||
|
async function openTimerContextMenu(page) {
|
||||||
|
const myItemsFolder = page.locator('text=Open MCT My Items >> span').nth(3);
|
||||||
|
const className = await myItemsFolder.getAttribute('class');
|
||||||
|
if (!className.includes('c-disclosure-triangle--expanded')) {
|
||||||
|
await myItemsFolder.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.locator(`a:has-text("Unnamed Timer")`).click({
|
||||||
|
button: 'right'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger a timer action from the tree context menu
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {TimerAction} action
|
||||||
|
*/
|
||||||
|
async function triggerTimerContextMenuAction(page, action) {
|
||||||
|
const menuAction = `.c-menu ul li >> text="${action}"`;
|
||||||
|
await openTimerContextMenu(page);
|
||||||
|
await page.locator(menuAction).click();
|
||||||
|
assertTimerStateAfterAction(page, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger a timer action from the 3dot menu
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {TimerAction} action
|
||||||
|
*/
|
||||||
|
async function triggerTimer3dotMenuAction(page, action) {
|
||||||
|
const menuAction = `.c-menu ul li >> text="${action}"`;
|
||||||
|
const threeDotMenuButton = 'button[title="More options"]';
|
||||||
|
let isActionAvailable = false;
|
||||||
|
let iterations = 0;
|
||||||
|
// Dismiss/open the 3dot menu until the action is available
|
||||||
|
// or a maxiumum number of iterations is reached
|
||||||
|
while (!isActionAvailable && iterations <= 20) {
|
||||||
|
await page.click('.c-object-view');
|
||||||
|
await page.click(threeDotMenuButton);
|
||||||
|
isActionAvailable = await page.locator(menuAction).isVisible();
|
||||||
|
iterations++;
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.locator(menuAction).click();
|
||||||
|
assertTimerStateAfterAction(page, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger a timer action from the object view
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {TimerViewAction} action
|
||||||
|
*/
|
||||||
|
async function triggerTimerViewAction(page, action) {
|
||||||
|
const buttonTitle = buttonTitleFromAction(action);
|
||||||
|
await page.click(`button[title="${buttonTitle}"]`);
|
||||||
|
assertTimerStateAfterAction(page, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes in a TimerViewAction and returns the button title
|
||||||
|
* @param {TimerViewAction} action
|
||||||
|
*/
|
||||||
|
function buttonTitleFromAction(action) {
|
||||||
|
switch (action) {
|
||||||
|
case 'Start':
|
||||||
|
return 'Start';
|
||||||
|
case 'Pause':
|
||||||
|
return 'Pause';
|
||||||
|
case 'Restart at 0':
|
||||||
|
return 'Reset';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the timer state after a timer action has been performed.
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {TimerAction} action
|
||||||
|
*/
|
||||||
|
async function assertTimerStateAfterAction(page, action) {
|
||||||
|
let timerStateClass;
|
||||||
|
switch (action) {
|
||||||
|
case 'Start':
|
||||||
|
case 'Restart at 0':
|
||||||
|
timerStateClass = "is-started";
|
||||||
|
break;
|
||||||
|
case 'Stop':
|
||||||
|
timerStateClass = 'is-stopped';
|
||||||
|
break;
|
||||||
|
case 'Pause':
|
||||||
|
timerStateClass = 'is-paused';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect.soft(page.locator('.c-timer')).toHaveClass(new RegExp(timerStateClass));
|
||||||
|
}
|
||||||
111
e2e/tests/ui/layout/search/grandsearch.e2e.spec.js
Normal file
111
e2e/tests/ui/layout/search/grandsearch.e2e.spec.js
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test suite is dedicated to tests which verify search functionality.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { expect } = require('@playwright/test');
|
||||||
|
const { test } = require('../../../../fixtures');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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('Can search for objects, and subsequent search dropdown behaves properly', async ({ page }) => {
|
||||||
|
await createClockAndDisplayLayout(page);
|
||||||
|
|
||||||
|
// Click [aria-label="OpenMCT Search"] input[type="search"]
|
||||||
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||||
|
// Fill [aria-label="OpenMCT Search"] input[type="search"]
|
||||||
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Cl');
|
||||||
|
await expect(page.locator('[aria-label="Search Result"]')).toContainText('Clock');
|
||||||
|
// 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();
|
||||||
|
// Click [aria-label="Unnamed Clock clock result"] >> text=Unnamed Clock
|
||||||
|
await page.locator('[aria-label="Unnamed Clock clock result"] >> text=Unnamed Clock').click();
|
||||||
|
await expect(page.locator('.js-preview-window')).toBeVisible();
|
||||||
|
|
||||||
|
// Click [aria-label="Close"]
|
||||||
|
await page.locator('[aria-label="Close"]').click();
|
||||||
|
await expect(page.locator('[aria-label="Search Result"]')).toBeVisible();
|
||||||
|
await expect(page.locator('[aria-label="Search Result"]')).toContainText('Cloc');
|
||||||
|
|
||||||
|
// Click [aria-label="OpenMCT Search"] a >> nth=0
|
||||||
|
await page.locator('[aria-label="OpenMCT Search"] a').first().click();
|
||||||
|
await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible();
|
||||||
|
|
||||||
|
// Fill [aria-label="OpenMCT Search"] input[type="search"]
|
||||||
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('foo');
|
||||||
|
await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible();
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
// Click text=Save and Finish Editing
|
||||||
|
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();
|
||||||
|
// Fill [aria-label="OpenMCT Search"] [aria-label="Search Input"]
|
||||||
|
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').fill('Cl');
|
||||||
|
// Click text=Unnamed Clock
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator('text=Unnamed Clock').click()
|
||||||
|
]);
|
||||||
|
await expect(page.locator('.is-object-type-clock')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
95
e2e/tests/visual/generateVisualTestData.e2e.spec.js
Normal file
95
e2e/tests/visual/generateVisualTestData.e2e.spec.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test suite is dedicated to generating LocalStorage via Session Storage to be used
|
||||||
|
in some visual test suites like controlledClock.visual.spec.js. This suite should run to completion
|
||||||
|
and generate an artifact named ./e2e/test-data/VisualTestData_storage.json . This will run
|
||||||
|
on every Commit to ensure that this object still loads into tests correctly and will retain the
|
||||||
|
.e2e.spec.js suffix.
|
||||||
|
|
||||||
|
TODO: Provide additional validation of object properties as it grows.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { test } = require('../../fixtures.js');
|
||||||
|
const { expect } = require('@playwright/test');
|
||||||
|
|
||||||
|
test('Generate Visual Test Data @localStorage', async ({ page, context }) => {
|
||||||
|
//Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
await page.locator('button:has-text("Create")').click();
|
||||||
|
|
||||||
|
// add overlay plot with defaults
|
||||||
|
await page.locator('li:has-text("Overlay Plot")').click();
|
||||||
|
|
||||||
|
// Click on My Items in Tree. Workaround for https://github.com/nasa/openmct/issues/5184
|
||||||
|
await page.click('form[name="mctForm"] a:has-text("My Items")');
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator('text=OK').click(),
|
||||||
|
//Wait for Save Banner to appear1
|
||||||
|
page.waitForSelector('.c-message-banner__message')
|
||||||
|
]);
|
||||||
|
//Wait until Save Banner is gone
|
||||||
|
await page.locator('.c-message-banner__close-button').click();
|
||||||
|
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
||||||
|
|
||||||
|
// save (exit edit mode)
|
||||||
|
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
||||||
|
await page.locator('text=Save and Finish Editing').click();
|
||||||
|
|
||||||
|
// click create button
|
||||||
|
await page.locator('button:has-text("Create")').click();
|
||||||
|
|
||||||
|
// add sine wave generator with defaults
|
||||||
|
await page.locator('li:has-text("Sine Wave Generator")').click();
|
||||||
|
|
||||||
|
//Add a 5000 ms Delay
|
||||||
|
await page.locator('[aria-label="Loading Delay \\(ms\\)"]').fill('5000');
|
||||||
|
|
||||||
|
// Click on My Items in Tree. Workaround for https://github.com/nasa/openmct/issues/5184
|
||||||
|
await page.click('form[name="mctForm"] a:has-text("Overlay Plot")');
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator('text=OK').click(),
|
||||||
|
//Wait for Save Banner to appear1
|
||||||
|
page.waitForSelector('.c-message-banner__message')
|
||||||
|
]);
|
||||||
|
//Wait until Save Banner is gone
|
||||||
|
await page.locator('.c-message-banner__close-button').click();
|
||||||
|
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
||||||
|
|
||||||
|
// focus the overlay plot
|
||||||
|
await page.locator('text=Open MCT My Items >> span').nth(3).click();
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator('text=Unnamed Overlay Plot').first().click()
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Overlay Plot');
|
||||||
|
//Save localStorage for future test execution
|
||||||
|
await context.storageState({ path: './e2e/test-data/VisualTestData_storage.json' });
|
||||||
|
});
|
||||||
@@ -25,6 +25,7 @@ import FormProperties from './components/FormProperties.vue';
|
|||||||
|
|
||||||
import EventEmitter from 'EventEmitter';
|
import EventEmitter from 'EventEmitter';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
export default class FormsAPI extends EventEmitter {
|
export default class FormsAPI extends EventEmitter {
|
||||||
constructor(openmct) {
|
constructor(openmct) {
|
||||||
@@ -158,7 +159,8 @@ export default class FormsAPI extends EventEmitter {
|
|||||||
key = property.join('.');
|
key = property.join('.');
|
||||||
}
|
}
|
||||||
|
|
||||||
changes[key] = data.value;
|
_.set(changes, `${key}`, data.value);
|
||||||
|
self.emit('formPropertiesChanged', changes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import PropertiesAction from './PropertiesAction';
|
|||||||
import CreateWizard from './CreateWizard';
|
import CreateWizard from './CreateWizard';
|
||||||
|
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
export default class CreateAction extends PropertiesAction {
|
export default class CreateAction extends PropertiesAction {
|
||||||
constructor(openmct, type, parentDomainObject) {
|
constructor(openmct, type, parentDomainObject) {
|
||||||
@@ -50,19 +51,15 @@ export default class CreateAction extends PropertiesAction {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const properties = key.split('.');
|
const existingValue = this.domainObject[`${key}`];
|
||||||
let object = this.domainObject;
|
if (!(existingValue instanceof Array) && (typeof existingValue === 'object')) {
|
||||||
const propertiesLength = properties.length;
|
value = {
|
||||||
properties.forEach((property, index) => {
|
...existingValue,
|
||||||
const isComplexProperty = propertiesLength > 1 && index !== propertiesLength - 1;
|
...value
|
||||||
if (isComplexProperty && object[property] !== null) {
|
};
|
||||||
object = object[property];
|
}
|
||||||
} else {
|
|
||||||
object[property] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
object = value;
|
_.set(this.domainObject, `${key}`, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
const parentDomainObject = parentDomainObjectPath[0];
|
const parentDomainObject = parentDomainObjectPath[0];
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
import PropertiesAction from './PropertiesAction';
|
import PropertiesAction from './PropertiesAction';
|
||||||
import CreateWizard from './CreateWizard';
|
import CreateWizard from './CreateWizard';
|
||||||
|
|
||||||
export default class EditPropertiesAction extends PropertiesAction {
|
export default class EditPropertiesAction extends PropertiesAction {
|
||||||
constructor(openmct) {
|
constructor(openmct) {
|
||||||
super(openmct);
|
super(openmct);
|
||||||
@@ -54,23 +55,26 @@ export default class EditPropertiesAction extends PropertiesAction {
|
|||||||
_onSave(changes) {
|
_onSave(changes) {
|
||||||
try {
|
try {
|
||||||
Object.entries(changes).forEach(([key, value]) => {
|
Object.entries(changes).forEach(([key, value]) => {
|
||||||
const properties = key.split('.');
|
const existingValue = this.domainObject[`${key}`];
|
||||||
let object = this.domainObject;
|
if (!(existingValue instanceof Array) && (typeof existingValue === 'object')) {
|
||||||
const propertiesLength = properties.length;
|
value = {
|
||||||
properties.forEach((property, index) => {
|
...existingValue,
|
||||||
const isComplexProperty = propertiesLength > 1 && index !== propertiesLength - 1;
|
...value
|
||||||
if (isComplexProperty && object[property] !== null) {
|
};
|
||||||
object = object[property];
|
}
|
||||||
} else {
|
|
||||||
object[property] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
object = value;
|
this.openmct.objects.mutate(this.domainObject, `${key}`, value);
|
||||||
this.openmct.objects.mutate(this.domainObject, key, value);
|
|
||||||
this.openmct.notifications.info('Save successful');
|
|
||||||
});
|
});
|
||||||
|
if (this.openmct.editor.isEditing()) {
|
||||||
|
this.openmct.editor.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.openmct.notifications.info('Save successful');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (this.openmct.editor.isEditing()) {
|
||||||
|
this.openmct.editor.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
this.openmct.notifications.error('Error saving objects');
|
this.openmct.notifications.error('Error saving objects');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
@@ -81,6 +85,9 @@ export default class EditPropertiesAction extends PropertiesAction {
|
|||||||
*/
|
*/
|
||||||
_onCancel() {
|
_onCancel() {
|
||||||
//noop
|
//noop
|
||||||
|
if (this.openmct.editor.isEditing()) {
|
||||||
|
this.openmct.editor.cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,6 +99,12 @@ export default class EditPropertiesAction extends PropertiesAction {
|
|||||||
const createWizard = new CreateWizard(this.openmct, this.domainObject, objectPath[1]);
|
const createWizard = new CreateWizard(this.openmct, this.domainObject, objectPath[1]);
|
||||||
const formStructure = createWizard.getFormStructure(false);
|
const formStructure = createWizard.getFormStructure(false);
|
||||||
formStructure.title = 'Edit ' + this.domainObject.name;
|
formStructure.title = 'Edit ' + this.domainObject.name;
|
||||||
|
//If we're in edit mode AND want to edit properties for the same domain object
|
||||||
|
//In this case, saving will put the object in view-only mode
|
||||||
|
//TODO: Maybe we should block editing properties if someone is in Edit mode?
|
||||||
|
if (!this.openmct.editor.isEditing()) {
|
||||||
|
this.openmct.editor.edit();
|
||||||
|
}
|
||||||
|
|
||||||
return this.openmct.forms.showForm(formStructure)
|
return this.openmct.forms.showForm(formStructure)
|
||||||
.then(this._onSave.bind(this))
|
.then(this._onSave.bind(this))
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ describe('EditPropertiesAction plugin', () => {
|
|||||||
function callback(newObject) {
|
function callback(newObject) {
|
||||||
expect(newObject.name).not.toEqual(oldName);
|
expect(newObject.name).not.toEqual(oldName);
|
||||||
expect(newObject.name).toEqual(newName);
|
expect(newObject.name).toEqual(newName);
|
||||||
|
expect(openmct.editor.isEditing()).toBeFalse();
|
||||||
|
|
||||||
unObserve();
|
unObserve();
|
||||||
done();
|
done();
|
||||||
@@ -164,6 +165,7 @@ describe('EditPropertiesAction plugin', () => {
|
|||||||
openmct.forms.on('onFormPropertyChange', deBouncedFormChange);
|
openmct.forms.on('onFormPropertyChange', deBouncedFormChange);
|
||||||
|
|
||||||
function handleFormPropertyChange(data) {
|
function handleFormPropertyChange(data) {
|
||||||
|
expect(openmct.editor.isEditing()).toBeTrue();
|
||||||
const form = document.querySelector('.js-form');
|
const form = document.querySelector('.js-form');
|
||||||
const title = form.querySelector('input');
|
const title = form.querySelector('input');
|
||||||
const notes = form.querySelector('textArea');
|
const notes = form.querySelector('textArea');
|
||||||
@@ -213,10 +215,12 @@ describe('EditPropertiesAction plugin', () => {
|
|||||||
editPropertiesAction.invoke([domainObject])
|
editPropertiesAction.invoke([domainObject])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
expect(domainObject.name).toEqual(name);
|
expect(domainObject.name).toEqual(name);
|
||||||
|
expect(openmct.editor.isEditing()).toBeFalse();
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
expect(domainObject.name).toEqual(name);
|
expect(domainObject.name).toEqual(name);
|
||||||
|
expect(openmct.editor.isEditing()).toBeFalse();
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
ToggleSwitch
|
ToggleSwitch
|
||||||
},
|
},
|
||||||
|
inject: ["openmct"],
|
||||||
props: {
|
props: {
|
||||||
model: {
|
model: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -107,6 +108,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
this.changes = {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isUseTelemetryLimits: this.model.value.isUseTelemetryLimits,
|
isUseTelemetryLimits: this.model.value.isUseTelemetryLimits,
|
||||||
isDisplayMinMax: this.model.value.isDisplayMinMax,
|
isDisplayMinMax: this.model.value.isDisplayMinMax,
|
||||||
@@ -118,21 +121,30 @@ export default {
|
|||||||
min: this.model.value.min
|
min: this.model.value.min
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.openmct.forms.on('formPropertiesChanged', this.onFormPropertyChange);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.openmct.forms.off('formPropertiesChanged', this.onFormPropertyChange);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onFormPropertyChange(data) {
|
||||||
|
this.changes = data?.configuration?.gaugeController || {};
|
||||||
|
},
|
||||||
onChange(event) {
|
onChange(event) {
|
||||||
const data = {
|
const data = {
|
||||||
model: this.model,
|
model: this.model,
|
||||||
value: {
|
value: {
|
||||||
gaugeType: this.model.value.gaugeType,
|
gaugeType: this.changes.gaugeType || this.model.value.gaugeType,
|
||||||
isDisplayMinMax: this.isDisplayMinMax,
|
isDisplayMinMax: this.changes.isDisplayMinMax === undefined ? this.isDisplayMinMax : this.changes.isDisplayMinMax,
|
||||||
isDisplayCurVal: this.isDisplayCurVal,
|
isDisplayCurVal: this.changes.isDisplayCurVal === undefined ? this.isDisplayCurVal : this.changes.isDisplayCurVal,
|
||||||
isDisplayUnits: this.isDisplayUnits,
|
isDisplayUnits: this.changes.isDisplayUnits === undefined ? this.isDisplayUnits : this.changes.isDisplayUnits,
|
||||||
isUseTelemetryLimits: this.isUseTelemetryLimits,
|
isUseTelemetryLimits: this.isUseTelemetryLimits,
|
||||||
limitLow: this.limitLow,
|
limitLow: this.limitLow,
|
||||||
limitHigh: this.limitHigh,
|
limitHigh: this.limitHigh,
|
||||||
max: this.max,
|
max: this.max,
|
||||||
min: this.min,
|
min: this.min,
|
||||||
precision: this.model.value.precision
|
precision: this.changes.precision === undefined ? this.model.value.precision : this.changes.precision
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user