Merge branch 'master' into dependabot/npm_and_yarn/mini-css-extract-plugin-2.8.1

This commit is contained in:
John Hill 2024-03-11 16:40:09 -07:00 committed by GitHub
commit fa2a44c734
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 465 additions and 165 deletions

View File

@ -5,7 +5,6 @@
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"Vue.volar",
"Vue.vscode-typescript-vue-plugin",
"dbaeumer.vscode-eslint",
"rvest.vs-code-prettier-eslint"
],

View File

@ -505,15 +505,14 @@ async function setTimeConductorBounds(page, startDate, endDate) {
* @param {string} startDate
* @param {string} endDate
*/
async function setIndependentTimeConductorBounds(page, startDate, endDate) {
// Activate Independent Time Conductor in Fixed Time Mode
await page.getByRole('switch').click();
async function setIndependentTimeConductorBounds(page, { start, end }) {
// Activate Independent Time Conductor
await page.getByLabel('Enable Independent Time Conductor').click();
// Bring up the time conductor popup
await page.click('.c-conductor-holder--compact .c-compact-tc');
await page.getByLabel('Independent Time Conductor Settings').click();
await expect(page.locator('.itc-popout')).toBeInViewport();
await setTimeBounds(page, startDate, endDate);
await setTimeBounds(page, start, end);
await page.keyboard.press('Enter');
}

File diff suppressed because one or more lines are too long

View File

@ -174,6 +174,6 @@ test.describe('AppActions', () => {
type: 'Folder'
});
await openObjectTreeContextMenu(page, folder.url);
await expect(page.getByLabel('Menu')).toBeVisible();
await expect(page.getByLabel(`${folder.name} Context Menu`)).toBeVisible();
});
});

View File

