mirror of
https://github.com/nasa/openmct.git
synced 2024-12-18 20:57:53 +00:00
fix(conditional styling): conditional visibility for images and alpha-numerics in display layouts (#7824)
* fix: apply `is-style-invisible` className to image and alphanumeric items * test: generate storagestate file with basic condition set * refactor: small a11y additions for Toolbars * test: add suite for display layout conditional styling * fix: make condition true half of the time * fix: use a period of 5 so tests are more stable * test: mark as slow * test: use inline base64 image text instead of a url * fix: use vue reactivity system to conditionally show these objects * test: use tiny base64 image * fix: condition for v-show * fix: use both v-if and v-show to toggle visibility * refactor: convert to ES6 class * fix: remove focused test * fix: switch back to a div due to visual artifacts. settle for an aria role instead - IT'S CALLED COMPROMISE!
This commit is contained in:
parent
21a4335c4e
commit
440474b2e3
22
e2e/test-data/condition_set_storage.json
Normal file
22
e2e/test-data/condition_set_storage.json
Normal file
File diff suppressed because one or more lines are too long
@ -286,6 +286,55 @@ test.describe('Generate Visual Test Data @localStorage @generatedata @clock', ()
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Generate Conditional Styling Data @localStorage @generatedata', () => {
|
||||
test('Generate basic condition set', async ({ page, context }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
// Create a Condition Set
|
||||
const conditionSet = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Condition Set',
|
||||
name: 'Test Condition Set'
|
||||
});
|
||||
|
||||
// Create a Telemetry Object (Sine Wave Generator)
|
||||
const swg = await createExampleTelemetryObject(page, conditionSet.uuid);
|
||||
|
||||
// Edit the Telemetry Object to have a 10hz data rate (Gotta go fast!)
|
||||
await page.goto(swg.url);
|
||||
await page.getByLabel('More actions').click();
|
||||
await page.getByRole('menuitem', { name: 'Edit Properties...' }).click();
|
||||
await page.getByLabel('Period', { exact: true }).fill('5');
|
||||
await page.getByLabel('Save').click();
|
||||
|
||||
// Edit the Condition Set
|
||||
await page.goto(conditionSet.url);
|
||||
await page.getByLabel('Edit Object').click();
|
||||
|
||||
// Add a Condition to the Condition Set
|
||||
await page.getByLabel('Add Condition').click();
|
||||
await page.getByLabel('Condition Name Input').first().fill('Test Condition');
|
||||
await page.getByLabel('Condition Output Type').first().selectOption('String');
|
||||
await page.getByLabel('Condition Output String').first().fill('Test Condition Met');
|
||||
|
||||
// Condition: True if sine value > 0 (half the time)
|
||||
await page.getByLabel('Criterion Telemetry Selection').selectOption(swg.name);
|
||||
await page.getByLabel('Criterion Metadata Selection').selectOption('Sine');
|
||||
await page.getByLabel('Criterion Comparison Selection').selectOption('is greater than');
|
||||
await page.getByLabel('Criterion Input').first().fill('0');
|
||||
|
||||
// Rename default condition
|
||||
await page.getByLabel('Condition Output String').nth(1).fill('Test Condition Unmet');
|
||||
await page.getByLabel('Save').click();
|
||||
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||
|
||||
// Save localStorage for future test execution
|
||||
await context.storageState({
|
||||
path: fileURLToPath(
|
||||
new URL('../../../e2e/test-data/condition_set_storage.json', import.meta.url)
|
||||
)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Validate Overlay Plot with Telemetry Object @localStorage @generatedata', () => {
|
||||
test.use({
|
||||
storageState: fileURLToPath(
|
||||
|
@ -0,0 +1,114 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2024, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import {
|
||||
createDomainObjectWithDefaults,
|
||||
navigateToObjectWithRealTime
|
||||
} from '../../../../../appActions.js';
|
||||
import { expect, test } from '../../../../../pluginFixtures.js';
|
||||
|
||||
const TINY_IMAGE_BASE64 =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII';
|
||||
|
||||
test.describe('Display Layout Conditional Styling', () => {
|
||||
test.use({
|
||||
storageState: fileURLToPath(
|
||||
new URL('../../../../../test-data/condition_set_storage.json', import.meta.url)
|
||||
)
|
||||
});
|
||||
|
||||
let displayLayout;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
displayLayout = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Display Layout',
|
||||
name: 'Test Display Layout'
|
||||
});
|
||||
});
|
||||
|
||||
test('Image Drawing Object can have visibility toggled conditionally', async ({ page }) => {
|
||||
await page.getByLabel('Edit Object').click();
|
||||
|
||||
// Add Image Drawing Object to the layout
|
||||
await page.getByLabel('Add Drawing Object').click();
|
||||
await page.getByLabel('Image').click();
|
||||
await page.getByLabel('Image URL').fill(TINY_IMAGE_BASE64);
|
||||
await page.getByText('Ok').click();
|
||||
|
||||
// Use the "Test Condition Set" for conditional styling on the image
|
||||
await page.getByRole('tab', { name: 'Styles' }).click();
|
||||
await page.getByRole('button', { name: 'Use Conditional Styling...' }).click();
|
||||
await page.getByLabel('Modal Overlay').getByLabel('Expand My Items folder').click();
|
||||
await page.getByLabel('Modal Overlay').getByLabel('Preview Test Condition Set').click();
|
||||
await page.getByText('Ok').click();
|
||||
|
||||
// Set the image to be hidden when the condition is met
|
||||
await page.getByTitle('Visible').first().click();
|
||||
await page.getByLabel('Save Style').first().click();
|
||||
await page.getByLabel('Save', { exact: true }).click();
|
||||
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||
|
||||
// Switch to real-time mode and verify that the image toggles visibility
|
||||
await navigateToObjectWithRealTime(page, displayLayout.url);
|
||||
await expect(page.getByLabel('Image View')).toBeVisible();
|
||||
await expect(page.getByLabel('Image View')).toBeHidden();
|
||||
|
||||
// Reload the page and verify that the image toggles visibility
|
||||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||
await expect(page.getByLabel('Image View')).toBeVisible();
|
||||
await expect(page.getByLabel('Image View')).toBeHidden();
|
||||
});
|
||||
|
||||
test('Alphanumeric object can have visibility toggled conditionally', async ({ page }) => {
|
||||
await page.getByLabel('Edit Object').click();
|
||||
|
||||
// Add Alphanumeric Object to the layout
|
||||
await page.getByLabel('Expand My Items folder').click();
|
||||
await page.getByLabel('Expand Test Condition Set').click();
|
||||
await page.getByLabel('Preview VIPER Rover Heading').dragTo(page.getByLabel('Layout Grid'));
|
||||
|
||||
// Use the "Test Condition Set" for conditional styling on the alphanumeric
|
||||
await page.getByRole('tab', { name: 'Styles' }).click();
|
||||
await page.getByRole('button', { name: 'Use Conditional Styling...' }).click();
|
||||
await page.getByLabel('Modal Overlay').getByLabel('Expand My Items folder').click();
|
||||
await page.getByLabel('Modal Overlay').getByLabel('Preview Test Condition Set').click();
|
||||
await page.getByText('Ok').click();
|
||||
|
||||
// Set the alphanumeric to be hidden when the condition is met
|
||||
await page.getByTitle('Visible').first().click();
|
||||
await page.getByLabel('Save Style').first().click();
|
||||
await page.getByLabel('Save', { exact: true }).click();
|
||||
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||
|
||||
// Switch to real-time mode and verify that the image toggles visibility
|
||||
await navigateToObjectWithRealTime(page, displayLayout.url);
|
||||
await expect(page.getByLabel('Alpha-numeric telemetry', { exact: true })).toBeVisible();
|
||||
await expect(page.getByLabel('Alpha-numeric telemetry', { exact: true })).toBeHidden();
|
||||
|
||||
// Reload the page and verify that the alphanumeric toggles visibility
|
||||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||
await expect(page.getByLabel('Alpha-numeric telemetry', { exact: true })).toBeVisible();
|
||||
await expect(page.getByLabel('Alpha-numeric telemetry', { exact: true })).toBeHidden();
|
||||
});
|
||||
});
|
@ -27,14 +27,16 @@
|
||||
aria-label="Condition Set Condition Collection"
|
||||
>
|
||||
<div class="c-cs__header c-section__header">
|
||||
<span
|
||||
<button
|
||||
class="c-disclosure-triangle c-tree__item__view-control is-enabled"
|
||||
:class="{ 'c-disclosure-triangle--expanded': expanded }"
|
||||
@click="expanded = !expanded"
|
||||
></span>
|
||||
:aria-expanded="expanded"
|
||||
aria-controls="conditionContent"
|
||||
@click="toggleExpanded"
|
||||
></button>
|
||||
<div class="c-cs__header-label c-section__label">Conditions</div>
|
||||
</div>
|
||||
<div v-if="expanded" class="c-cs__content">
|
||||
<div v-if="expanded" id="conditionContent" class="c-cs__content">
|
||||
<div
|
||||
v-show="isEditing"
|
||||
class="hint"
|
||||
@ -54,9 +56,10 @@
|
||||
v-show="isEditing"
|
||||
id="addCondition"
|
||||
class="c-button c-button--major icon-plus labeled"
|
||||
aria-labelledby="addConditionButtonLabel"
|
||||
@click="addCondition"
|
||||
>
|
||||
<span class="c-cs-button__label">Add Condition</span>
|
||||
<span id="addConditionButtonLabel" class="c-cs-button__label">Add Condition</span>
|
||||
</button>
|
||||
|
||||
<div class="c-cs__conditions-h" :class="{ 'is-active-dragging': isDragging }">
|
||||
|
@ -29,7 +29,7 @@
|
||||
@end-move="endMove"
|
||||
>
|
||||
<template #content>
|
||||
<div class="c-image-view" :style="style"></div>
|
||||
<div v-show="showImage" aria-label="Image View" class="c-image-view" :style="style"></div>
|
||||
</template>
|
||||
</LayoutFrame>
|
||||
</template>
|
||||
@ -76,6 +76,9 @@ export default {
|
||||
},
|
||||
emits: ['move', 'end-move'],
|
||||
computed: {
|
||||
showImage() {
|
||||
return this.isEditing || !this.itemStyle?.isStyleInvisible;
|
||||
},
|
||||
style() {
|
||||
let backgroundImage = 'url(' + this.item.url + ')';
|
||||
let border = '1px solid ' + this.item.stroke;
|
||||
|
@ -31,9 +31,10 @@
|
||||
<template #content>
|
||||
<div
|
||||
v-if="domainObject"
|
||||
v-show="showTelemetry"
|
||||
ref="telemetryViewWrapper"
|
||||
class="c-telemetry-view u-style-receiver"
|
||||
:class="[itemClasses]"
|
||||
:class="classNames"
|
||||
:style="styleObject"
|
||||
:data-font-size="item.fontSize"
|
||||
:data-font="item.font"
|
||||
@ -151,7 +152,10 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
itemClasses() {
|
||||
showTelemetry() {
|
||||
return this.isEditing || !this.itemStyle?.isStyleInvisible;
|
||||
},
|
||||
classNames() {
|
||||
let classes = [];
|
||||
|
||||
if (this.status) {
|
||||
|
@ -25,65 +25,67 @@
|
||||
*
|
||||
* @interface ToolbarRegistry
|
||||
*/
|
||||
export default function ToolbarRegistry() {
|
||||
this.providers = {};
|
||||
export default class ToolbarRegistry {
|
||||
constructor() {
|
||||
this.providers = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets toolbar controls from providers which can provide a toolbar for this selection.
|
||||
*
|
||||
* @param {Object} selection the selection object
|
||||
* @returns {Object[]} an array of objects defining controls for the toolbar
|
||||
* @private for platform-internal use
|
||||
*/
|
||||
get(selection) {
|
||||
const providers = this.getAllProviders().filter(function (provider) {
|
||||
return provider.forSelection(selection);
|
||||
});
|
||||
|
||||
const structure = [];
|
||||
|
||||
providers.forEach((provider) => {
|
||||
provider.toolbar(selection).forEach((item) => structure.push(item));
|
||||
});
|
||||
|
||||
return structure;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getAllProviders() {
|
||||
return Object.values(this.providers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getByProviderKey(key) {
|
||||
return this.providers[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new type of toolbar.
|
||||
*
|
||||
* @param {module:openmct.ToolbarRegistry} provider the provider for this toolbar
|
||||
* @method addProvider
|
||||
*/
|
||||
addProvider(provider) {
|
||||
const key = provider.key;
|
||||
|
||||
if (key === undefined) {
|
||||
throw "Toolbar providers must have a unique 'key' property defined.";
|
||||
}
|
||||
|
||||
if (this.providers[key] !== undefined) {
|
||||
console.warn("Provider already defined for key '%s'. Provider keys must be unique.", key);
|
||||
}
|
||||
|
||||
this.providers[key] = provider;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets toolbar controls from providers which can provide a toolbar for this selection.
|
||||
*
|
||||
* @param {Object} selection the selection object
|
||||
* @returns {Object[]} an array of objects defining controls for the toolbar
|
||||
* @private for platform-internal use
|
||||
*/
|
||||
ToolbarRegistry.prototype.get = function (selection) {
|
||||
const providers = this.getAllProviders().filter(function (provider) {
|
||||
return provider.forSelection(selection);
|
||||
});
|
||||
|
||||
const structure = [];
|
||||
|
||||
providers.forEach((provider) => {
|
||||
provider.toolbar(selection).forEach((item) => structure.push(item));
|
||||
});
|
||||
|
||||
return structure;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ToolbarRegistry.prototype.getAllProviders = function () {
|
||||
return Object.values(this.providers);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ToolbarRegistry.prototype.getByProviderKey = function (key) {
|
||||
return this.providers[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers a new type of toolbar.
|
||||
*
|
||||
* @param {module:openmct.ToolbarRegistry} provider the provider for this toolbar
|
||||
* @method addProvider
|
||||
*/
|
||||
ToolbarRegistry.prototype.addProvider = function (provider) {
|
||||
const key = provider.key;
|
||||
|
||||
if (key === undefined) {
|
||||
throw "Toolbar providers must have a unique 'key' property defined.";
|
||||
}
|
||||
|
||||
if (this.providers[key] !== undefined) {
|
||||
console.warn("Provider already defined for key '%s'. Provider keys must be unique.", key);
|
||||
}
|
||||
|
||||
this.providers[key] = provider;
|
||||
};
|
||||
|
||||
/**
|
||||
* Exposes types of toolbars in Open MCT.
|
||||
*
|
||||
|
@ -34,9 +34,9 @@
|
||||
}"
|
||||
@click="onClick"
|
||||
>
|
||||
<div v-if="options.label" class="c-icon-button__label">
|
||||
<span v-if="options.label" class="c-icon-button__label">
|
||||
{{ options.label }}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -25,11 +25,13 @@
|
||||
class="c-icon-button c-icon-button--menu"
|
||||
:class="options.icon"
|
||||
:title="options.title"
|
||||
:aria-label="options.label"
|
||||
role="button"
|
||||
@click="toggle"
|
||||
>
|
||||
<div v-if="options.label" class="c-icon-button__label">
|
||||
<span v-if="options.label" class="c-icon-button__label">
|
||||
{{ options.label }}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="open" class="c-menu" role="menu">
|
||||
<ul>
|
||||
|
Loading…
Reference in New Issue
Block a user