fix(#6942): Toggling FlexibleLayout toolbar options reflects immediately in the view (#6943)

* fix: restore reactivity of config settings

- move initialization steps to `created()` hook

- remove unnecessary `:key` binds

- fix comments

* refactor: clean up

* refactor: clean up

* refactor: lint:fix

* test(e2e): add regression test and cleanup suite

* refactor: consistency is key!

* test(fix): fix unit tests, further cleanup
This commit is contained in:
Jesse Mazzella 2023-08-16 10:52:23 -07:00 committed by GitHub
parent 82b1760b0e
commit 6c92e31036
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 127 additions and 74 deletions

View File

@ -29,6 +29,10 @@ const {
test.describe('Flexible Layout', () => {
let sineWaveObject;
let clockObject;
let treePane;
let sineWaveGeneratorTreeItem;
let clockTreeItem;
let flexibleLayout;
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
@ -41,23 +45,27 @@ test.describe('Flexible Layout', () => {
clockObject = await createDomainObjectWithDefaults(page, {
type: 'Clock'
});
// Create a Flexible Layout
flexibleLayout = await createDomainObjectWithDefaults(page, {
type: 'Flexible Layout'
});
// Define the Sine Wave Generator and Clock tree items
treePane = page.getByRole('tree', {
name: 'Main Tree'
});
sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(sineWaveObject.name)
});
clockTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(clockObject.name)
});
});
test('panes have the appropriate draggable attribute while in Edit and Browse modes', async ({
page
}) => {
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(sineWaveObject.name)
});
const clockTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(clockObject.name)
});
// Create a Flexible Layout
await createDomainObjectWithDefaults(page, {
type: 'Flexible Layout'
});
await page.goto(flexibleLayout.url);
// Edit Flexible Layout
await page.locator('[title="Edit"]').click();
@ -78,19 +86,79 @@ test.describe('Flexible Layout', () => {
dragWrapper = page.locator('.c-fl-container__frames-holder .c-fl-frame__drag-wrapper').first();
await expect(dragWrapper).toHaveAttribute('draggable', 'false');
});
test('changing toolbar settings in edit mode is immediately reflected and persists upon save', async ({
page
}) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6942'
});
await page.goto(flexibleLayout.url);
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
// Add the Sine Wave Generator and Clock to the Flexible Layout
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
await clockTreeItem.dragTo(page.locator('.c-fl__container.is-empty'));
// Click on the first frame to select it
await page.locator('.c-fl-container__frame').first().click();
await expect(page.locator('.c-fl-container__frame > .c-frame').first()).toHaveAttribute(
's-selected',
''
);
// Assert the toolbar is visible
await expect(page.locator('.c-toolbar')).toBeInViewport();
// Assert the layout is in columns orientation
expect(await page.locator('.c-fl--rows').count()).toEqual(0);
// Change the layout to rows orientation
await page.getByTitle('Columns layout').click();
// Assert the layout is in rows orientation
expect(await page.locator('.c-fl--rows').count()).toBeGreaterThan(0);
// Assert the frame of the first item is visible
await expect(page.locator('.c-so-view').first()).not.toHaveClass(/c-so-view--no-frame/);
// Hide the frame of the first item
await page.getByTitle('Frame visible').click();
// Assert the frame is hidden
await expect(page.locator('.c-so-view').first()).toHaveClass(/c-so-view--no-frame/);
// Assert there are 2 containers
expect(await page.locator('.c-fl-container').count()).toEqual(2);
// Add a container
await page.getByTitle('Add Container').click();
// Assert there are 3 containers
expect(await page.locator('.c-fl-container').count()).toEqual(3);
// Save Flexible Layout
await page.locator('button[title="Save"]').click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Nav away and back
await page.goto(sineWaveObject.url);
await page.goto(flexibleLayout.url);
// Wait for the first frame to be visible so we know the layout has loaded
await expect(page.locator('.c-fl-container').nth(0)).toBeInViewport();
// Assert the settings have persisted
expect(await page.locator('.c-fl-container').count()).toEqual(3);
expect(await page.locator('.c-fl--rows').count()).toBeGreaterThan(0);
await expect(page.locator('.c-so-view').first()).toHaveClass(/c-so-view--no-frame/);
});
test('items in a flexible layout can be removed with object tree context menu when viewing the flexible layout', async ({
page
}) => {
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(sineWaveObject.name)
});
// Create a Display Layout
await createDomainObjectWithDefaults(page, {
type: 'Flexible Layout'
});
await page.goto(flexibleLayout.url);
// Edit Flexible Layout
await page.locator('[title="Edit"]').click();
@ -121,17 +189,7 @@ test.describe('Flexible Layout', () => {
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/3117'
});
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(sineWaveObject.name)
});
// Create a Flexible Layout
const flexibleLayout = await createDomainObjectWithDefaults(page, {
type: 'Flexible Layout'
});
await page.goto(flexibleLayout.url);
// Edit Flexible Layout
await page.locator('[title="Edit"]').click();
@ -167,19 +225,13 @@ test.describe('Flexible Layout', () => {
const exampleImageryObject = await createDomainObjectWithDefaults(page, {
type: 'Example Imagery'
});
// Create a Flexible Layout
await createDomainObjectWithDefaults(page, {
type: 'Flexible Layout'
});
// Edit Display Layout
await page.goto(flexibleLayout.url);
// Edit Flexible Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the Flexible Layout and save changes
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const exampleImageryTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(exampleImageryObject.name)
});