@ -33,7 +33,12 @@
import { fileURLToPath } from 'url';
import { createDomainObjectWithDefaults, createExampleTelemetryObject } from '../../appActions.js';
import {
createDomainObjectWithDefaults,
createExampleTelemetryObject,
setIndependentTimeConductorBounds,
setTimeConductorBounds
} from '../../appActions.js';
import { MISSION_TIME } from '../../constants.js';
import { expect, test } from '../../pluginFixtures.js';
@ -89,6 +94,53 @@ test.describe('Generate Visual Test Data @localStorage @generatedata @clock', ()
});
});
test('Generate display layout with 1 child overlay plot', async ({ page, context }) => {
const parent = await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
name: 'Parent Display Layout'
});
const overlayPlot = await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot',
name: 'Child Overlay Plot 1',
parent: parent.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: 'Child SWG 1',
parent: overlayPlot.uuid
});
await page.goto(parent.url, { waitUntil: 'domcontentloaded' });
await setIndependentTimeConductorBounds(page, {
start: '2024-11-12 19:11:11.000Z',
end: '2024-11-12 20:11:11.000Z'
});
const NEW_GLOBAL_START_BOUNDS = '2024-11-11 19:11:11.000Z';
const NEW_GLOBAL_END_BOUNDS = '2024-11-11 20:11:11.000Z';
await setTimeConductorBounds(page, NEW_GLOBAL_START_BOUNDS, NEW_GLOBAL_END_BOUNDS);
// Verify that the global time conductor bounds have been updated
expect(
await page.getByLabel('Global Time Conductor').getByLabel('Start bounds').textContent()
).toEqual(NEW_GLOBAL_START_BOUNDS);
expect(
await page.getByLabel('Global Time Conductor').getByLabel('End bounds').textContent()
).toEqual(NEW_GLOBAL_END_BOUNDS);
//Save localStorage for future test execution
await context.storageState({
path: fileURLToPath(
new URL(
'../../../e2e/test-data/display_layout_with_child_overlay_plot.json',
import.meta.url
)
)
});
});
test('Generate flexible layout with 2 child display layouts', async ({ page, context }) => {
// Create Display Layout
const parent = await createDomainObjectWithDefaults(page, {

View File

@ -131,7 +131,10 @@ test.describe('Time Strip', () => {
const startBoundString = new Date(startBound).toISOString().replace('T', ' ');
const endBoundString = new Date(endBound).toISOString().replace('T', ' ');
await setIndependentTimeConductorBounds(page, startBoundString, endBoundString);
await setIndependentTimeConductorBounds(page, {
start: startBoundString,
end: endBoundString
});
expect(await activityBounds.count()).toEqual(1);
});
@ -160,7 +163,10 @@ test.describe('Time Strip', () => {
const startBoundString = new Date(startBound).toISOString().replace('T', ' ');
const endBoundString = new Date(endBound).toISOString().replace('T', ' ');
await setIndependentTimeConductorBounds(page, startBoundString, endBoundString);
await setIndependentTimeConductorBounds(page, {
start: startBoundString,
end: endBoundString
});
// Verify that two events are displayed
expect(await activityBounds.count()).toEqual(2);

View File

@ -286,7 +286,7 @@ test.describe('Basic Condition Set Use', () => {
await page.locator('button[title="Save"]').click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await page.click('button[title="Change the current view"]');
await page.getByLabel('Open the View Switcher Menu').click();
await expect(page.getByRole('menuitem', { name: /Lad Table/ })).toBeHidden();
await expect(page.getByRole('menuitem', { name: /Conditions View/ })).toBeVisible();

View File

@ -23,6 +23,7 @@ import { fileURLToPath } from 'url';
import {
createDomainObjectWithDefaults,
navigateToObjectWithFixedTimeBounds,
setFixedTimeMode,
setIndependentTimeConductorBounds,
setRealTimeMode,
@ -30,12 +31,120 @@ import {
} from '../../../../appActions.js';
import { expect, test } from '../../../../pluginFixtures.js';
const LOCALSTORAGE_PATH = fileURLToPath(
const CHILD_LAYOUT_STORAGE_STATE_PATH = fileURLToPath(
new URL('../../../../test-data/display_layout_with_child_layouts.json', import.meta.url)
);
const CHILD_PLOT_STORAGE_STATE_PATH = fileURLToPath(
new URL('../../../../test-data/display_layout_with_child_overlay_plot.json', import.meta.url)
);
const TINY_IMAGE_BASE64 =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII';
test.describe('Display Layout Sub-object Actions @localStorage', () => {
const INIT_ITC_START_BOUNDS = '2024-11-12 19:11:11.000Z';
const INIT_ITC_END_BOUNDS = '2024-11-12 20:11:11.000Z';
const NEW_GLOBAL_START_BOUNDS = '2024-11-11 19:11:11.000Z';
const NEW_GLOBAL_END_BOUNDS = '2024-11-11 20:11:11.000Z';
test.use({
storageState: CHILD_PLOT_STORAGE_STATE_PATH
});
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.getByLabel('Expand My Items folder').click();
const waitForMyItemsNavigation = page.waitForURL(`**/mine/?*`);
await page
.getByLabel('Main Tree')
.getByLabel('Navigate to Parent Display Layout layout Object')
.click();
// Wait for the URL to change to the display layout
await waitForMyItemsNavigation;
});
test('Open in New Tab action preserves time bounds @2p', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/7524'
});
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6982'
});
const TEST_FIXED_START_TIME = 1731352271000; // 2024-11-11 19:11:11.000Z
const TEST_FIXED_END_TIME = TEST_FIXED_START_TIME + 3600000; // 2024-11-11 20:11:11.000Z
// Verify the ITC has the expected initial bounds
expect(
await page
.getByLabel('Child Overlay Plot 1 Frame Controls')
.getByLabel('Start bounds')
.textContent()
).toEqual(INIT_ITC_START_BOUNDS);
expect(
await page
.getByLabel('Child Overlay Plot 1 Frame Controls')
.getByLabel('End bounds')
.textContent()
).toEqual(INIT_ITC_END_BOUNDS);
// Update the global fixed bounds to 2024-11-11 19:11:11.000Z / 2024-11-11 20:11:11.000Z
const url = page.url().split('?')[0];
await navigateToObjectWithFixedTimeBounds(
page,
url,
TEST_FIXED_START_TIME,
TEST_FIXED_END_TIME
);
// ITC bounds should still match the initial ITC bounds
expect(
await page
.getByLabel('Child Overlay Plot 1 Frame Controls')
.getByLabel('Start bounds')
.textContent()
).toEqual(INIT_ITC_START_BOUNDS);
expect(
await page
.getByLabel('Child Overlay Plot 1 Frame Controls')
.getByLabel('End bounds')
.textContent()
).toEqual(INIT_ITC_END_BOUNDS);
// Open the Child Overlay Plot 1 in a new tab
await page.getByLabel('View menu items').click();
const pagePromise = page.context().waitForEvent('page');
await page.getByLabel('Open In New Tab').click();
const newPage = await pagePromise;
await newPage.waitForLoadState('domcontentloaded');
// Verify that the global time conductor bounds in the new page match the updated global bounds
expect(
await newPage.getByLabel('Global Time Conductor').getByLabel('Start bounds').textContent()
).toEqual(NEW_GLOBAL_START_BOUNDS);
expect(
await newPage.getByLabel('Global Time Conductor').getByLabel('End bounds').textContent()
).toEqual(NEW_GLOBAL_END_BOUNDS);
// Verify that the ITC is enabled in the new page
await expect(newPage.getByLabel('Disable Independent Time Conductor')).toBeVisible();
// Verify that the ITC bounds in the new page match the original ITC bounds
expect(
await newPage
.getByLabel('Independent Time Conductor Panel')
.getByLabel('Start bounds')
.textContent()
).toEqual(INIT_ITC_START_BOUNDS);
expect(
await newPage
.getByLabel('Independent Time Conductor Panel')
.getByLabel('End bounds')
.textContent()
).toEqual(INIT_ITC_END_BOUNDS);
});
});
test.describe('Display Layout Toolbar Actions @localStorage', () => {
const PARENT_DISPLAY_LAYOUT_NAME = 'Parent Display Layout';
const CHILD_DISPLAY_LAYOUT_NAME1 = 'Child Layout 1';
@ -50,7 +159,7 @@ test.describe('Display Layout Toolbar Actions @localStorage', () => {
await page.getByLabel('Edit Object').click();
});
test.use({
storageState: LOCALSTORAGE_PATH
storageState: CHILD_LAYOUT_STORAGE_STATE_PATH
});
test('can add/remove Text element to a single layout', async ({ page }) => {
@ -336,7 +445,7 @@ test.describe('Display Layout', () => {
const startDate = '2021-12-30 01:01:00.000Z';
const endDate = '2021-12-30 01:11:00.000Z';
await setIndependentTimeConductorBounds(page, startDate, endDate);
await setIndependentTimeConductorBounds(page, { start: startDate, end: endDate });
// check image date
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();

View File

@ -248,11 +248,10 @@ test.describe('Flexible Layout', () => {
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// flip on independent time conductor
await setIndependentTimeConductorBounds(
page,
'2021-12-30 01:01:00.000Z',
'2021-12-30 01:11:00.000Z'
);
await setIndependentTimeConductorBounds(page, {
start: '2021-12-30 01:01:00.000Z',
end: '2021-12-30 01:11:00.000Z'
});
// check image date
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();

View File

@ -175,13 +175,13 @@ test.describe('Gauge', () => {
});
// Try to create a Folder into the Gauge. Should be disallowed.
await page.getByRole('button', { name: /Create/ }).click();
await page.getByRole('button', { name: 'Create' }).click();
await page.getByRole('menuitem', { name: /Folder/ }).click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
await page.getByLabel('Cancel').click();
// Try to create a Display Layout into the Gauge. Should be disallowed.
await page.getByRole('button', { name: /Create/ }).click();
await page.getByRole('button', { name: 'Create' }).click();
await page.getByRole('menuitem', { name: /Display Layout/ }).click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
});

View File

@ -37,6 +37,8 @@ test.describe('Testing numeric data with inspector data visualization (i.e., dat
});
test('Can click on telemetry and see data in inspector @2p', async ({ page, context }) => {
const initStartBounds = await page.getByLabel('Start bounds').textContent();
const initEndBounds = await page.getByLabel('End bounds').textContent();
const exampleDataVisualizationSource = await createDomainObjectWithDefaults(page, {
type: 'Example Data Visualization Source'
});
@ -78,5 +80,9 @@ test.describe('Testing numeric data with inspector data visualization (i.e., dat
await newPage.waitForLoadState();
// expect new tab title to contain 'Second Sine Wave Generator'
await expect(newPage).toHaveTitle('Second Sine Wave Generator');
// Verify that "Open in New Tab" preserves the time bounds
expect(initStartBounds).toEqual(await newPage.getByLabel('Start bounds').textContent());
expect(initEndBounds).toEqual(await newPage.getByLabel('End bounds').textContent());
});
});

View File

@ -83,7 +83,7 @@ test.describe('Snapshot Container tests', () => {
// name: "Dropped Overlay Plot"
// });
await page.getByLabel('Take a Notebook Snapshot').click();
await page.getByLabel('Open the Notebook Snapshot Menu').click();
await page.getByRole('menuitem', { name: 'Save to Notebook Snapshots' }).click();
await page.getByLabel('Show Snapshots').click();
});

View File

@ -114,7 +114,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(defaultFrameBorderColor),
NO_STYLE_RGBA,
hexToRGB(setTextColor),
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot1 Frame' })
.getByLabel('Stacked Plot Style Target')
);
// Check styles on StackedPlot2. Note: https://github.com/nasa/openmct/issues/7337
@ -122,7 +124,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(defaultFrameBorderColor),
NO_STYLE_RGBA,
hexToRGB(setTextColor),
page.getByLabel('StackedPlot2 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot2 Frame' })
.getByLabel('Stacked Plot Style Target')
);
});
@ -143,7 +147,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(defaultBorderTargetColor),
NO_STYLE_RGBA,
hexToRGB(defaultTextColor),
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot1 Frame' })
.getByLabel('Stacked Plot Style Target')
);
// Check styles on StackedPlot2
@ -151,7 +157,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(defaultBorderTargetColor),
NO_STYLE_RGBA,
hexToRGB(defaultTextColor),
page.getByLabel('StackedPlot2 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot2 Frame' })
.getByLabel('Stacked Plot Style Target')
);
// Set styles using setStyles function on StackedPlot1 but not StackedPlot2
@ -160,7 +168,7 @@ test.describe('Flexible Layout styling', () => {
setBorderColor,
setBackgroundColor,
setTextColor,
page.getByLabel('StackedPlot1 Frame')
page.getByRole('group', { name: 'StackedPlot1 Frame' })
);
// Check styles on StackedPlot1
@ -168,7 +176,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(setBorderColor),
hexToRGB(setBackgroundColor),
hexToRGB(setTextColor),
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot1 Frame' })
.getByLabel('Stacked Plot Style Target')
);
// Check styles on StackedPlot2
@ -176,7 +186,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(defaultBorderTargetColor),
NO_STYLE_RGBA,
hexToRGB(defaultTextColor),
page.getByLabel('StackedPlot2 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot2 Frame' })
.getByLabel('Stacked Plot Style Target')
);
// Save Flexible Layout
@ -191,7 +203,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(setBorderColor),
hexToRGB(setBackgroundColor),
hexToRGB(setTextColor),
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot1 Frame' })
.getByLabel('Stacked Plot Style Target')
);
// Check styles on StackedPlot2
@ -199,7 +213,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(defaultBorderTargetColor),
NO_STYLE_RGBA,
hexToRGB(defaultTextColor),
page.getByLabel('StackedPlot2 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot2 Frame' })
.getByLabel('Stacked Plot Style Target')
);
});
@ -241,7 +257,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(setBorderColor),
hexToRGB(setBackgroundColor),
hexToRGB(setTextColor),
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot1 Frame' })
.getByLabel('Stacked Plot Style Target')
);
// Check styles on StackedPlot2 to verify they are the default
@ -249,7 +267,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(defaultBorderTargetColor),
NO_STYLE_RGBA,
hexToRGB(defaultTextColor),
page.getByLabel('StackedPlot2 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot2 Frame' })
.getByLabel('Stacked Plot Style Target')
);
// Set styles using setStyles function on StackedPlot2
@ -258,7 +278,7 @@ test.describe('Flexible Layout styling', () => {
setBorderColor,
setBackgroundColor,
setTextColor,
page.getByLabel('StackedPlot2 Frame')
page.getByRole('group', { name: 'StackedPlot2 Frame' })
);
// Check styles on StackedPlot2
@ -266,7 +286,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(setBorderColor),
hexToRGB(setBackgroundColor),
hexToRGB(setTextColor),
page.getByLabel('StackedPlot2 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot2 Frame' })
.getByLabel('Stacked Plot Style Target')
);
// Save Flexible Layout
@ -281,7 +303,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(setBorderColor),
hexToRGB(setBackgroundColor),
hexToRGB(setTextColor),
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot1 Frame' })
.getByLabel('Stacked Plot Style Target')
);
// Check styles on StackedPlot2
@ -289,7 +313,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(setBorderColor),
hexToRGB(setBackgroundColor),
hexToRGB(setTextColor),
page.getByLabel('StackedPlot2 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot2 Frame' })
.getByLabel('Stacked Plot Style Target')
);
// Directly navigate to the flexible layout
@ -326,7 +352,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(setBorderColor),
hexToRGB(setBackgroundColor),
hexToRGB(setTextColor),
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot1 Frame' })
.getByLabel('Stacked Plot Style Target')
);
// Check styles on StackedPlot2 matches previous set colors
@ -334,7 +362,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(setBorderColor),
hexToRGB(setBackgroundColor),
hexToRGB(setTextColor),
page.getByLabel('StackedPlot2 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot2 Frame' })
.getByLabel('Stacked Plot Style Target')
);
});
@ -356,7 +386,7 @@ test.describe('Flexible Layout styling', () => {
setBorderColor,
setBackgroundColor,
setTextColor,
page.getByLabel('StackedPlot1 Frame')
page.getByRole('group', { name: 'StackedPlot1 Frame' })
);
// Check styles using checkStyles function
@ -364,7 +394,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(setBorderColor),
hexToRGB(setBackgroundColor),
hexToRGB(setTextColor),
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot1 Frame' })
.getByLabel('Stacked Plot Style Target')
);
// Save Flexible Layout
@ -386,7 +418,7 @@ test.describe('Flexible Layout styling', () => {
'No Style',
'No Style',
'No Style',
page.getByLabel('StackedPlot1 Frame')
page.getByRole('group', { name: 'StackedPlot1 Frame' })
);
// Check styles using checkStyles function
@ -394,7 +426,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(defaultBorderTargetColor),
NO_STYLE_RGBA,
hexToRGB(inheritedColor),
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot1 Frame' })
.getByLabel('Stacked Plot Style Target')
);
// Save Flexible Layout
await page.getByRole('button', { name: 'Save' }).click();
@ -408,7 +442,9 @@ test.describe('Flexible Layout styling', () => {
hexToRGB(defaultBorderTargetColor),
NO_STYLE_RGBA,
hexToRGB(inheritedColor),
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
page
.getByRole('group', { name: 'StackedPlot1 Frame' })
.getByLabel('Stacked Plot Style Target')
);
});

