Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recent objects do not update when object names are changed #6927

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion e2e/appActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,21 @@ async function getCanvasPixels(page, canvasSelector) {
return getTelemValuePromise;
}

/**
* @param {import('@playwright/test').Page} page
* @param {string} myItemsFolderName
* @param {string} url
* @param {string} newName
*/
async function renameObjectFromContextMenu(page, url, newName) {
await openObjectTreeContextMenu(page, url);
await page.click('li:text("Edit Properties")');
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
await nameInput.fill('');
await nameInput.fill(newName);
await page.click('[aria-label="Save"]');
}

// eslint-disable-next-line no-undef
module.exports = {
createDomainObjectWithDefaults,
Expand All @@ -639,5 +654,6 @@ module.exports = {
setTimeConductorBounds,
setIndependentTimeConductorBounds,
selectInspectorTab,
waitForPlotsToRender
waitForPlotsToRender,
renameObjectFromContextMenu
};
93 changes: 46 additions & 47 deletions e2e/tests/functional/recentObjects.e2e.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,59 +59,58 @@ test.describe('Recent Objects', () => {
await page.mouse.move(0, 100);
await page.mouse.up();
});
test.fixme(
'Navigated objects show up in recents, object renames and deletions are reflected',
async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6818'
});
test('Navigated objects show up in recents, object renames and deletions are reflected', async ({
page
}) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6818'
});

// Verify that both created objects appear in the list and are in the correct order
await assertInitialRecentObjectsListState();
// Verify that both created objects appear in the list and are in the correct order
await assertInitialRecentObjectsListState();

// Navigate to the folder by clicking on the main object name in the recent objects list item
await page.getByRole('listitem', { name: folderA.name }).getByText(folderA.name).click();
await page.waitForURL(`**/${folderA.uuid}?*`);
expect(recentObjectsList.getByRole('listitem').nth(0).getByText(folderA.name)).toBeTruthy();
// Navigate to the folder by clicking on the main object name in the recent objects list item
await page.getByRole('listitem', { name: folderA.name }).getByText(folderA.name).click();
await page.waitForURL(`**/${folderA.uuid}?*`);
expect(recentObjectsList.getByRole('listitem').nth(0).getByText(folderA.name)).toBeTruthy();

// Rename
folderA.name = `${folderA.name}-NEW!`;
await page.locator('.l-browse-bar__object-name').fill('');
await page.locator('.l-browse-bar__object-name').fill(folderA.name);
await page.keyboard.press('Enter');
// Rename
folderA.name = `${folderA.name}-NEW!`;
await page.locator('.l-browse-bar__object-name').fill('');
await page.locator('.l-browse-bar__object-name').fill(folderA.name);
await page.keyboard.press('Enter');

// Verify rename has been applied in recent objects list item and objects paths
expect(
await page
.getByRole('navigation', {
name: clock.name
})
.locator('a')
.filter({
hasText: folderA.name
})
.count()
).toBeGreaterThan(0);
expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeTruthy();

// Delete
await page.click('button[title="Show selected item in tree"]');
// Delete the folder via the left tree pane treeitem context menu
// Verify rename has been applied in recent objects list item and objects paths
expect(
await page
.getByRole('treeitem', { name: new RegExp(folderA.name) })
.getByRole('navigation', {
name: clock.name
})
.locator('a')
.click({
button: 'right'
});
await page.getByRole('menuitem', { name: /Remove/ }).click();
await page.getByRole('button', { name: 'OK' }).click();

// Verify that the folder and clock are no longer in the recent objects list
await expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeHidden();
await expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeHidden();
}
);
.filter({
hasText: folderA.name
})
.count()
).toBeGreaterThan(0);
expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeTruthy();

// Delete
await page.click('button[title="Show selected item in tree"]');
// Delete the folder via the left tree pane treeitem context menu
await page
.getByRole('treeitem', { name: new RegExp(folderA.name) })
.locator('a')
.click({
button: 'right'
});
await page.getByRole('menuitem', { name: /Remove/ }).click();
await page.getByRole('button', { name: 'OK' }).click();

// Verify that the folder and clock are no longer in the recent objects list
await expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeHidden();
await expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeHidden();
});

