Fix stacked plot child selection (#6275)

* Fix selections for different scenarios

* Ensure plot selection in stacked plots works when there are no selected or found annotations

* Adds e2e test for stacked plot selection and fixes the old e2e test which was testing overlay plots instead.

* Fix selection of plots while in Edit mode

* Improve tests for stacked plots

* refactor: remove unnecessary `await`s

* a11y: move aria-label to StackedPlotItem

* refactor(e2e): combine like tests, unique object names

- Use unique object names in `text=` selectors

- Combine like tests to reduce execution time

- Use `getByRole` selectors where able

* docs(e2e): add comments to test

* fix: add class back for unit test selector

---------

Co-authored-by: Scott Bell <scott@traclabs.com>
Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
This commit is contained in:
Shefali Joshi 2023-02-03 15:56:50 -08:00 committed by GitHub
parent 0f312a88bb
commit be38c3e654
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 165 additions and 48 deletions

View File

@ -29,29 +29,39 @@ const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults } = require('../../../../appActions'); const { createDomainObjectWithDefaults } = require('../../../../appActions');
test.describe('Stacked Plot', () => { test.describe('Stacked Plot', () => {
let stackedPlot;
let swgA;
let swgB;
let swgC;
test.beforeEach(async ({ page }) => {
//Open a browser, navigate to the main page, and wait until all networkevents to resolve
await page.goto('/', { waitUntil: 'networkidle' });
stackedPlot = await createDomainObjectWithDefaults(page, {
type: "Stacked Plot"
});
swgA = await createDomainObjectWithDefaults(page, {
type: "Sine Wave Generator",
parent: stackedPlot.uuid
});
swgB = await createDomainObjectWithDefaults(page, {
type: "Sine Wave Generator",
parent: stackedPlot.uuid
});
swgC = await createDomainObjectWithDefaults(page, {
type: "Sine Wave Generator",
parent: stackedPlot.uuid
});
});
test('Using the remove action removes the correct plot', async ({ page }) => { test('Using the remove action removes the correct plot', async ({ page }) => {
await page.goto('/', { waitUntil: 'networkidle' }); const swgAElementsPoolItem = page.locator('#inspector-elements-tree').locator('.c-object-label', { hasText: swgA.name });
const overlayPlot = await createDomainObjectWithDefaults(page, { const swgBElementsPoolItem = page.locator('#inspector-elements-tree').locator('.c-object-label', { hasText: swgB.name });
type: "Overlay Plot" const swgCElementsPoolItem = page.locator('#inspector-elements-tree').locator('.c-object-label', { hasText: swgC.name });
});
await page.goto(stackedPlot.url);
await createDomainObjectWithDefaults(page, {
type: "Sine Wave Generator",
name: 'swg a',
parent: overlayPlot.uuid
});
await createDomainObjectWithDefaults(page, {
type: "Sine Wave Generator",
name: 'swg b',
parent: overlayPlot.uuid
});
await createDomainObjectWithDefaults(page, {
type: "Sine Wave Generator",
name: 'swg c',
parent: overlayPlot.uuid
});
await page.goto(overlayPlot.url);
await page.click('button[title="Edit"]'); await page.click('button[title="Edit"]');
// Expand the elements pool vertically // Expand the elements pool vertically
@ -60,20 +70,70 @@ test.describe('Stacked Plot', () => {
await page.mouse.move(0, 100); await page.mouse.move(0, 100);
await page.mouse.up(); await page.mouse.up();
await page.locator('.js-elements-pool__tree >> text=swg b').click({ button: 'right' }); await swgBElementsPoolItem.click({ button: 'right' });
await page.locator('li[role="menuitem"]:has-text("Remove")').click(); await page.getByRole('menuitem').filter({ hasText: /Remove/ }).click();
await page.locator('.js-overlay .js-overlay__button >> text=OK').click(); await page.getByRole('button').filter({ hasText: "OK" }).click();
// Wait until the number of elements in the elements pool has changed, and then confirm that the correct children were retained await expect(page.locator('#inspector-elements-tree .js-elements-pool__item')).toHaveCount(2);
// await page.waitForFunction(() => {
// return Array.from(document.querySelectorAll('.js-elements-pool__tree .js-elements-pool__item')).length === 2;
// });
// Wait until there are only two items in the elements pool (ie the remove action has completed)
await expect(page.locator('.js-elements-pool__tree .js-elements-pool__item')).toHaveCount(2);
// Confirm that the elements pool contains the items we expect // Confirm that the elements pool contains the items we expect
await expect(page.locator('.js-elements-pool__tree >> text=swg a')).toHaveCount(1); await expect(swgAElementsPoolItem).toHaveCount(1);
await expect(page.locator('.js-elements-pool__tree >> text=swg b')).toHaveCount(0); await expect(swgBElementsPoolItem).toHaveCount(0);
await expect(page.locator('.js-elements-pool__tree >> text=swg c')).toHaveCount(1); await expect(swgCElementsPoolItem).toHaveCount(1);
});
test('Selecting a child plot while in browse and edit modes shows its properties in the inspector', async ({ page }) => {
await page.goto(stackedPlot.url);
// Click on the 1st plot
await page.locator(`[aria-label="Stacked Plot Item ${swgA.name}"] canvas`).nth(1).click();
// Assert that the inspector shows the Y Axis properties for swgA
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
await expect(page.getByRole('list', { name: "Y Axis Properties" }).locator("h2")).toContainText("Y Axis");
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgA.name);
// Click on the 2nd plot
await page.locator(`[aria-label="Stacked Plot Item ${swgB.name}"] canvas`).nth(1).click();
// Assert that the inspector shows the Y Axis properties for swgB
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
await expect(page.getByRole('list', { name: "Y Axis Properties" }).locator("h2")).toContainText("Y Axis");
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgB.name);
// Click on the 3rd plot
await page.locator(`[aria-label="Stacked Plot Item ${swgC.name}"] canvas`).nth(1).click();
// Assert that the inspector shows the Y Axis properties for swgC
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
await expect(page.getByRole('list', { name: "Y Axis Properties" }).locator("h2")).toContainText("Y Axis");
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgC.name);
// Go into edit mode
await page.click('button[title="Edit"]');
// Click on canvas for the 1st plot
await page.locator(`[aria-label="Stacked Plot Item ${swgA.name}"]`).click();
// Assert that the inspector shows the Y Axis properties for swgA
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
await expect(page.getByRole('list', { name: "Y Axis Properties" }).locator("h2")).toContainText("Y Axis");
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgA.name);
//Click on canvas for the 2nd plot
await page.locator(`[aria-label="Stacked Plot Item ${swgB.name}"]`).click();
// Assert that the inspector shows the Y Axis properties for swgB
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
await expect(page.getByRole('list', { name: "Y Axis Properties" }).locator("h2")).toContainText("Y Axis");
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgB.name);
//Click on canvas for the 3rd plot
await page.locator(`[aria-label="Stacked Plot Item ${swgC.name}"]`).click();
// Assert that the inspector shows the Y Axis properties for swgC
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
await expect(page.getByRole('list', { name: "Y Axis Properties" }).locator("h2")).toContainText("Y Axis");
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgC.name);
}); });
}); });

