From 39f30c0c3a9ff23ac8c94f62a2933523a9e8bad7 Mon Sep 17 00:00:00 2001 From: Khalid Adil Date: Thu, 9 Nov 2023 11:56:27 -0600 Subject: [PATCH 01/34] Add css class to flexible layouts to allow them to be styled, reset selections on save, disable styling on flexible layout containers, and fix styling on plots on flexible layouts --- src/plugins/flexibleLayout/components/FlexibleLayout.vue | 2 +- .../inspectorViews/styles/StylesInspectorViewProvider.js | 9 ++++++--- src/plugins/plot/stackedPlot/StackedPlot.vue | 2 +- src/ui/components/ObjectView.vue | 6 +++++- src/ui/layout/BrowseBar.vue | 4 ++++ 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/plugins/flexibleLayout/components/FlexibleLayout.vue b/src/plugins/flexibleLayout/components/FlexibleLayout.vue index 6e92ef00e70..44a251c1af9 100644 --- a/src/plugins/flexibleLayout/components/FlexibleLayout.vue +++ b/src/plugins/flexibleLayout/components/FlexibleLayout.vue @@ -29,7 +29,7 @@
-1) { if (elemToStyle.style[key]) { - elemToStyle.style[key] = ''; + if (key === 'background-color') { + elemToStyle.style[key] = 'transparent'; + } else { + elemToStyle.style[key] = ''; + } } } else { if ( diff --git a/src/ui/layout/BrowseBar.vue b/src/ui/layout/BrowseBar.vue index ee57e99029f..4909c0e8a63 100644 --- a/src/ui/layout/BrowseBar.vue +++ b/src/ui/layout/BrowseBar.vue @@ -368,6 +368,10 @@ export default { title: 'Saving' }); + const currentSelection = this.openmct.selection.selected[0]; + const parentObject = currentSelection[currentSelection.length - 1]; + this.openmct.selection.select(parentObject); + return this.openmct.editor .save() .then(() => { From 30b34a2bcdf4913a2b93a73706921974b6ab22df Mon Sep 17 00:00:00 2001 From: Khalid Adil Date: Wed, 20 Dec 2023 05:48:20 -0600 Subject: [PATCH 02/34] Add tests --- e2e/tests/visual/styling.visual.spec.js | 301 ++++++++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 e2e/tests/visual/styling.visual.spec.js diff --git a/e2e/tests/visual/styling.visual.spec.js b/e2e/tests/visual/styling.visual.spec.js new file mode 100644 index 00000000000..22dca2792c9 --- /dev/null +++ b/e2e/tests/visual/styling.visual.spec.js @@ -0,0 +1,301 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2023, 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 is dedicated to test styling of various plugins + */ + +const { test, expect } = require('../../pluginFixtures'); +const { createDomainObjectWithDefaults } = require('../../appActions'); + +test.describe('Flexible Layout styling', () => { + let sineWaveObject; + let sineWaveObject2; + let treePane; + let sineWaveGeneratorTreeItem; + let sineWaveGeneratorTreeItem2; + let stackedPlotTreeItem; + let stackedPlot; + let flexibleLayout; + test.beforeEach(async ({ page }) => { + await page.goto('./', { waitUntil: 'domcontentloaded' }); + + // Expand the 'My Items' folder in the left tree + await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); + + // Create a few Sine Wave Generators + sineWaveObject = await createDomainObjectWithDefaults(page, { + type: 'Sine Wave Generator' + }); + + sineWaveObject2 = await createDomainObjectWithDefaults(page, { + type: 'Sine Wave Generator' + }); + + // Define the Sine Wave Generator items + treePane = page.getByRole('tree', { + name: 'Main Tree' + }); + sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', { + name: new RegExp(sineWaveObject.name) + }); + sineWaveGeneratorTreeItem2 = treePane.getByRole('treeitem', { + name: new RegExp(sineWaveObject2.name) + }); + + // Create a Stacked Plot + stackedPlot = await createDomainObjectWithDefaults(page, { + type: 'Stacked Plot' + }); + + // Add the Sine Wave Generators to the Stacked Plot + await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-plot--stacked.holder')); + await sineWaveGeneratorTreeItem2.dragTo(page.locator('.c-plot--stacked.holder')); + await page.locator('button[title="Save"]').click(); + await page.locator('text=Save and Finish Editing').click(); + + stackedPlotTreeItem = treePane.getByRole('treeitem', { + name: new RegExp(stackedPlot.name) + }); + + // Create a Flexible Layout + flexibleLayout = await createDomainObjectWithDefaults(page, { + type: 'Flexible Layout' + }); + + // Add the Sine Wave Generators and the Stacked Plot to the Flexible Layout + await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container').first()); + await sineWaveGeneratorTreeItem2.dragTo(page.locator('.c-fl__container').first()); + await stackedPlotTreeItem.dragTo(page.locator('.c-fl__container').nth(1)); + + await page.locator('button[title="Save"]').click(); + await page.locator('text=Save and Finish Editing').click(); + }); + + test('selecting a flexible layout column hides the styles tab', async ({ page }) => { + // Edit Flexible Layout + await page.locator('[title="Edit"]').click(); + // Expect to find a styles tab + let stylesTab = await page.locator('.c-inspector__tab[title="Styles"]').count(); + expect(stylesTab).toBe(1); + + // Select flexible layout column + await page.locator('.c-fl-container__header').first().click(); + // Expect to find no styles tab + stylesTab = await page.locator('.c-inspector__tab[title="Styles"]').count(); + expect(stylesTab).toBe(0); + }); + + test('styling the flexible layout properly applies the styles to the container', async ({ + page + }) => { + const backgroundColor = 'rgb(91, 15, 0)'; + const fontColor = 'rgb(230, 184, 175)'; + // Edit Flexible Layout + await page.locator('[title="Edit"]').click(); + // Select styles tab + await page.locator('.c-inspector__tab[title="Styles"]').click(); + // Set background color + await page.locator('.c-icon-button.icon-paint-bucket').click(); + await page.locator(`.c-palette__item[style="background: ${backgroundColor};"]`).click(); + // Set font color + await page.locator('.c-icon-button.icon-font').click(); + await page.locator(`.c-palette__item[style="background: ${fontColor};"]`).click(); + + // Find Flexible Layout container + let flexibleLayoutContainer = await page.locator('.c-fl__container-holder'); + let layoutStyles = await flexibleLayoutContainer.evaluate((el) => { + return { + background: window.getComputedStyle(el).getPropertyValue('background-color'), + fontColor: window.getComputedStyle(el).getPropertyValue('color') + }; + }); + + // Verify styles match set styles + expect(layoutStyles.background).toBe(backgroundColor); + expect(layoutStyles.fontColor).toBe(fontColor); + + // Save Flexible Layout + await page.locator('button[title="Save"]').click(); + await page.locator('text=Save and Finish Editing').click(); + + // Reload page and check styles again + await page.goto(flexibleLayout.url); + flexibleLayoutContainer = await page.locator('.c-fl__container-holder'); + layoutStyles = await flexibleLayoutContainer.evaluate((el) => { + return { + background: window.getComputedStyle(el).getPropertyValue('background-color'), + fontColor: window.getComputedStyle(el).getPropertyValue('color') + }; + }); + + // Verify styles match set styles + expect(layoutStyles.background).toBe(backgroundColor); + expect(layoutStyles.fontColor).toBe(fontColor); + }); + + test('styling a child object of the flexible layout properly applies that style to only that child', async ({ + page + }) => { + const backgroundColor = 'rgb(91, 15, 0)'; + const fontColor = 'rgb(230, 184, 175)'; + // Edit Flexible Layout + await page.locator('[title="Edit"]').click(); + // Select Stacked Plot + let stackedPlotContainer = await page.locator('.c-plot--stacked'); + // eslint-disable-next-line playwright/no-force-option + stackedPlotContainer.click({ force: true }); + + // Select styles tab + await page.locator('.c-inspector__tab[title="Styles"]').click(); + // Set background color + await page.locator('.c-icon-button.icon-paint-bucket').click(); + await page.locator(`.c-palette__item[style="background: ${backgroundColor};"]`).click(); + // Set font color + await page.locator('.c-icon-button.icon-font[title="Set text color"]').click(); + await page.locator(`.c-palette__item[style="background: ${fontColor};"]`).click(); + + // Get Stacked Plot styles + let stackedPlotStyles = await stackedPlotContainer.evaluate((el) => { + return { + background: window.getComputedStyle(el).getPropertyValue('background-color'), + fontColor: window.getComputedStyle(el).getPropertyValue('color') + }; + }); + + // Verify styles match set styles + expect(stackedPlotStyles.background).toBe(backgroundColor); + expect(stackedPlotStyles.fontColor).toBe(fontColor); + + // Save Flexible Layout + await page.locator('button[title="Save"]').click(); + await page.locator('text=Save and Finish Editing').click(); + + // Reload page and check styles again + await page.goto(flexibleLayout.url); + stackedPlotContainer = await page.locator('.c-plot--stacked'); + stackedPlotStyles = await stackedPlotContainer.evaluate((el) => { + return { + background: window.getComputedStyle(el).getPropertyValue('background-color'), + fontColor: window.getComputedStyle(el).getPropertyValue('color') + }; + }); + + // Verify styles match set styles + expect(stackedPlotStyles.background).toBe(backgroundColor); + expect(stackedPlotStyles.fontColor).toBe(fontColor); + }); + + test('When the "no style" option is selected, background and text should be reset to inherited styles', async ({ + page + }) => { + // Set background and font color on Stacked Plot object + const backgroundColor = 'rgb(91, 15, 0)'; + const fontColor = 'rgb(230, 184, 175)'; + const inheritedBackgroundColor = 'rgba(0, 0, 0, 0)'; // inherited from the theme + const inheritedFontColor = 'rgb(170, 170, 170)'; // inherited from the body style + // Edit Flexible Layout + await page.locator('[title="Edit"]').click(); + // Select Stacked Plot + let stackedPlotContainer = await page.locator('.c-plot--stacked'); + // eslint-disable-next-line playwright/no-force-option + stackedPlotContainer.click({ force: true }); + + // Select styles tab + await page.locator('.c-inspector__tab[title="Styles"]').click(); + // Set background color + await page.locator('.c-icon-button.icon-paint-bucket').click(); + await page.locator(`.c-palette__item[style="background: ${backgroundColor};"]`).click(); + // Set font color + await page.locator('.c-icon-button.icon-font[title="Set text color"]').click(); + await page.locator(`.c-palette__item[style="background: ${fontColor};"]`).click(); + + // Get Stacked Plot styles + let stackedPlotStyles = await stackedPlotContainer.evaluate((el) => { + return { + background: window.getComputedStyle(el).getPropertyValue('background-color'), + fontColor: window.getComputedStyle(el).getPropertyValue('color') + }; + }); + + // Verify styles match set styles + expect(stackedPlotStyles.background).toBe(backgroundColor); + expect(stackedPlotStyles.fontColor).toBe(fontColor); + + // Save Flexible Layout + await page.locator('button[title="Save"]').click(); + await page.locator('text=Save and Finish Editing').click(); + + // Reload page and set styles to "None" + await page.goto(flexibleLayout.url); + + // Edit Flexible Layout + await page.locator('[title="Edit"]').click(); + // Select Stacked Plot + stackedPlotContainer = await page.locator('.c-plot--stacked'); + // eslint-disable-next-line playwright/no-force-option + stackedPlotContainer.click({ force: true }); + + // Select styles tab + await page.locator('.c-inspector__tab[title="Styles"]').click(); + // Set background color + await page.locator('.c-icon-button.icon-paint-bucket').click(); + await page.locator(`.c-palette__item-none`).click(); + // Set font color + await page.locator('.c-icon-button.icon-font[title="Set text color"]').click(); + await page.locator(`.c-palette__item-none`).click(); + + // Get Stacked Plot styles + stackedPlotStyles = await stackedPlotContainer.evaluate((el) => { + return { + background: window.getComputedStyle(el).getPropertyValue('background-color'), + fontColor: window.getComputedStyle(el).getPropertyValue('color') + }; + }); + + // Verify styles match inherited styles + expect(stackedPlotStyles.background).toBe(inheritedBackgroundColor); + expect(stackedPlotStyles.fontColor).toBe(inheritedFontColor); + + // Save Flexible Layout + await page.locator('button[title="Save"]').click(); + await page.locator('text=Save and Finish Editing').click(); + + // Reload page and check styles again after resetting styles to "None" + await page.goto(flexibleLayout.url); + + // Select Stacked Plot + stackedPlotContainer = await page.locator('.c-plot--stacked'); + + // Get Stacked Plot styles + stackedPlotStyles = await stackedPlotContainer.evaluate((el) => { + return { + background: window.getComputedStyle(el).getPropertyValue('background-color'), + fontColor: window.getComputedStyle(el).getPropertyValue('color') + }; + }); + + // Verify styles match inherited styles + expect(stackedPlotStyles.background).toBe(inheritedBackgroundColor); + expect(stackedPlotStyles.fontColor).toBe(inheritedFontColor); + }); +}); From 58893763d99b4394f3118db4139d918f2413ac28 Mon Sep 17 00:00:00 2001 From: Khalid Adil Date: Wed, 20 Dec 2023 05:51:19 -0600 Subject: [PATCH 03/34] Add tests --- e2e/tests/visual/styling.visual.spec.js | 301 ++++++++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 e2e/tests/visual/styling.visual.spec.js diff --git a/e2e/tests/visual/styling.visual.spec.js b/e2e/tests/visual/styling.visual.spec.js new file mode 100644 index 00000000000..22dca2792c9 --- /dev/null +++ b/e2e/tests/visual/styling.visual.spec.js @@ -0,0 +1,301 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2023, 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 is dedicated to test styling of various plugins + */ + +const { test, expect } = require('../../pluginFixtures'); +const { createDomainObjectWithDefaults } = require('../../appActions'); + +test.describe('Flexible Layout styling', () => { + let sineWaveObject; + let sineWaveObject2; + let treePane; + let sineWaveGeneratorTreeItem; + let sineWaveGeneratorTreeItem2; + let stackedPlotTreeItem; + let stackedPlot; + let flexibleLayout; + test.beforeEach(async ({ page }) => { + await page.goto('./', { waitUntil: 'domcontentloaded' }); + + // Expand the 'My Items' folder in the left tree + await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); + + // Create a few Sine Wave Generators + sineWaveObject = await createDomainObjectWithDefaults(page, { + type: 'Sine Wave Generator' + }); + + sineWaveObject2 = await createDomainObjectWithDefaults(page, { + type: 'Sine Wave Generator' + }); + + // Define the Sine Wave Generator items + treePane = page.getByRole('tree', { + name: 'Main Tree' + }); + sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', { + name: new RegExp(sineWaveObject.name) + }); + sineWaveGeneratorTreeItem2 = treePane.getByRole('treeitem', { + name: new RegExp(sineWaveObject2.name) + }); + + // Create a Stacked Plot + stackedPlot = await createDomainObjectWithDefaults(page, { + type: 'Stacked Plot' + }); + + // Add the Sine Wave Generators to the Stacked Plot + await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-plot--stacked.holder')); + await sineWaveGeneratorTreeItem2.dragTo(page.locator('.c-plot--stacked.holder')); + await page.locator('button[title="Save"]').click(); + await page.locator('text=Save and Finish Editing').click(); + + stackedPlotTreeItem = treePane.getByRole('treeitem', { + name: new RegExp(stackedPlot.name) + }); + + // Create a Flexible Layout + flexibleLayout = await createDomainObjectWithDefaults(page, { + type: 'Flexible Layout' + }); + + // Add the Sine Wave Generators and the Stacked Plot to the Flexible Layout + await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container').first()); + await sineWaveGeneratorTreeItem2.dragTo(page.locator('.c-fl__container').first()); + await stackedPlotTreeItem.dragTo(page.locator('.c-fl__container').nth(1)); + + await page.locator('button[title="Save"]').click(); + await page.locator('text=Save and Finish Editing').click(); + }); + + test('selecting a flexible layout column hides the styles tab', async ({ page }) => { + // Edit Flexible Layout + await page.locator('[title="Edit"]').click(); + // Expect to find a styles tab + let stylesTab = await page.locator('.c-inspector__tab[title="Styles"]').count(); + expect(stylesTab).toBe(1); + + // Select flexible layout column + await page.locator('.c-fl-container__header').first().click(); + // Expect to find no styles tab + stylesTab = await page.locator('.c-inspector__tab[title="Styles"]').count(); + expect(stylesTab).toBe(0); + }); + + test('styling the flexible layout properly applies the styles to the container', async ({ + page + }) => { + const backgroundColor = 'rgb(91, 15, 0)'; + const fontColor = 'rgb(230, 184, 175)'; + // Edit Flexible Layout + await page.locator('[title="Edit"]').click(); + // Select styles tab + await page.locator('.c-inspector__tab[title="Styles"]').click(); + // Set background color + await page.locator('.c-icon-button.icon-paint-bucket').click(); + await page.locator(`.c-palette__item[style="background: ${backgroundColor};"]`).click(); + // Set font color + await page.locator('.c-icon-button.icon-font').click(); + await page.locator(`.c-palette__item[style="background: ${fontColor};"]`).click(); + + // Find Flexible Layout container + let flexibleLayoutContainer = await page.locator('.c-fl__container-holder'); + let layoutStyles = await flexibleLayoutContainer.evaluate((el) => { + return { + background: window.getComputedStyle(el).getPropertyValue('background-color'), + fontColor: window.getComputedStyle(el).getPropertyValue('color') + }; + }); + + // Verify styles match set styles + expect(layoutStyles.background).toBe(backgroundColor); + expect(layoutStyles.fontColor).toBe(fontColor); + + // Save Flexible Layout + await page.locator('button[title="Save"]').click(); + await page.locator('text=Save and Finish Editing').click(); + + // Reload page and check styles again + await page.goto(flexibleLayout.url); + flexibleLayoutContainer = await page.locator('.c-fl__container-holder'); + layoutStyles = await flexibleLayoutContainer.evaluate((el) => { + return { + background: window.getComputedStyle(el).getPropertyValue('background-color'), + fontColor: window.getComputedStyle(el).getPropertyValue('color') + }; + }); + + // Verify styles match set styles + expect(layoutStyles.background).toBe(backgroundColor); + expect(layoutStyles.fontColor).toBe(fontColor); + }); + + test('styling a child object of the flexible layout properly applies that style to only that child', async ({ + page + }) => { + const backgroundColor = 'rgb(91, 15, 0)'; + const fontColor = 'rgb(230, 184, 175)'; + // Edit Flexible Layout + await page.locator('[title="Edit"]').click(); + // Select Stacked Plot + let stackedPlotContainer = await page.locator('.c-plot--stacked'); + // eslint-disable-next-line playwright/no-force-option + stackedPlotContainer.click({ force: true }); + + // Select styles tab + await page.locator('.c-inspector__tab[title="Styles"]').click(); + // Set background color + await page.locator('.c-icon-button.icon-paint-bucket').click(); + await page.locator(`.c-palette__item[style="background: ${backgroundColor};"]`).click(); + // Set font color + await page.locator('.c-icon-button.icon-font[title="Set text color"]').click(); + await page.locator(`.c-palette__item[style="background: ${fontColor};"]`).click(); + + // Get Stacked Plot styles + let stackedPlotStyles = await stackedPlotContainer.evaluate((el) => { + return { + background: window.getComputedStyle(el).getPropertyValue('background-color'), + fontColor: window.getComputedStyle(el).getPropertyValue('color') + }; + }); + + // Verify styles match set styles + expect(stackedPlotStyles.background).toBe(backgroundColor); + expect(stackedPlotStyles.fontColor).toBe(fontColor); + + // Save Flexible Layout + await page.locator('button[title="Save"]').click(); + await page.locator('text=Save and Finish Editing').click(); + + // Reload page and check styles again + await page.goto(flexibleLayout.url); + stackedPlotContainer = await page.locator('.c-plot--stacked'); + stackedPlotStyles = await stackedPlotContainer.evaluate((el) => { + return { + background: window.getComputedStyle(el).getPropertyValue('background-color'), + fontColor: window.getComputedStyle(el).getPropertyValue('color') + }; + }); + + // Verify styles match set styles + expect(stackedPlotStyles.background).toBe(backgroundColor); + expect(stackedPlotStyles.fontColor).toBe(fontColor); + }); + + test('When the "no style" option is selected, background and text should be reset to inherited styles', async ({ + page + }) => { + // Set background and font color on Stacked Plot object + const backgroundColor = 'rgb(91, 15, 0)'; + const fontColor = 'rgb(230, 184, 175)'; + const inheritedBackgroundColor = 'rgba(0, 0, 0, 0)'; // inherited from the theme + const inheritedFontColor = 'rgb(170, 170, 170)'; // inherited from the body style + // Edit Flexible Layout + await page.locator('[title="Edit"]').click(); + // Select Stacked Plot + let stackedPlotContainer = await page.locator('.c-plot--stacked'); + // eslint-disable-next-line playwright/no-force-option + stackedPlotContainer.click({ force: true }); + + // Select styles tab + await page.locator('.c-inspector__tab[title="Styles"]').click(); + // Set background color + await page.locator('.c-icon-button.icon-paint-bucket').click(); + await page.locator(`.c-palette__item[style="background: ${backgroundColor};"]`).click(); + // Set font color + await page.locator('.c-icon-button.icon-font[title="Set text color"]').click(); + await page.locator(`.c-palette__item[style="background: ${fontColor};"]`).click(); + + // Get Stacked Plot styles + let stackedPlotStyles = await stackedPlotContainer.evaluate((el) => { + return { + background: window.getComputedStyle(el).getPropertyValue('background-color'), + fontColor: window.getComputedStyle(el).getPropertyValue('color') + }; + }); + + // Verify styles match set styles + expect(stackedPlotStyles.background).toBe(backgroundColor); + expect(stackedPlotStyles.fontColor).toBe(fontColor); + + // Save Flexible Layout + await page.locator('button[title="Save"]').click(); + await page.locator('text=Save and Finish Editing').click(); + + // Reload page and set styles to "None" + await page.goto(flexibleLayout.url); + + // Edit Flexible Layout + await page.locator('[title="Edit"]').click(); + // Select Stacked Plot + stackedPlotContainer = await page.locator('.c-plot--stacked'); + // eslint-disable-next-line playwright/no-force-option + stackedPlotContainer.click({ force: true }); + + // Select styles tab + await page.locator('.c-inspector__tab[title="Styles"]').click(); + // Set background color + await page.locator('.c-icon-button.icon-paint-bucket').click(); + await page.locator(`.c-palette__item-none`).click(); + // Set font color + await page.locator('.c-icon-button.icon-font[title="Set text color"]').click(); + await page.locator(`.c-palette__item-none`).click(); + + // Get Stacked Plot styles + stackedPlotStyles = await stackedPlotContainer.evaluate((el) => { + return { + background: window.getComputedStyle(el).getPropertyValue('background-color'), + fontColor: window.getComputedStyle(el).getPropertyValue('color') + }; + }); + + // Verify styles match inherited styles + expect(stackedPlotStyles.background).toBe(inheritedBackgroundColor); + expect(stackedPlotStyles.fontColor).toBe(inheritedFontColor); + + // Save Flexible Layout + await page.locator('button[title="Save"]').click(); + await page.locator('text=Save and Finish Editing').click(); + + // Reload page and check styles again after resetting styles to "None" + await page.goto(flexibleLayout.url); + + // Select Stacked Plot + stackedPlotContainer = await page.locator('.c-plot--stacked'); + + // Get Stacked Plot styles + stackedPlotStyles = await stackedPlotContainer.evaluate((el) => { + return { + background: window.getComputedStyle(el).getPropertyValue('background-color'), + fontColor: window.getComputedStyle(el).getPropertyValue('color') + }; + }); + + // Verify styles match inherited styles + expect(stackedPlotStyles.background).toBe(inheritedBackgroundColor); + expect(stackedPlotStyles.fontColor).toBe(inheritedFontColor); + }); +}); From 6ab76ba8630bf3e0e476e24c2e7a24618a7ddfa9 Mon Sep 17 00:00:00 2001 From: Khalid Adil Date: Wed, 20 Dec 2023 05:52:25 -0600 Subject: [PATCH 04/34] Remove accidental commit --- e2e/tests/visual/styling.visual.spec.js | 301 ------------------------ 1 file changed, 301 deletions(-) delete mode 100644 e2e/tests/visual/styling.visual.spec.js diff --git a/e2e/tests/visual/styling.visual.spec.js b/e2e/tests/visual/styling.visual.spec.js deleted file mode 100644 index 22dca2792c9..00000000000 --- a/e2e/tests/visual/styling.visual.spec.js +++ /dev/null @@ -1,301 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2023, 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 is dedicated to test styling of various plugins - */ - -const { test, expect } = require('../../pluginFixtures'); -const { createDomainObjectWithDefaults } = require('../../appActions'); - -test.describe('Flexible Layout styling', () => { - let sineWaveObject; - let sineWaveObject2; - let treePane; - let sineWaveGeneratorTreeItem; - let sineWaveGeneratorTreeItem2; - let stackedPlotTreeItem; - let stackedPlot; - let flexibleLayout; - test.beforeEach(async ({ page }) => { - await page.goto('./', { waitUntil: 'domcontentloaded' }); - - // Expand the 'My Items' folder in the left tree - await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); - - // Create a few Sine Wave Generators - sineWaveObject = await createDomainObjectWithDefaults(page, { - type: 'Sine Wave Generator' - }); - - sineWaveObject2 = await createDomainObjectWithDefaults(page, { - type: 'Sine Wave Generator' - }); - - // Define the Sine Wave Generator items - treePane = page.getByRole('tree', { - name: 'Main Tree' - }); - sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', { - name: new RegExp(sineWaveObject.name) - }); - sineWaveGeneratorTreeItem2 = treePane.getByRole('treeitem', { - name: new RegExp(sineWaveObject2.name) - }); - - // Create a Stacked Plot - stackedPlot = await createDomainObjectWithDefaults(page, { - type: 'Stacked Plot' - }); - - // Add the Sine Wave Generators to the Stacked Plot - await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-plot--stacked.holder')); - await sineWaveGeneratorTreeItem2.dragTo(page.locator('.c-plot--stacked.holder')); - await page.locator('button[title="Save"]').click(); - await page.locator('text=Save and Finish Editing').click(); - - stackedPlotTreeItem = treePane.getByRole('treeitem', { - name: new RegExp(stackedPlot.name) - }); - - // Create a Flexible Layout - flexibleLayout = await createDomainObjectWithDefaults(page, { - type: 'Flexible Layout' - }); - - // Add the Sine Wave Generators and the Stacked Plot to the Flexible Layout - await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container').first()); - await sineWaveGeneratorTreeItem2.dragTo(page.locator('.c-fl__container').first()); - await stackedPlotTreeItem.dragTo(page.locator('.c-fl__container').nth(1)); - - await page.locator('button[title="Save"]').click(); - await page.locator('text=Save and Finish Editing').click(); - }); - - test('selecting a flexible layout column hides the styles tab', async ({ page }) => { - // Edit Flexible Layout - await page.locator('[title="Edit"]').click(); - // Expect to find a styles tab - let stylesTab = await page.locator('.c-inspector__tab[title="Styles"]').count(); - expect(stylesTab).toBe(1); - - // Select flexible layout column - await page.locator('.c-fl-container__header').first().click(); - // Expect to find no styles tab - stylesTab = await page.locator('.c-inspector__tab[title="Styles"]').count(); - expect(stylesTab).toBe(0); - }); - - test('styling the flexible layout properly applies the styles to the container', async ({ - page - }) => { - const backgroundColor = 'rgb(91, 15, 0)'; - const fontColor = 'rgb(230, 184, 175)'; - // Edit Flexible Layout - await page.locator('[title="Edit"]').click(); - // Select styles tab - await page.locator('.c-inspector__tab[title="Styles"]').click(); - // Set background color - await page.locator('.c-icon-button.icon-paint-bucket').click(); - await page.locator(`.c-palette__item[style="background: ${backgroundColor};"]`).click(); - // Set font color - await page.locator('.c-icon-button.icon-font').click(); - await page.locator(`.c-palette__item[style="background: ${fontColor};"]`).click(); - - // Find Flexible Layout container - let flexibleLayoutContainer = await page.locator('.c-fl__container-holder'); - let layoutStyles = await flexibleLayoutContainer.evaluate((el) => { - return { - background: window.getComputedStyle(el).getPropertyValue('background-color'), - fontColor: window.getComputedStyle(el).getPropertyValue('color') - }; - }); - - // Verify styles match set styles - expect(layoutStyles.background).toBe(backgroundColor); - expect(layoutStyles.fontColor).toBe(fontColor); - - // Save Flexible Layout - await page.locator('button[title="Save"]').click(); - await page.locator('text=Save and Finish Editing').click(); - - // Reload page and check styles again - await page.goto(flexibleLayout.url); - flexibleLayoutContainer = await page.locator('.c-fl__container-holder'); - layoutStyles = await flexibleLayoutContainer.evaluate((el) => { - return { - background: window.getComputedStyle(el).getPropertyValue('background-color'), - fontColor: window.getComputedStyle(el).getPropertyValue('color') - }; - }); - - // Verify styles match set styles - expect(layoutStyles.background).toBe(backgroundColor); - expect(layoutStyles.fontColor).toBe(fontColor); - }); - - test('styling a child object of the flexible layout properly applies that style to only that child', async ({ - page - }) => { - const backgroundColor = 'rgb(91, 15, 0)'; - const fontColor = 'rgb(230, 184, 175)'; - // Edit Flexible Layout - await page.locator('[title="Edit"]').click(); - // Select Stacked Plot - let stackedPlotContainer = await page.locator('.c-plot--stacked'); - // eslint-disable-next-line playwright/no-force-option - stackedPlotContainer.click({ force: true }); - - // Select styles tab - await page.locator('.c-inspector__tab[title="Styles"]').click(); - // Set background color - await page.locator('.c-icon-button.icon-paint-bucket').click(); - await page.locator(`.c-palette__item[style="background: ${backgroundColor};"]`).click(); - // Set font color - await page.locator('.c-icon-button.icon-font[title="Set text color"]').click(); - await page.locator(`.c-palette__item[style="background: ${fontColor};"]`).click(); - - // Get Stacked Plot styles - let stackedPlotStyles = await stackedPlotContainer.evaluate((el) => { - return { - background: window.getComputedStyle(el).getPropertyValue('background-color'), - fontColor: window.getComputedStyle(el).getPropertyValue('color') - }; - }); - - // Verify styles match set styles - expect(stackedPlotStyles.background).toBe(backgroundColor); - expect(stackedPlotStyles.fontColor).toBe(fontColor); - - // Save Flexible Layout - await page.locator('button[title="Save"]').click(); - await page.locator('text=Save and Finish Editing').click(); - - // Reload page and check styles again - await page.goto(flexibleLayout.url); - stackedPlotContainer = await page.locator('.c-plot--stacked'); - stackedPlotStyles = await stackedPlotContainer.evaluate((el) => { - return { - background: window.getComputedStyle(el).getPropertyValue('background-color'), - fontColor: window.getComputedStyle(el).getPropertyValue('color') - }; - }); - - // Verify styles match set styles - expect(stackedPlotStyles.background).toBe(backgroundColor); - expect(stackedPlotStyles.fontColor).toBe(fontColor); - }); - - test('When the "no style" option is selected, background and text should be reset to inherited styles', async ({ - page - }) => { - // Set background and font color on Stacked Plot object - const backgroundColor = 'rgb(91, 15, 0)'; - const fontColor = 'rgb(230, 184, 175)'; - const inheritedBackgroundColor = 'rgba(0, 0, 0, 0)'; // inherited from the theme - const inheritedFontColor = 'rgb(170, 170, 170)'; // inherited from the body style - // Edit Flexible Layout - await page.locator('[title="Edit"]').click(); - // Select Stacked Plot - let stackedPlotContainer = await page.locator('.c-plot--stacked'); - // eslint-disable-next-line playwright/no-force-option - stackedPlotContainer.click({ force: true }); - - // Select styles tab - await page.locator('.c-inspector__tab[title="Styles"]').click(); - // Set background color - await page.locator('.c-icon-button.icon-paint-bucket').click(); - await page.locator(`.c-palette__item[style="background: ${backgroundColor};"]`).click(); - // Set font color - await page.locator('.c-icon-button.icon-font[title="Set text color"]').click(); - await page.locator(`.c-palette__item[style="background: ${fontColor};"]`).click(); - - // Get Stacked Plot styles - let stackedPlotStyles = await stackedPlotContainer.evaluate((el) => { - return { - background: window.getComputedStyle(el).getPropertyValue('background-color'), - fontColor: window.getComputedStyle(el).getPropertyValue('color') - }; - }); - - // Verify styles match set styles - expect(stackedPlotStyles.background).toBe(backgroundColor); - expect(stackedPlotStyles.fontColor).toBe(fontColor); - - // Save Flexible Layout - await page.locator('button[title="Save"]').click(); - await page.locator('text=Save and Finish Editing').click(); - - // Reload page and set styles to "None" - await page.goto(flexibleLayout.url); - - // Edit Flexible Layout - await page.locator('[title="Edit"]').click(); - // Select Stacked Plot - stackedPlotContainer = await page.locator('.c-plot--stacked'); - // eslint-disable-next-line playwright/no-force-option - stackedPlotContainer.click({ force: true }); - - // Select styles tab - await page.locator('.c-inspector__tab[title="Styles"]').click(); - // Set background color - await page.locator('.c-icon-button.icon-paint-bucket').click(); - await page.locator(`.c-palette__item-none`).click(); - // Set font color - await page.locator('.c-icon-button.icon-font[title="Set text color"]').click(); - await page.locator(`.c-palette__item-none`).click(); - - // Get Stacked Plot styles - stackedPlotStyles = await stackedPlotContainer.evaluate((el) => { - return { - background: window.getComputedStyle(el).getPropertyValue('background-color'), - fontColor: window.getComputedStyle(el).getPropertyValue('color') - }; - }); - - // Verify styles match inherited styles - expect(stackedPlotStyles.background).toBe(inheritedBackgroundColor); - expect(stackedPlotStyles.fontColor).toBe(inheritedFontColor); - - // Save Flexible Layout - await page.locator('button[title="Save"]').click(); - await page.locator('text=Save and Finish Editing').click(); - - // Reload page and check styles again after resetting styles to "None" - await page.goto(flexibleLayout.url); - - // Select Stacked Plot - stackedPlotContainer = await page.locator('.c-plot--stacked'); - - // Get Stacked Plot styles - stackedPlotStyles = await stackedPlotContainer.evaluate((el) => { - return { - background: window.getComputedStyle(el).getPropertyValue('background-color'), - fontColor: window.getComputedStyle(el).getPropertyValue('color') - }; - }); - - // Verify styles match inherited styles - expect(stackedPlotStyles.background).toBe(inheritedBackgroundColor); - expect(stackedPlotStyles.fontColor).toBe(inheritedFontColor); - }); -}); From e28abcbbc90de048fadc58f8495ed8b9ce41e072 Mon Sep 17 00:00:00 2001 From: John Hill Date: Thu, 21 Dec 2023 22:21:11 -0800 Subject: [PATCH 05/34] first pass --- .../styling.e2e.spec.js} | 82 ++++++++----------- 1 file changed, 32 insertions(+), 50 deletions(-) rename e2e/tests/{visual-a11y/styling.visual.spec.js => functional/styling.e2e.spec.js} (83%) diff --git a/e2e/tests/visual-a11y/styling.visual.spec.js b/e2e/tests/functional/styling.e2e.spec.js similarity index 83% rename from e2e/tests/visual-a11y/styling.visual.spec.js rename to e2e/tests/functional/styling.e2e.spec.js index 22dca2792c9..088b886be9b 100644 --- a/e2e/tests/visual-a11y/styling.visual.spec.js +++ b/e2e/tests/functional/styling.e2e.spec.js @@ -39,70 +39,52 @@ test.describe('Flexible Layout styling', () => { test.beforeEach(async ({ page }) => { await page.goto('./', { waitUntil: 'domcontentloaded' }); - // Expand the 'My Items' folder in the left tree - await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); - - // Create a few Sine Wave Generators - sineWaveObject = await createDomainObjectWithDefaults(page, { - type: 'Sine Wave Generator' - }); - - sineWaveObject2 = await createDomainObjectWithDefaults(page, { - type: 'Sine Wave Generator' - }); - - // Define the Sine Wave Generator items - treePane = page.getByRole('tree', { - name: 'Main Tree' - }); - sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', { - name: new RegExp(sineWaveObject.name) - }); - sineWaveGeneratorTreeItem2 = treePane.getByRole('treeitem', { - name: new RegExp(sineWaveObject2.name) + // Create a Flexible Layout and attach to the Stacked Plot + flexibleLayout = await createDomainObjectWithDefaults(page, { + type: 'Flexible Layout', + name: 'Flexible Layout' }); + console.log('flexibleLayout', flexibleLayout.uuid); - // Create a Stacked Plot + // Create a Stacked Plot and attach to the Flexible Layout stackedPlot = await createDomainObjectWithDefaults(page, { - type: 'Stacked Plot' + type: 'Stacked Plot', + name: 'Stacked Plot', + parent: flexibleLayout.uuid }); + console.log('stackedplot', stackedPlot.uuid); - // Add the Sine Wave Generators to the Stacked Plot - await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-plot--stacked.holder')); - await sineWaveGeneratorTreeItem2.dragTo(page.locator('.c-plot--stacked.holder')); - await page.locator('button[title="Save"]').click(); - await page.locator('text=Save and Finish Editing').click(); - - stackedPlotTreeItem = treePane.getByRole('treeitem', { - name: new RegExp(stackedPlot.name) + // Create two SWGs and attach them to the Stacked Plot + sineWaveObject = await createDomainObjectWithDefaults(page, { + type: 'Sine Wave Generator', + name: 'Sine Wave Generator 1', + parent: stackedPlot.uuid }); + console.log('sineWaveObject', sineWaveObject.uuid); - // Create a Flexible Layout - flexibleLayout = await createDomainObjectWithDefaults(page, { - type: 'Flexible Layout' + sineWaveObject2 = await createDomainObjectWithDefaults(page, { + type: 'Sine Wave Generator', + name: 'Sine Wave Generator 2', + parent: stackedPlot.uuid }); - - // Add the Sine Wave Generators and the Stacked Plot to the Flexible Layout - await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container').first()); - await sineWaveGeneratorTreeItem2.dragTo(page.locator('.c-fl__container').first()); - await stackedPlotTreeItem.dragTo(page.locator('.c-fl__container').nth(1)); - - await page.locator('button[title="Save"]').click(); - await page.locator('text=Save and Finish Editing').click(); + console.log('sineWaveObject2', sineWaveObject2.uuid); }); - test('selecting a flexible layout column hides the styles tab', async ({ page }) => { + test.only('selecting a flexible layout column hides the styles tab', async ({ page }) => { + await page.goto(flexibleLayout.url, { waitUntil: 'domcontentloaded' }); + // Edit Flexible Layout - await page.locator('[title="Edit"]').click(); - // Expect to find a styles tab - let stylesTab = await page.locator('.c-inspector__tab[title="Styles"]').count(); - expect(stylesTab).toBe(1); + await page.getByLabel('Edit').click(); + + // Expect to find styles tab + await expect(page.getByRole('tab', { name: 'Styles' })).toBeVisible(); // Select flexible layout column - await page.locator('.c-fl-container__header').first().click(); + // await page.getByLabel('Stacked Plot Frame').click(); + await page.getByRole('group', { name: /Container \b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/ }).click(); + // Expect to find no styles tab - stylesTab = await page.locator('.c-inspector__tab[title="Styles"]').count(); - expect(stylesTab).toBe(0); + await expect(page.getByRole('tab', { name: 'Styles' })).toBeHidden(); }); test('styling the flexible layout properly applies the styles to the container', async ({ From b462c6d498d10676b681847aba31666389c664bd Mon Sep 17 00:00:00 2001 From: John Hill Date: Fri, 22 Dec 2023 15:04:34 -0800 Subject: [PATCH 06/34] drive-by to improve flex layout git blame --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 6aa1fd0665d..3b2ed7400f7 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -4,6 +4,8 @@ # Requires Git > 2.23 # See https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-fileltfilegt +# vue-eslint update 2019 +14a0f84c1bcd56886d7c9e4e6afa8f7d292734e5 # Copyright year update 2022 4a9744e916d24122a81092f6b7950054048ba860 # Copyright year update 2023 From 3aaf74b46cebe311712c9aaa5a36c1a2e669eeb1 Mon Sep 17 00:00:00 2001 From: John Hill Date: Fri, 22 Dec 2023 15:04:47 -0800 Subject: [PATCH 07/34] drive-by to improve watch mode --- e2e/playwright-watch.config.js | 54 ++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 e2e/playwright-watch.config.js diff --git a/e2e/playwright-watch.config.js b/e2e/playwright-watch.config.js new file mode 100644 index 00000000000..d219d02ff16 --- /dev/null +++ b/e2e/playwright-watch.config.js @@ -0,0 +1,54 @@ +/* eslint-disable no-undef */ +// playwright.config.js +// @ts-check + +// eslint-disable-next-line no-unused-vars +const { devices } = require('@playwright/test'); +const MAX_FAILURES = 5; +const NUM_WORKERS = 2; + +/** @type {import('@playwright/test').PlaywrightTestConfig} */ +const config = { + retries: 0, //Retries 2 times for a total of 3 runs. When running sharded and with max-failures=5, this should ensure that flake is managed without failing the full suite + testDir: 'tests', + timeout: 60 * 1000, + webServer: { + command: 'npm run start', //Start in dev mode for hot reloading + url: 'http://localhost:8080/#', + timeout: 200 * 1000, + reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging. + }, + maxFailures: MAX_FAILURES, //Limits failures to 5 to reduce CI Waste + workers: NUM_WORKERS, //Limit to 2 for CircleCI Agent + use: { + baseURL: 'http://localhost:8080/', + headless: true, + ignoreHTTPSErrors: true, + screenshot: 'only-on-failure', + trace: 'on-first-retry', + video: 'off' + }, + projects: [ + { + name: 'chrome', + testMatch: '**/*.spec.js', // run all tests + use: { + browserName: 'chromium' + } + } + ], + reporter: [ + ['list'], + [ + 'html', + { + open: 'never', + outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840 + } + ], + ['junit', { outputFile: '../test-results/results.xml' }], + ['@deploysentinel/playwright'] + ] +}; + +module.exports = config; diff --git a/package.json b/package.json index fd35e7b3876..80e7b5ee431 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "test:e2e:visual:ci": "percy exec --config ./e2e/.percy.ci.yml --partial -- npx playwright test --config=e2e/playwright-visual-a11y.config.js --project=chrome --grep-invert @unstable", "test:e2e:visual:full": "percy exec --config ./e2e/.percy.nightly.yml -- npx playwright test --config=e2e/playwright-visual-a11y.config.js --grep-invert @unstable", "test:e2e:full": "npx playwright test --config=e2e/playwright-ci.config.js --grep-invert @couchdb", - "test:e2e:watch": "npx playwright test --ui --config=e2e/playwright-ci.config.js", + "test:e2e:watch": "npx playwright test --ui --config=e2e/playwright-watch.config.js", "test:perf:contract": "npx playwright test --config=e2e/playwright-performance-dev.config.js", "test:perf:localhost": "npx playwright test --config=e2e/playwright-performance-prod.config.js --project=chrome", "test:perf:memory": "npx playwright test --config=e2e/playwright-performance-prod.config.js --project=chrome-memory", From aef2432821a34f529eb00a6232fcca26352df7c7 Mon Sep 17 00:00:00 2001 From: John Hill Date: Fri, 22 Dec 2023 15:05:27 -0800 Subject: [PATCH 08/34] fix flexlayout toolbar options --- src/ui/toolbar/ToolbarContainer.vue | 2 +- src/ui/toolbar/components/ToolbarButton.vue | 2 ++ src/ui/toolbar/components/ToolbarSeparator.vue | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ui/toolbar/ToolbarContainer.vue b/src/ui/toolbar/ToolbarContainer.vue index 4a64ec545d9..6e771e065ee 100644 --- a/src/ui/toolbar/ToolbarContainer.vue +++ b/src/ui/toolbar/ToolbarContainer.vue @@ -20,7 +20,7 @@ at runtime from the About dialog for additional information. --> diff --git a/src/api/menu/components/SuperMenu.vue b/src/api/menu/components/SuperMenu.vue index 808750d8ca3..3e2157f9c6f 100644 --- a/src/api/menu/components/SuperMenu.vue +++ b/src/api/menu/components/SuperMenu.vue @@ -20,7 +20,12 @@ at runtime from the About dialog for additional information. -->