test('Clicking on an object in the path of a recent object navigates to the object', async ({
page,
Expand Down
74 changes: 74 additions & 0 deletions e2e/tests/functional/renaming.e2e.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*****************************************************************************
* 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 suite is dedicated to tests which verify branding related components.
*/

const { test, expect } = require('../../baseFixtures.js');
const {
createDomainObjectWithDefaults,
renameObjectFromContextMenu
} = require('../../appActions.js');

test.describe('Renaming objects', () => {
test.beforeEach(async ({ page }) => {
// Go to baseURL
await page.goto('./', { waitUntil: 'networkidle' });
});

test('When renaming objects, the browse bar and various components all update', async ({
page
}) => {
const folder = await createDomainObjectWithDefaults(page, {
type: 'Folder'
});
// Create a new 'Clock' object with default settings
const clock = await createDomainObjectWithDefaults(page, {
type: 'Clock',
parent: folder.uuid
});

// Rename
clock.name = `${clock.name}-NEW!`;
renameObjectFromContextMenu(page, clock.url, clock.name);
// check inspector for new name
await expect(page.locator(`.c-inspect-properties__value >> text=${clock.name}`)).toBeVisible();
// check browse bar for new name
await expect(page.locator(`.l-browse-bar >> text=${clock.name}`)).toBeVisible();
// check tree item for new name
await expect(
page.getByRole('listitem', {
name: clock.name
})
).toBeVisible();
// check recent objects for new name
await expect(
page.getByRole('navigation', {
name: clock.name
})
).toBeVisible();
// check title for new name
const title = await page.title();
expect(title).toBe(clock.name);
});
});
17 changes: 1 addition & 16 deletions e2e/tests/functional/tree.e2e.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
const { test, expect } = require('../../pluginFixtures.js');
const {
createDomainObjectWithDefaults,
openObjectTreeContextMenu
renameObjectFromContextMenu
} = require('../../appActions.js');

test.describe('Main Tree', () => {
Expand Down Expand Up @@ -249,18 +249,3 @@ async function expandTreePaneItemByName(page, name) {
});
await treeItem.locator('.c-disclosure-triangle').click();
}

/**
* @param {import('@playwright/test').Page} page
* @param {string} myItemsFolderName
* @param {string} url
* @param {string} newName
*/
async function renameObjectFromContextMenu(page, url, newName) {
await openObjectTreeContextMenu(page, url);
await page.click('li:text("Edit Properties")');
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
await nameInput.fill('');
await nameInput.fill(newName);
await page.click('[aria-label="Save"]');
}
37 changes: 37 additions & 0 deletions src/plugins/inspectorViews/properties/Location.vue
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,42 @@ export default {
}
},
async mounted() {
this.nameChangeListeners = {};
await this.createPathBreadCrumb();
},
unmounted() {
Object.values(this.nameChangeListeners).forEach((unlisten) => {
unlisten();
});
},
methods: {
updateObjectPathName(keyString, newName) {
this.pathBreadCrumb = this.pathBreadCrumb.map((pathObject) => {
if (this.openmct.objects.makeKeyString(pathObject.domainObject.identifier) === keyString) {
return {
...pathObject,
domainObject: { ...pathObject.domainObject, name: newName }
};
}
return pathObject;
});
},
removeNameListenerFor(domainObject) {
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
if (this.nameChangeListeners[keyString]) {
this.nameChangeListeners[keyString]();
}
},
addNameListenerFor(domainObject) {
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
if (!this.nameChangeListeners[keyString]) {
this.nameChangeListeners[keyString] = this.openmct.objects.observe(
domainObject,
'name',
this.updateObjectPathName.bind(this, keyString)
);
}
},
async createPathBreadCrumb() {
if (!this.domainObject && this.parentDomainObject) {
this.setPathBreadCrumb([this.parentDomainObject]);
Expand All @@ -99,6 +132,10 @@ export default {
});

this.pathBreadCrumb = pathBreadCrumb;

this.pathBreadCrumb.forEach((pathObject) => {
this.addNameListenerFor(pathObject.domainObject);
});
}
}
};
Expand Down
15 changes: 15 additions & 0 deletions src/plugins/inspectorViews/properties/Properties.vue
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,22 @@ export default {
return `detail-${component}`;
},
updateSelection(selection) {
this.removeListener();
this.selection.splice(0, this.selection.length, ...selection);
if (this.domainObject) {
this.addListener();
}
},
removeListener() {
if (this.nameListener) {
this.nameListener();
this.nameListener = null;
}
},
addListener() {
this.nameListener = this.openmct.objects.observe(this.context?.item, 'name', (newValue) => {
this.context.item = { ...this.context?.item, name: newValue };
});
}
}
};
Expand Down
36 changes: 36 additions & 0 deletions src/ui/components/ObjectPath.vue
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export default {
};
},
async mounted() {
this.nameChangeListeners = {};
const keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);

if (keyString && this.keyString !== keyString) {
Expand Down Expand Up @@ -108,8 +109,16 @@ export default {
// remove ROOT and object itself from path
this.orderedPath = pathWithDomainObject.slice(1, pathWithDomainObject.length - 1).reverse();
}
this.orderedPath.forEach((pathObject) => {
this.addNameListenerFor(pathObject.domainObject);
});
}
},
unmounted() {
Object.values(this.nameChangeListeners).forEach((unlisten) => {
unlisten();
});
},
methods: {
/**
* Generate the hash url for the given object path, removing the '/ROOT' prefix if present.
Expand All @@ -120,6 +129,33 @@ export default {
const path = `/browse/${this.openmct.objects.getRelativePath(objectPath)}`;

return path.replace('ROOT/', '');
},
updateObjectPathName(keyString, newName) {
this.orderedPath = this.orderedPath.map((pathObject) => {
if (this.openmct.objects.makeKeyString(pathObject.domainObject.identifier) === keyString) {
return {
...pathObject,
domainObject: { ...pathObject.domainObject, name: newName }
};
}
return pathObject;
});
},
removeNameListenerFor(domainObject) {
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
if (this.nameChangeListeners[keyString]) {
this.nameChangeListeners[keyString]();
}
},
addNameListenerFor(domainObject) {
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
if (!this.nameChangeListeners[keyString]) {
this.nameChangeListeners[keyString] = this.openmct.objects.observe(
domainObject,
'name',
this.updateObjectPathName.bind(this, keyString)
);
}
}
}
};
Expand Down
Loading