Skip to content

Commit 48fc569

Browse files
committed
[TypeRegistry, Create Menu] implement selectable plugins for create menu
1 parent a6517bb commit 48fc569

14 files changed

+704
-16
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*****************************************************************************
2+
* Open MCT, Copyright (c) 2014-2025, United States Government
3+
* as represented by the Administrator of the National Aeronautics and Space
4+
* Administration. All rights reserved.
5+
*
6+
* Open MCT is licensed under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
* License for the specific language governing permissions and limitations
15+
* under the License.
16+
*
17+
* Open MCT includes source code licensed under additional open source
18+
* licenses. See the Open Source Licenses file (LICENSES.md) included with
19+
* this source code distribution or the Licensing information page available
20+
* at runtime from the About dialog for additional information.
21+
*****************************************************************************/
22+
import { expect, test } from '../../pluginFixtures.js';
23+
24+
test.describe('Create button', () => {
25+
test.beforeEach(async ({ page }) => {
26+
await page.goto('./', { waitUntil: 'domcontentloaded' });
27+
});
28+
test('plugins can be selected', async ({ page }) => {
29+
await page.goto('./#/browse/mine');
30+
31+
//verify clicking the Create button will both open AND close the Create menu
32+
await page.getByRole('button', { name: 'Create' }).click();
33+
await expect(page.getByLabel('Select Plugins...')).toBeVisible();
34+
await page.getByRole('button', { name: 'Create' }).click();
35+
await expect(page.getByLabel('Select Plugins...')).toBeHidden();
36+
37+
//verify plugin selector form is visible
38+
await page.getByRole('button', { name: 'Create' }).click();
39+
await expect(page.getByRole('menuitem', { name: 'Clock' })).toBeVisible();
40+
await page.getByLabel('Select Plugins...').click();
41+
await expect(page.locator('.js-form-title')).toContainText('Plugin Selector');
42+
await expect(page.getByRole('button', { name: 'Save' })).toBeVisible();
43+
await expect(page.getByRole('button', { name: 'Cancel' })).toBeVisible();
44+
45+
//disable clock plugin
46+
await expect(page.locator('.plugin-selector-row').first()).toContainText('Clock');
47+
await expect(page.getByLabel('Clock plugin checkbox')).toBeChecked();
48+
await expect(page.getByLabel('Clock plugin checkbox')).toBeEnabled();
49+
await page.getByLabel('Clock plugin checkbox').uncheck();
50+
await page.getByRole('button', { name: 'Save' }).click();
51+
await expect(page.locator('.js-form-title')).toBeHidden();
52+
53+
//verify Clock plugin option is no longer in Create menu
54+
await page.getByRole('button', { name: 'Create' }).click();
55+
await expect(page.getByRole('menuitem', { name: 'Clock' })).toBeHidden();
56+
57+
//re-enable clock plugin
58+
await page.getByLabel('Select Plugins...').click();
59+
await expect(page.locator('.js-form-title')).toContainText('Plugin Selector');
60+
await expect(page.getByLabel('Clock plugin checkbox')).not.toBeChecked();
61+
await expect(page.getByLabel('Clock plugin checkbox')).toBeEnabled();
62+
await page.getByLabel('Clock plugin checkbox').click();
63+
await page.getByRole('button', { name: 'Save' }).click();
64+
await expect(page.locator('.js-form-title')).toBeHidden();
65+
66+
//verify Clock plugin option appears in Create menu
67+
await page.getByRole('button', { name: 'Create' }).click();
68+
await expect(page.getByRole('menuitem', { name: 'Clock' })).toBeVisible();
69+
70+
//activate clock plugin
71+
await page.getByRole('menuitem', { name: 'Clock' }).click();
72+
await page.getByRole('button', { name: 'Save' }).click();
73+
74+
//verify the active clock plugin cannot be disabled while in-use
75+
await page.getByRole('button', { name: 'Create' }).click();
76+
await page.getByLabel('Select Plugins...').click();
77+
await expect(page.locator('.plugin-selector-row').first()).toContainText('Clock');
78+
await expect(page.getByLabel('Clock plugin checkbox')).toBeChecked();
79+
await expect(page.getByLabel('Clock plugin checkbox')).toBeDisabled();
80+
await page.getByRole('button', { name: 'Cancel' }).click();
81+
82+
//remove active clock plugin
83+
await page.getByLabel('Expand My Items folder').click();
84+
await page.getByLabel('Navigate to Unnamed Clock clock Object').click({
85+
button: 'right'
86+
});
87+
await page.getByLabel('Remove').click();
88+
await expect(
89+
page.getByText(
90+
'Warning! This action will remove this object. Are you sure you want to continue?'
91+
)
92+
).toBeVisible();
93+
await page.getByRole('button', { name: 'Ok', exact: true }).click();
94+
95+
//verify the active clock plugin can be disabled since the plugin is no longer in-use
96+
await page.getByRole('button', { name: 'Create' }).click();
97+
await page.getByLabel('Select Plugins...').click();
98+
await expect(page.locator('.plugin-selector-row').first()).toContainText('Clock');
99+
await expect(page.getByLabel('Clock plugin checkbox')).toBeChecked();
100+
await expect(page.getByLabel('Clock plugin checkbox')).toBeEnabled();
101+
});
102+
});