View File

@ -67,7 +67,7 @@ test.describe('Style Inspector Options', () => {
await expect(page.getByRole('tab', { name: 'Styles' })).toBeVisible();
// Select Stacked Layout Column
await page.getByLabel('Stacked Plot Frame').click();
await page.getByRole('group', { name: 'Stacked Plot Frame' }).click();
// The overall Flex Layout or Stacked Plot itself MUST be style-able.
await expect(page.getByRole('tab', { name: 'Styles' })).toBeVisible();

View File

@ -178,7 +178,7 @@ test.describe('Performance tests', () => {
console.log('jpgResourceTiming ' + JSON.stringify(jpgResourceTiming));
// Click Close Icon
await page.locator('[aria-label="Close"]').click();
await page.getByRole('button', { name: 'Close' }).click();
await page.evaluate(() => window.performance.mark('view-large-close-button'));
//await client.send('HeapProfiler.enable');

View File

@ -69,14 +69,14 @@ test.describe('Visual - Header @a11y', () => {
});
test('show snapshot button', async ({ page, theme }) => {
await page.getByLabel('Take a Notebook Snapshot').click();
await page.getByLabel('Open the Notebook Snapshot Menu').click();
await page.getByRole('menuitem', { name: 'Save to Notebook Snapshots' }).click();
await percySnapshot(page, `Notebook Snapshot Show button (theme: '${theme}')`, {
scope: header
});
await expect(await page.getByLabel('Show Snapshots')).toBeVisible();
await expect(page.getByLabel('Show Snapshots')).toBeVisible();
});
});

View File

@ -91,7 +91,7 @@ test.describe('Flexible Layout styling @a11y', () => {
setBorderColor,
setBackgroundColor,
setTextColor,
page.getByLabel('StackedPlot1 Frame')
page.getByRole('group', { name: 'StackedPlot1 Frame' })
);
await percySnapshot(

View File

@ -53,11 +53,11 @@ test.describe('Visual - Telemetry Views', () => {
await page.goto(telemetry.url, { waitUntil: 'domcontentloaded' });
//Click this button to see telemetry display options
await page.getByRole('button', { name: 'Plot' }).click();
await page.getByLabel('Open the View Switcher Menu').click();
await page.getByLabel('Telemetry Table').click();
//Get Table View in place
expect(await page.getByLabel('Expand Columns')).toBeInViewport();
await expect(page.getByLabel('Expand Columns')).toBeInViewport();
await percySnapshot(page, `Default Telemetry Table View (theme: ${theme})`);

View File

@ -55,6 +55,7 @@
</template>
<script>
const ONE_HOUR = 60 * 60 * 1000;
export default {
inject: ['openmct', 'domainObject'],
data() {
@ -77,6 +78,10 @@ export default {
selectItem(item, event) {
event.stopPropagation();
const bounds = this.openmct.time.getBounds();
const otherBounds = {
start: bounds.start - ONE_HOUR,
end: bounds.end + ONE_HOUR
};
const selection = [
{
element: this.$el,
@ -88,6 +93,9 @@ export default {
icon: item.type.cssClass
},
dataRanges: [
{
bounds: otherBounds
},
{
bounds
}

View File

@ -19,7 +19,7 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />

View File

@ -29,10 +29,13 @@
:key="action.name"
role="menuitem"
:aria-disabled="action.isDisabled"
:class="action.cssClass"
:aria-label="action.name"
aria-describedby="item-description"
:class="action.cssClass"
:title="action.description"
@click="action.onItemClicked"
@mouseover="toggleItem(action)"
@mouseleave="toggleItem()"
>
{{ action.name }}
</li>
@ -52,16 +55,23 @@
v-for="action in options.actions"
:key="action.name"
role="menuitem"
aria-describedby="item-description"
:aria-disabled="action.isDisabled"
:class="action.cssClass"
:aria-label="action.name"
:title="action.description"
@click="action.onItemClicked"
@mouseover="toggleItem(action)"
@mouseleave="toggleItem()"
>
{{ action.name }}
</li>
<li v-if="options.actions.length === 0">No actions defined.</li>
</ul>
<div v-if="hoveredItem" id="item-description" class="visually-hidden" aria-live="polite">
<span v-if="hoveredItem.name">{{ hoveredItem.name }}</span>
<span v-if="hoveredItem.description">: {{ hoveredItem.description }}</span>
</div>
</div>
</template>
@ -70,11 +80,21 @@ import popupMenuMixin from '../mixins/popupMenuMixin.js';
export default {
mixins: [popupMenuMixin],
inject: ['options'],
data() {
return {
hoveredItem: null
};
},
computed: {
optionsLabel() {
const label = this.options.label ? `${this.options.label} Menu` : 'Menu';
const label = this.options.label ? `${this.options.label} Context Menu` : 'Context Menu';
return label;
}
},
methods: {
toggleItem(action) {
this.hoveredItem = action ?? null;
}
}
};
</script>

View File

@ -38,8 +38,8 @@
:key="action.name"
role="menuitem"
:aria-disabled="action.isDisabled"
aria-describedby="item-description"
:class="action.cssClass"
:title="action.description"
@click="action.onItemClicked"
@mouseover="toggleItemDescription(action)"
@mouseleave="toggleItemDescription()"
@ -64,7 +64,7 @@
role="menuitem"
:class="action.cssClass"
:aria-label="action.name"
:title="action.description"
aria-describedby="item-description"
@click="action.onItemClicked"
@mouseover="toggleItemDescription(action)"
@mouseleave="toggleItemDescription()"
@ -74,13 +74,13 @@
<li v-if="options.actions.length === 0">No actions defined.</li>
</ul>
<div class="c-super-menu__item-description">
<div :class="['l-item-description__icon', 'bg-' + hoveredItem.cssClass]"></div>
<div aria-live="polite" class="c-super-menu__item-description">
<div :class="itemDescriptionIconClass"></div>
<div class="l-item-description__name">
{{ hoveredItem.name }}
{{ hoveredItemName }}
</div>
<div class="l-item-description__description">
{{ hoveredItem.description }}
<div id="item-description" class="l-item-description__description">
{{ hoveredItemDescription }}
</div>
</div>
</div>
@ -90,26 +90,39 @@ import popupMenuMixin from '../mixins/popupMenuMixin.js';
export default {
mixins: [popupMenuMixin],
inject: ['options'],
data: function () {
data() {
return {
hoveredItem: {}
hoveredItem: null
};
},
computed: {
optionsLabel() {
const label = this.options.label ? `${this.options.label} Super Menu` : 'Super Menu';
return label;
},
itemDescriptionIconClass() {
const iconClass = ['l-item-description__icon'];
if (this.hoveredItem) {
iconClass.push('bg-' + this.hoveredItem.cssClass);
}
return iconClass;
},
hoveredItemName() {
return this.hoveredItem?.name ?? '';
},
hoveredItemDescription() {
return this.hoveredItem?.description ?? '';
}
},
methods: {
toggleItemDescription(action = {}) {
toggleItemDescription(action = null) {
const hoveredItem = {
name: action.name,
description: action.description,
cssClass: action.cssClass
name: action?.name,
description: action?.description,
cssClass: action?.cssClass
};
this.hoveredItem = Object.assign({}, this.hoveredItem, hoveredItem);
this.hoveredItem = hoveredItem;
}
}
};

View File

@ -58,7 +58,7 @@
const CONTEXT_MENU_ACTIONS = ['viewDatumAction', 'viewHistoricalData', 'remove'];
const BLANK_VALUE = '---';
import identifierToString from '/src/tools/url.js';
import { objectPathToUrl } from '/src/tools/url.js';
import PreviewAction from '@/ui/preview/PreviewAction.js';
import tooltipHelpers from '../../../api/tooltips/tooltipMixins.js';
@ -260,7 +260,7 @@ export default {
event.preventDefault();
this.preview(this.objectPath);
} else {
const resultUrl = identifierToString(this.openmct, this.objectPath);
const resultUrl = objectPathToUrl(this.openmct, this.objectPath);
this.openmct.router.navigate(resultUrl);
}
},

View File

@ -25,6 +25,7 @@
aria-label="Clock Indicator"
class="c-indicator t-indicator-clock icon-clock no-minify c-indicator--not-clickable"
role="complementary"
aria-live="off"
>
<span class="label c-indicator__label">
{{ timeTextValue }}

View File

@ -25,7 +25,18 @@
<div class="c-inspect-properties">
<div class="c-inspect-properties__header">Numeric Data</div>
</div>
<div ref="numericDataView"></div>
<div ref="numericDataView">
<TelemetryFrame
v-for="plotObject of plotObjects"
:key="plotObject.identifier.key"
:bounds="bounds"
:telemetry-object="plotObject"
:path="[plotObject]"
:render-when-visible="plotObject.renderWhenVisible"
>
<Plot />
</TelemetryFrame>
</div>
<div v-if="!hasNumericData">
{{ noNumericDataText }}
@ -33,13 +44,15 @@
</div>
</template>
<script>
import mount from 'utils/mount';
import VisibilityObserver from '../../utils/visibility/VisibilityObserver.js';
import Plot from '../plot/PlotView.vue';
import TelemetryFrame from './TelemetryFrame.vue';
export default {
components: {
TelemetryFrame,
Plot
},
inject: ['openmct', 'domainObject', 'timeFormatter'],
props: {
bounds: {
@ -90,16 +103,19 @@ export default {
this.clearPlots();
this.unregisterTimeContextList = [];
this.componentsList = [];
this.elementsList = [];
this.visibilityObservers = [];
this.telemetryKeys.forEach(async (telemetryKey) => {
const plotObject = await this.openmct.objects.get(telemetryKey);
const visibilityObserver = new VisibilityObserver(
this.$refs.numericDataView,
this.openmct.element
);
plotObject.renderWhenVisible = visibilityObserver.renderWhenVisible;
this.visibilityObservers.push(visibilityObserver);
this.plotObjects.push(plotObject);
this.unregisterTimeContextList.push(this.setIndependentTimeContextForComponent(plotObject));
this.renderPlot(plotObject);
});
},
setIndependentTimeContextForComponent(plotObject) {
@ -110,63 +126,14 @@ export default {
// set the time context of the object to the selected time range
return this.openmct.time.addIndependentContext(keyString, this.bounds);
},
renderPlot(plotObject) {
const wrapper = document.createElement('div');
const visibilityObserver = new VisibilityObserver(wrapper, this.openmct.element);
const { destroy } = mount(
{
components: {
TelemetryFrame,
Plot
},
provide: {
openmct: this.openmct,
path: [plotObject],
renderWhenVisible: visibilityObserver.renderWhenVisible
},
data() {
return {
plotObject,
bounds: this.bounds
};
},
template: `<TelemetryFrame
:bounds="bounds"
:telemetry-object="plotObject"
>
<Plot />
</TelemetryFrame>`
},
{
app: this.openmct.app,
element: wrapper
}
);
this.componentsList.push(destroy);
this.elementsList.push(wrapper);
this.visibilityObservers.push(visibilityObserver);
this.$refs.numericDataView.append(wrapper);
},
clearPlots() {
if (this.componentsList?.length) {
this.componentsList.forEach((destroy) => destroy());
delete this.componentsList;
}
if (this.elementsList?.length) {
this.elementsList.forEach((element) => element.remove());
delete this.elementsList;
}
if (this.visibilityObservers?.length) {
this.visibilityObservers.forEach((visibilityObserver) => visibilityObserver.destroy());
delete this.visibilityObservers;
}
if (this.plotObjects?.length) {
this.plotObjects = [];
this.plotObjects.splice(0, this.plotObjects.length);
}
if (this.unregisterTimeContextList?.length) {

View File

@ -70,7 +70,9 @@ export default {
inject: ['openmct'],
provide() {
return {
domainObject: this.telemetryObject
domainObject: this.telemetryObject,
path: this.path,
renderWhenVisible: this.renderWhenVisible
};
},
props: {
@ -81,6 +83,14 @@ export default {
telemetryObject: {
type: Object,
default: () => {}
},
path: {
type: Array,
default: () => []
},
renderWhenVisible: {
type: Function,
required: true
}
},
data() {
@ -110,7 +120,10 @@ export default {
'tc.mode': 'fixed'
};
const newTabAction = this.openmct.actions.getAction('newTab');
newTabAction.invoke([sourceTelemObject], urlParams);
// No view context needed, so pass undefined.
// The urlParams arg will override the global time bounds with the data visualization
// plot bounds.
newTabAction.invoke([sourceTelemObject], undefined, urlParams);
this.showMenu = false;
},
previewTelemetry() {

View File

@ -52,7 +52,7 @@
import Moment from 'moment';
import mount from 'utils/mount';
import objectPathToUrl from '@/tools/url';
import { objectPathToUrl } from '@/tools/url';
import tooltipHelpers from '../../../api/tooltips/tooltipMixins.js';
import ImageExporter from '../../../exporters/ImageExporter.js';

View File

@ -23,11 +23,11 @@
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
<button
class="c-icon-button c-button--menu icon-camera"
aria-label="Take a Notebook Snapshot"
title="Take a Notebook Snapshot"
:aria-label="snapshotMenuLabel"
:title="snapshotMenuLabel"
@click.stop.prevent="showMenu"
>
<span title="Take Notebook Snapshot" class="c-icon-button__label"> Snapshot </span>
<span class="c-icon-button__label">Snapshot</span>
</button>
</div>
</template>
@ -72,6 +72,11 @@ export default {
notebookTypes: []
};
},
computed: {
snapshotMenuLabel() {
return 'Open the Notebook Snapshot Menu';
}
},
mounted() {
validateNotebookStorageObject();

View File

@ -19,7 +19,7 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import objectPathToUrl from '/src/tools/url.js';
import { objectPathToUrl } from '/src/tools/url.js';
export default class OpenInNewTab {
constructor(openmct) {
this.name = 'Open In New Tab';
@ -31,8 +31,26 @@ export default class OpenInNewTab {
this._openmct = openmct;
}
invoke(objectPath, urlParams = undefined) {
let url = objectPathToUrl(this._openmct, objectPath, urlParams);
/**
* Invokes the "Open in New Tab" action. This will open the object in a new
* browser tab. The URL for the new tab is determined by the current object
* path and any custom time bounds.
*
* @param {import('@/api/objects/ObjectAPI').DomainObject[]} objectPath The current object path
* @param {ViewContext} _view The view context for the object being opened (unused)
* @param {Object<string, string | number>} customUrlParams Provides the ability to override
* the global time conductor bounds. It is an object with the following key/value pairs:
* ```
* {
* 'tc.start': <number>,
* 'tc.end': <number>,
* 'tc.mode': 'fixed' | 'local' | <string>
* }
* ```
*/
invoke(objectPath, _view, customUrlParams) {
const url = objectPathToUrl(this._openmct, objectPath, customUrlParams);
window.open(url);
}
}

View File

@ -27,6 +27,7 @@
isFixed ? 'is-fixed-mode' : independentTCEnabled ? 'is-realtime-mode' : 'is-fixed-mode',
{ 'is-expanded': independentTCEnabled }
]"
aria-label="Independent Time Conductor Panel"
>
<ToggleSwitch
id="independentTCToggle"

View File

@ -68,6 +68,18 @@ div {
flex: 1 1 auto;
}
.visually-hidden {
// Provides a way to add accessible text to elements
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
/******************************************************** BROWSER ELEMENTS */
body.desktop {
::-webkit-scrollbar {
@ -361,7 +373,7 @@ body.desktop .has-local-controls {
}
}
[aria-disabled = 'true'],
[aria-disabled='true'],
*[disabled],
.disabled {
opacity: $controlDisabledOpacity;
@ -397,7 +409,7 @@ body.desktop .has-local-controls {
position: absolute;
top: 0;
bottom: 0;
content: "";
content: '';
right: 0;
width: $fadeTruncateW * 1.5;
z-index: 2;

View File

@ -24,10 +24,17 @@
* Module defining url handling.
*/
function getUrlParams(openmct, customUrlParams = {}) {
/**
* Convert the current URL parameters to an array of strings.
* @param {import('../../openmct').OpenMCT} openmct
* @returns {Array<string>} newTabParams
*/
export function paramsToArray(openmct, customUrlParams = {}) {
let urlParams = openmct.router.getParams();
Object.entries(customUrlParams).forEach((urlParam) => {
const [key, value] = urlParam;
// Merge the custom URL parameters with the current URL parameters.
Object.entries(customUrlParams).forEach((param) => {
const [key, value] = param;
urlParams[key] = value;
});
@ -39,21 +46,7 @@ function getUrlParams(openmct, customUrlParams = {}) {
delete urlParams['tc.endBound'];
}
return urlParams;
}
export function paramsToArray(openmct, customUrlParams = {}) {
// parse urlParams from an object to an array.
let urlParams = getUrlParams(openmct, customUrlParams);
let newTabParams = [];
for (let key in urlParams) {
if ({}.hasOwnProperty.call(urlParams, key)) {
let param = `${key}=${urlParams[key]}`;
newTabParams.push(param);
}
}
return newTabParams;
return Object.entries(urlParams).map(([key, value]) => `${key}=${value}`);
}
export function identifierToString(openmct, objectPath) {
@ -66,7 +59,7 @@ export function identifierToString(openmct, objectPath) {
* @param {any} customUrlParams
* @returns {string} url
*/
export default function objectPathToUrl(openmct, objectPath, customUrlParams = {}) {
export function objectPathToUrl(openmct, objectPath, customUrlParams = {}) {
let url = identifierToString(openmct, objectPath);
let urlParams = paramsToArray(openmct, customUrlParams);

View File

@ -1,5 +1,5 @@
import { createOpenMct, resetApplicationState } from '../utils/testing.js';
import { default as objectPathToUrl, identifierToString, paramsToArray } from './url.js';
import { identifierToString, objectPathToUrl, paramsToArray } from './url.js';
describe('the url tool', function () {
let openmct;

View File

@ -32,6 +32,7 @@
'has-complex-content': complexContent
}
]"
:aria-label="ariaLabel"
>
<div class="c-so-view__header">
<div class="c-object-label" :class="[statusClass]">
@ -58,6 +59,7 @@
'c-so-view__frame-controls--no-frame': !hasFrame,
'has-complex-content': complexContent
}"
:aria-label="`${ariaLabel} Controls`"
>
<div v-if="supportsIndependentTime" class="c-conductor-holder--compact">
<independent-time-conductor :domain-object="domainObject" :object-path="objectPath" />
@ -163,6 +165,9 @@ export default {
};
},
computed: {
ariaLabel() {
return `${this.domainObject.name} Frame`;
},
statusClass() {
return this.status ? `is-status--${this.status}` : '';
}

View File

@ -145,7 +145,11 @@
:show-edit-view="true"
@change-action-collection="setActionCollection"
/>
<component :is="conductorComponent" class="l-shell__time-conductor" />
<component
:is="conductorComponent"
class="l-shell__time-conductor"
aria-label="Global Time Conductor"
/>
</pane>
<pane
class="l-shell__pane-inspector l-pane--holds-multipane"

View File

@ -24,9 +24,10 @@
<button
class="c-create-button c-button--menu c-button--major icon-plus"
:aria-disabled="isEditing"
aria-labelledby="create-button-label"
@click.prevent.stop="showCreateMenu"
>
<span class="c-button__label">Create</span>
<span id="create-button-label" class="c-button__label">Create</span>
</button>
</div>
</template>

View File

@ -27,7 +27,8 @@
<button
class="c-icon-button c-button--menu"
:class="currentView.cssClass"
title="Change the current view"
:title="viewSwitcherLabel"
:aria-label="viewSwitcherLabel"
@click.prevent.stop="showMenu"
>
<span class="c-icon-button__label">
@ -51,6 +52,11 @@ export default {
}
},
emits: ['set-view'],
computed: {
viewSwitcherLabel() {
return 'Open the View Switcher Menu';
}
},
methods: {
setView(view) {
this.$emit('set-view', view);

View File

@ -55,7 +55,7 @@
<script>
import tooltipHelpers from '../../../api/tooltips/tooltipMixins.js';
import identifierToString from '../../../tools/url.js';
import { objectPathToUrl } from '../../../tools/url.js';
import ObjectPath from '../../components/ObjectPath.vue';
import PreviewAction from '../../preview/PreviewAction.js';
@ -101,7 +101,7 @@ export default {
event.preventDefault();
this.preview(objectPath);
} else {
let resultUrl = identifierToString(this.openmct, objectPath);
let resultUrl = objectPathToUrl(this.openmct, objectPath);
// Remove the vestigial 'ROOT' identifier from url if it exists
if (resultUrl.includes('/ROOT')) {

View File

@ -61,7 +61,8 @@ export default {
let sortedActions = this.openmct.actions._groupAndSortActions(actions);
const menuOptions = {
onDestroy: this.onContextMenuDestroyed
onDestroy: this.onContextMenuDestroyed,
label: this.objectPath[0].name
};
const menuItems = this.openmct.menus.actionsToMenuItems(

View File

@ -1,4 +1,4 @@
import objectPathToUrl from '../../tools/url.js';
import { objectPathToUrl } from '../../tools/url.js';
export default {
inject: ['openmct'],