View File

@ -20,14 +20,14 @@
at runtime from the About dialog for additional information.
-->
<template>
<layout-frame
<LayoutFrame
:item="item"
:grid-size="gridSize"
:is-editing="isEditing"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')"
>
<object-frame
<ObjectFrame
v-if="domainObject"
ref="objectFrame"
:domain-object="domainObject"
@ -37,7 +37,7 @@
:layout-font-size="item.fontSize"
:layout-font="item.font"
/>
</layout-frame>
</LayoutFrame>
</template>
<script>

View File

@ -37,7 +37,6 @@
<template v-for="(container, index) in containers" :key="`component-${container.id}`">
<drop-hint
v-if="index === 0 && containers.length > 1"
:key="`hint-top-${container.id}`"
class="c-fl-frame__drop-hint"
:index="-1"
:allow-drop="allowContainerDrop"
@ -59,7 +58,6 @@
<resize-handle
v-if="index !== containers.length - 1"
:key="`handle-${container.id}`"
:index="index"
:orientation="rowsLayout ? 'vertical' : 'horizontal'"
:is-editing="isEditing"
@ -70,7 +68,6 @@
<drop-hint
v-if="containers.length > 1"
:key="`hint-bottom-${container.id}`"
class="c-fl-frame__drop-hint"
:index="index"
:allow-drop="allowContainerDrop"
@ -137,15 +134,16 @@ export default {
ResizeHandle,
DropHint
},
inject: ['openmct', 'objectPath', 'layoutObject'],
inject: ['openmct', 'objectPath', 'domainObject'],
props: {
isEditing: Boolean
},
data() {
return {
domainObject: this.layoutObject,
newFrameLocation: [],
identifierMap: {}
identifierMap: {},
containers: this.domainObject.configuration.containers,
rowsLayout: this.domainObject.configuration.rowsLayout
};
},
computed: {
@ -156,22 +154,22 @@ export default {
return 'Columns';
}
},
containers() {
return this.domainObject.configuration.containers;
},
rowsLayout() {
return this.domainObject.configuration.rowsLayout;
},
allContainersAreEmpty() {
return this.containers.every((container) => container.frames.length === 0);
}
},
mounted() {
created() {
this.buildIdentifierMap();
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('remove', this.removeChildObject);
this.composition.on('add', this.addFrame);
this.composition.load();
this.openmct.objects.observe(this.domainObject, 'configuration.containers', (containers) => {
this.containers = containers;
});
this.openmct.objects.observe(this.domainObject, 'configuration.rowsLayout', (rowsLayout) => {
this.rowsLayout = rowsLayout;
});
},
beforeUnmount() {
this.composition.off('remove', this.removeChildObject);
@ -211,20 +209,16 @@ export default {
let container = this.containers.filter((c) => c.id === containerId)[0];
let containerIndex = this.containers.indexOf(container);
/*
remove associated domainObjects from composition
*/
// remove associated domainObjects from composition
container.frames.forEach((f) => {
this.removeFromComposition(f.domainObjectIdentifier);
});
this.containers.splice(containerIndex, 1);
/*
add a container when there are no containers in the FL,
to prevent user from not being able to add a frame via
drag and drop.
*/
// add a container when there are no containers in the FL,
// to prevent user from not being able to add a frame via
// drag and drop.
if (this.containers.length === 0) {
this.containers.push(new Container(100));
}

View File

@ -47,17 +47,16 @@ export default class FlexibleLayoutViewProvider {
let component = null;
return {
show: function (element, isEditing) {
show(element, isEditing) {
const { vNode, destroy } = mount(
{
el: element,
components: {
FlexibleLayoutComponent
},
provide: {
openmct: openmct,
openmct,
objectPath,
layoutObject: domainObject
domainObject
},
data() {
return {
@ -75,7 +74,7 @@ export default class FlexibleLayoutViewProvider {
component = vNode.componentInstance;
_destroy = destroy;
},
getSelectionContext: function () {
getSelectionContext() {
return {
item: domainObject,
addContainer: component.$refs.flexibleLayout.addContainer,
@ -84,10 +83,10 @@ export default class FlexibleLayoutViewProvider {
type: 'flexible-layout'
};
},
onEditModeChange: function (isEditing) {
onEditModeChange(isEditing) {
component.isEditing = isEditing;
},
destroy: function (element) {
destroy() {
if (_destroy) {
_destroy();
component = null;

View File

@ -33,6 +33,10 @@ describe('the plugin', function () {
let mockComposition;
const testViewObject = {
identifier: {
namespace: '',
key: 'test-object'
},
id: 'test-object',
type: 'flexible-layout',
configuration: {
@ -116,6 +120,10 @@ describe('the plugin', function () {
beforeEach(() => {
flexibleLayoutItem = {
identifier: {
namespace: '',
key: 'test-object'
},
id: 'test-object',
type: 'flexible-layout',
configuration: {