View File

@ -1190,28 +1190,42 @@ export default {
selectNearbyAnnotations(event) { selectNearbyAnnotations(event) {
// need to stop propagation right away to prevent selecting the plot itself // need to stop propagation right away to prevent selecting the plot itself
event.stopPropagation(); event.stopPropagation();
if (!this.annotationViewingAndEditingAllowed || this.annotationSelections.length) {
return;
}
const nearbyAnnotations = this.gatherNearbyAnnotations(); const nearbyAnnotations = this.gatherNearbyAnnotations();
if (!nearbyAnnotations.length) {
const emptySelection = this.createPathSelection(); if (this.annotationViewingAndEditingAllowed && this.annotationSelections.length) {
this.openmct.selection.select(emptySelection, true); //no annotations were found, but we are adding some now
// should show plot itself if we didn't find any annotations return;
}
if (this.annotationViewingAndEditingAllowed && nearbyAnnotations.length) {
//show annotations if some were found
const { targetDomainObjects, targetDetails } = this.prepareExistingAnnotationSelection(nearbyAnnotations);
this.selectPlotAnnotations({
targetDetails,
targetDomainObjects,
annotations: nearbyAnnotations
});
return; return;
} }
const { targetDomainObjects, targetDetails } = this.prepareExistingAnnotationSelection(nearbyAnnotations); //Fall through to here if either there is no new selection add tags or no existing annotations were retrieved
this.selectPlotAnnotations({ this.selectPlot();
targetDetails, },
targetDomainObjects, selectPlot() {
annotations: nearbyAnnotations // should show plot itself if we didn't find any annotations
}); const selection = this.createPathSelection();
this.openmct.selection.select(selection, true);
}, },
createPathSelection() { createPathSelection() {
let selection = []; let selection = [];
selection.unshift({
element: this.$el,
context: {
item: this.domainObject
}
});
this.path.forEach((pathObject, index) => { this.path.forEach((pathObject, index) => {
selection.push({ selection.push({
element: this.openmct.layout.$refs.browseObject.$el, element: this.openmct.layout.$refs.browseObject.$el,

View File

@ -27,6 +27,7 @@
<ul <ul
v-if="!isStackedPlotObject" v-if="!isStackedPlotObject"
class="c-tree" class="c-tree"
aria-label="Plot Series Properties"
> >
<h2 title="Plot series display properties in this object">Plot Series</h2> <h2 title="Plot series display properties in this object">Plot Series</h2>
<plot-options-item <plot-options-item
@ -43,6 +44,7 @@
v-for="(yAxis, index) in yAxesWithSeries" v-for="(yAxis, index) in yAxesWithSeries"
:key="`yAxis-${index}`" :key="`yAxis-${index}`"
class="l-inspector-part js-yaxis-properties" class="l-inspector-part js-yaxis-properties"
:aria-label="yAxesWithSeries.length > 1 ? `Y Axis ${yAxis.id} Properties` : 'Y Axis Properties'"
> >
<h2 title="Y axis settings for this object">Y Axis {{ yAxesWithSeries.length > 1 ? yAxis.id : '' }}</h2> <h2 title="Y axis settings for this object">Y Axis {{ yAxesWithSeries.length > 1 ? yAxis.id : '' }}</h2>
<li class="grid-row"> <li class="grid-row">

View File

@ -27,6 +27,7 @@
<ul <ul
v-if="!isStackedPlotObject" v-if="!isStackedPlotObject"
class="c-tree" class="c-tree"
aria-label="Plot Series Properties"
> >
<h2 title="Display properties for this object">Plot Series</h2> <h2 title="Display properties for this object">Plot Series</h2>
<li <li

View File

@ -1,6 +1,9 @@
<template> <template>
<div v-if="loaded"> <div v-if="loaded">
<ul class="l-inspector-part"> <ul
class="l-inspector-part"
:aria-label="id > 1 ? `Y Axis ${id} Properties` : 'Y Axis Properties'"
>
<h2>Y Axis {{ id > 1 ? id : '' }}</h2> <h2>Y Axis {{ id > 1 ? id : '' }}</h2>
<li class="grid-row"> <li class="grid-row">
<div <div

View File

@ -20,7 +20,9 @@
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<template> <template>
<div></div> <div
:aria-label="`Stacked Plot Item ${childObject.name}`"
></div>
</template> </template>
<script> <script>
@ -102,13 +104,33 @@ export default {
}, },
mounted() { mounted() {
this.updateView(); this.updateView();
this.isEditing = this.openmct.editor.isEditing();
this.openmct.editor.on('isEditing', this.setEditState);
}, },
beforeDestroy() { beforeDestroy() {
this.openmct.editor.off('isEditing', this.setEditState);
if (this.removeSelectable) {
this.removeSelectable();
}
if (this.component) { if (this.component) {
this.component.$destroy(); this.component.$destroy();
} }
}, },
methods: { methods: {
setEditState(isEditing) {
this.isEditing = isEditing;
if (this.isEditing) {
this.setSelection();
} else {
if (this.removeSelectable) {
this.removeSelectable();
}
}
},
updateComponentProp(prop, value) { updateComponentProp(prop, value) {
if (this.component) { if (this.component) {
this.component[prop] = value; this.component[prop] = value;
@ -203,6 +225,10 @@ export default {
@loadingUpdated="loadingUpdated"/> @loadingUpdated="loadingUpdated"/>
</div>` </div>`
}); });
if (this.isEditing) {
this.setSelection();
}
}, },
onLockHighlightPointUpdated() { onLockHighlightPointUpdated() {
this.$emit('lockHighlightPoint', ...arguments); this.$emit('lockHighlightPoint', ...arguments);
@ -226,6 +252,17 @@ export default {
this.status = status; this.status = status;
this.updateComponentProp('status', status); this.updateComponentProp('status', status);
}, },
setSelection() {
let childContext = {};
childContext.item = this.childObject;
this.context = childContext;
if (this.removeSelectable) {
this.removeSelectable();
}
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context);
},
getProps() { getProps() {
return { return {
limitLineLabels: this.showLimitLineLabels, limitLineLabels: this.showLimitLineLabels,

View File

@ -21,7 +21,7 @@
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div></div> <div aria-label="Inspector Views"></div>
</template> </template>
<script> <script>