e2e/tests/functional/plugins/clocks/clock.e2e.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ test.describe('Clock Generator CRUD Operations', () => {
4141
await page.getByRole('button', { name: 'Create' }).click();
4242

4343
// Click Clock
44-
await page.getByRole('menuitem').first().click();
44+
await page.getByRole('menuitem', { name: 'Clock' }).click();
4545

4646
// Click .icon-arrow-down
4747
await page.locator('.icon-arrow-down').click();

src/api/menu/components/SuperMenu.vue

+20-2
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,29 @@
5353
class="c-menu__section-separator"
5454
></div>
5555
<li v-if="actionGroups.length === 0" :key="index">No actions defined.</li>
56-
</div></template
57-
>
56+
</div>
57+
</template>
5858
</ul>
5959

6060
<ul v-else class="c-super-menu__menu" role="menu">
61+
<template v-if="options.menuHeaderActions">
62+
<div>
63+
<li
64+
v-for="action in options.menuHeaderActions"
65+
:key="action.name"
66+
role="menuitem"
67+
:class="action.cssClass"
68+
:aria-label="action.name"
69+
aria-describedby="item-description"
70+
@click="action.onItemClicked"
71+
@mouseover="toggleItemDescription(action)"
72+
@mouseleave="toggleItemDescription()"
73+
>
74+
{{ action.name }}
75+
</li>
76+
<div role="separator" class="c-menu__section-separator"></div>
77+
</div>
78+
</template>
6179
<li
6280
v-for="action in options.actions"
6381
:key="action.name"

src/api/types/TypeRegistry.js

+34-2
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,27 @@ export default class TypeRegistry {
4949
* @type {Record<string, Type>}
5050
*/
5151
this.types = {};
52+
53+
/**
54+
* @type {Record<string, Type>}
55+
*/
56+
this.deactivatedTypes = {};
5257
}
5358
/**
5459
* Register a new object type.
5560
*
5661
* @param {string} typeKey a string identifier for this type
5762
* @param {TypeDefinition} typeDef the type to add
63+
* @param {boolean} isDeactivated if true, will load type in a deactivated state
5864
*/
59-
addType(typeKey, typeDef) {
65+
addType(typeKey, typeDef, isDeactivated = false) {
6066
this.standardizeType(typeDef);
61-
this.types[typeKey] = new Type(typeDef);
67+
68+
if (isDeactivated) {
69+
this.deactivatedTypes[typeKey] = new Type(typeDef);
70+
} else {
71+
this.types[typeKey] = new Type(typeDef);
72+
}
6273
}
6374
/**
6475
* Takes a typeDef, standardizes it, and logs warnings about unsupported
@@ -74,13 +85,26 @@ export default class TypeRegistry {
7485
delete typeDef.label;
7586
}
7687
}
88+
removeType(typeKey) {
89+
delete this.types[typeKey];
90+
}
91+
removeDeactivatedType(typeKey) {
92+
delete this.deactivatedTypes[typeKey];
93+
}
7794
/**
7895
* List keys for all registered types.
7996
* @returns {string[]} all registered type keys
8097
*/
8198
listKeys() {
8299
return Object.keys(this.types);
83100
}
101+
/**
102+
* List keys for all deactivated types.
103+
* @returns {string[]} all deactivated type keys
104+
*/
105+
listDeactivatedKeys() {
106+
return Object.keys(this.deactivatedTypes);
107+
}
84108
/**
85109
* Retrieve a registered type by its key.
86110
* @param {string} typeKey the key for this type
@@ -89,6 +113,14 @@ export default class TypeRegistry {
89113
get(typeKey) {
90114
return this.types[typeKey] || UNKNOWN_TYPE;
91115
}
116+
/**
117+
* Retrieve a registered type that's deactivated by its key.
118+
* @param {string} typeKey the key for this deactivated type
119+
* @returns {Type} the registered type that's deactivated
120+
*/
121+
getDeactivated(typeKey) {
122+
return this.deactivatedTypes[typeKey] || UNKNOWN_TYPE;
123+
}
92124
importLegacyTypes(types) {
93125
types
94126
.filter((t) => this.get(t.key) === UNKNOWN_TYPE)

src/api/types/TypeRegistrySpec.js

+59
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,63 @@ describe('The Type API', function () {
5252
it('type registry contains new keys', function () {
5353
expect(typeRegistryInstance.listKeys()).toContain('testType');
5454
});
55+
56+
it('types can be removed', function () {
57+
expect(typeRegistryInstance.get('testType').definition.name).toBe('Test Type');
58+
typeRegistryInstance.removeType('testType');
59+
expect(typeRegistryInstance.get('testType').definition.name).toBe('Unknown Type');
60+
});
61+
62+
it('types can be added in a deactivated state', function () {
63+
typeRegistryInstance.addType(
64+
'deactivatedTestType',
65+
{
66+
name: 'Deactivated Test Type',
67+
description: 'This is a deactivated test type.',
68+
creatable: true
69+
},
70+
true
71+
);
72+
73+
expect(typeRegistryInstance.get('deactivatedTestType').definition.name).toBe('Unknown Type');
74+
expect(typeRegistryInstance.getDeactivated('deactivatedTestType').definition.name).toBe(
75+
'Deactivated Test Type'
76+
);
77+
});
78+
79+
it('deactivated types contain keys', function () {
80+
typeRegistryInstance.addType(
81+
'deactivatedTestType',
82+
{
83+
name: 'Deactivated Test Type',
84+
description: 'This is a deactivated test type.',
85+
creatable: true
86+
},
87+
true
88+
);
89+
90+
let deactivatedKeys = typeRegistryInstance.listDeactivatedKeys();
91+
expect(deactivatedKeys.length).toBe(1);
92+
expect(deactivatedKeys[0]).toBe('deactivatedTestType');
93+
});
94+
95+
it('deactivated types can be removed', function () {
96+
typeRegistryInstance.addType(
97+
'deactivatedTestType',
98+
{
99+
name: 'Deactivated Test Type',
100+
description: 'This is a deactivated test type.',
101+
creatable: true
102+
},
103+
true
104+
);
105+
106+
expect(typeRegistryInstance.getDeactivated('deactivatedTestType').definition.name).toBe(
107+
'Deactivated Test Type'
108+
);
109+
typeRegistryInstance.removeDeactivatedType('deactivatedTestType');
110+
expect(typeRegistryInstance.getDeactivated('deactivatedTestType').definition.name).toBe(
111+
'Unknown Type'
112+
);
113+
});
55114
});

0 commit comments

Comments
 (0)