UI: Remove extra request for LDAP engine config (#10919) (#10947)

* remove request for ldap config;

* simplify tabs to accept routes as args

* reuse tabs in ember engines

Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com>
This commit is contained in:
Vault Automation 2025-11-19 19:26:36 -05:00 committed by GitHub
parent d602d6164f
commit 51c6f056bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 216 additions and 283 deletions

View file

@ -1,23 +0,0 @@
{{!
Copyright IBM Corp. 2016, 2025
SPDX-License-Identifier: BUSL-1.1
}}
<nav class="tabs" aria-label={{@engineMetadata.displayName}}>
<ul>
<li data-test-tab="general-settings">
<LinkTo @route="{{this.routePrefix}}configuration.general-settings" @model={{@path}}>
General settings
</LinkTo>
</li>
{{! If there are not engine-specific settings do not render this tab }}
{{#if @engineMetadata.isConfigurable}}
<li data-test-tab="plugin-settings">
<LinkTo @route={{this.pluginSettingsRoute}} @model={{@path}}>
{{@engineMetadata.displayName}}
settings
</LinkTo>
</li>
{{/if}}
</ul>
</nav>

View file

@ -1,48 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Component from '@glimmer/component';
import type { EngineDisplayData } from 'vault/utils/all-engines-metadata';
/**
* @module ConfigureTabs
* These tabs render in the shared general-settings, plugin-settings and edit routes of the secret engines headers.
*
* @param {string} [configRoute] - only passed when rendering the vault.cluster.secrets.backend.configuration.edit route to highlight the tab for that view
* @param {object} engineMetadata - engine specific metadata
* @param {boolean} [isConfigured] - whether an engine has been configured. if configured, plugin settings exist
* @param {object} path - the secret engine mount path, sometimes referred to as the engine "id" or "backend"
*/
interface Args {
configRoute?: string;
engineMetadata: EngineDisplayData;
isConfigured: boolean;
path: string;
}
export default class ConfigureTabs extends Component<Args> {
routePrefix = 'vault.cluster.secrets.backend.';
// The plugin settings tab only renders if an engine is configurable.
// `configEditRoute` and `configReadRoute` are defined for engines with custom route patterns, like Ember engines.
// Otherwise navigates to default 'backend.configuration' routes.
get pluginSettingsRoute() {
const { engineMetadata, isConfigured } = this.args;
// If the engine is configurable, but not configured, navigate to its edit route
if (engineMetadata.isConfigurable && !isConfigured) {
const route = engineMetadata.configEditRoute || 'configuration.edit';
return this.routePrefix + route;
}
// For configured engines, determine route based on context:
// - If @configRoute is passed (from edit.hbs) the user has navigated to edit and this ensures the tab is highlighted.
// - Otherwise, route to the read view (`configReadRoute` or 'plugin-settings')
const route = this.args.configRoute || engineMetadata?.configReadRoute || 'configuration.plugin-settings';
return this.routePrefix + route;
}
}

View file

@ -7,15 +7,17 @@
@title="{{@model.secretsEngine.id}} configuration"
@description={{engineDisplayData.displayName}}
@icon={{engineDisplayData.glyph}}
@subtitle={{engineDisplayData.typeDisplay}}
>
<:breadcrumbs>
<Page::Breadcrumbs @breadcrumbs={{@breadcrumbs}} />
</:breadcrumbs>
<:tabs>
<SecretEngine::ConfigureTabs
@engineMetadata={{engineDisplayData}}
@isConfigured={{not (is-empty-value @model.config)}}
<Mount::ConfigureTabs
@configRoute={{if
engineDisplayData.isConfigurable
(or engineDisplayData.configRoute "configuration.plugin-settings")
}}
@displayName={{engineDisplayData.displayName}}
@path={{@model.secretsEngine.id}}
/>
</:tabs>

View file

@ -2,20 +2,20 @@
Copyright IBM Corp. 2016, 2025
SPDX-License-Identifier: BUSL-1.1
}}
{{#let (engines-display-data @model.secretsEngine.type) as |engineDisplayData|}}
<Page::Header
@title="{{@model.secretsEngine.id}} configuration"
@description={{engineDisplayData.displayName}}
@icon={{engineDisplayData.glyph}}
@subtitle={{engineDisplayData.typeDisplay}}
>
<:breadcrumbs>
<Page::Breadcrumbs @breadcrumbs={{@breadcrumbs}} />
</:breadcrumbs>
<:tabs>
<SecretEngine::ConfigureTabs
@engineMetadata={{engineDisplayData}}
@isConfigured={{not (is-empty-value @model.config)}}
<Mount::ConfigureTabs
@configRoute={{or engineDisplayData.configRoute "configuration.plugin-settings"}}
@displayName={{engineDisplayData.displayName}}
@path={{@model.secretsEngine.id}}
/>
</:tabs>
@ -28,70 +28,62 @@
/>
</:actions>
</Page::Header>
{{/let}}
{{#if @model.config}}
<Toolbar>
<ToolbarActions>
<ToolbarLink
@route="vault.cluster.secrets.backend.configuration.edit"
@model={{this.model.secretsEngine.id}}
data-test-secret-backend-configure
>
Edit configuration
</ToolbarLink>
</ToolbarActions>
</Toolbar>
{{#if @model.config}}
<Toolbar>
<ToolbarActions>
<ToolbarLink
@route="vault.cluster.secrets.backend.configuration.edit"
@model={{this.model.secretsEngine.id}}
data-test-secret-backend-configure
>
Edit configuration
</ToolbarLink>
</ToolbarActions>
</Toolbar>
{{#each this.displayFields as |field|}}
{{! public key while not sensitive when editing/creating, should be hidden by default on viewing }}
{{#if (eq field "public_key")}}
<InfoTableRow @label="Public key" @value={{@model.config.public_key}}>
<MaskedInput @value={{@model.config.public_key}} @name={{field}} @displayOnly={{true}} @allowCopy={{true}} />
</InfoTableRow>
{{else}}
<InfoTableRow
@alwaysRender={{not (is-empty-value (get @model.config field))}}
@label={{this.label field}}
@value={{get @model.config field}}
@formatTtl={{this.isDuration field}}
/>
{{/if}}
{{/each}}
{{else}}
{{#if (get (engines-display-data @model.secretsEngine.type) "isConfigurable")}}
{{#each this.displayFields as |field|}}
{{! public key while not sensitive when editing/creating, should be hidden by default on viewing }}
{{#if (eq field "public_key")}}
<InfoTableRow @label="Public key" @value={{@model.config.public_key}}>
<MaskedInput @value={{@model.config.public_key}} @name={{field}} @displayOnly={{true}} @allowCopy={{true}} />
</InfoTableRow>
{{else}}
<InfoTableRow
@alwaysRender={{not (is-empty-value (get @model.config field))}}
@label={{this.label field}}
@value={{get @model.config field}}
@formatTtl={{this.isDuration field}}
/>
{{/if}}
{{/each}}
{{else if engineDisplayData.isConfigurable}}
{{! Prompt user to configure the secret engine }}
<EmptyState
@title="{{get (engines-display-data @model.secretsEngine.type) 'displayName'}} not configured"
@message="Get started by configuring your {{get
(engines-display-data @model.secretsEngine.type)
'displayName'
}} secrets engine."
@title="{{engineDisplayData.displayName}} not configured"
@message="Get started by configuring your {{engineDisplayData.displayName}} secrets engine."
>
<Hds::Link::Standalone
@icon="chevron-right"
@iconPosition="trailing"
@text="Configure {{get (engines-display-data @model.secretsEngine.type) 'displayName'}}"
@text="Configure {{engineDisplayData.displayName}}"
@route="vault.cluster.secrets.backend.configuration.edit"
@model={{@id}}
@model={{@model.secretsEngine.id}}
/>
</EmptyState>
{{else}}
<EmptyState
data-test-no-config
@title="No configuration details available"
@message="{{get
(engines-display-data @model.secretsEngine.type)
'displayName'
}} does not have any plugin specific configuration. All configurable parameters for this engine are under 'General Settings'."
@message="{{engineDisplayData.displayName}} does not have any plugin specific configuration. All configurable parameters for this engine are under 'General Settings'."
>
<Hds::Link::Standalone
@icon="chevron-right"
@iconPosition="trailing"
@text="Back to general settings"
@route="vault.cluster.secrets.backend.configuration.general-settings"
@model={{@id}}
@model={{@model.secretsEngine.id}}
/>
</EmptyState>
{{/if}}
{{/if}}
{{/let}}

View file

@ -21,15 +21,23 @@ export default class SecretsBackendConfigurationRoute extends Route {
const { type, id } = secretsEngine;
return {
secretsEngine,
config: await this.fetchConfig(type, id), // fetch config for configurable engines (aws, azure, gcp, ssh)
// fetch config for configurable secrets engines that use the "general" route pattern: aws, azure, gcp, ssh
// ember-engines manage their own engine config routes and requests so do not fetch here.
config: await this.fetchConfig(type, id),
};
}
// TODO after update to show separated general settings vs plugin settings redirect if not configured?
// afterModel(resolvedModel) {
// // Redirect to edit route if not configured
// if (!resolvedModel.config) {
// this.router.transitionTo('vault.cluster.secrets.backend.configuration.edit');
// }
// }
fetchConfig(type, id) {
// id is the path where the backend is mounted since there's only one config per engine (often this path is referred to just as backend)
switch (type) {
case 'ldap':
return this.fetchLdapConfigs(id);
case 'aws':
return this.fetchAwsConfigs(id);
case 'azure':
@ -41,18 +49,6 @@ export default class SecretsBackendConfigurationRoute extends Route {
}
}
async fetchLdapConfigs(path) {
try {
const { data } = await this.api.secrets.ldapReadConfiguration(path);
return data;
} catch (e) {
const error = await this.parseApiError(e);
if (error.httpStatus === 404) {
return {};
}
}
}
async fetchAwsConfigs(path) {
// AWS has two configuration endpoints root and lease, as well as a separate endpoint for the issuer.
const handleError = async (e) => {

View file

@ -20,12 +20,4 @@ export default class SecretsBackendConfigurationIndexRoute extends Route {
return this.router.replaceWith('vault.cluster.secrets.backend.configuration.general-settings');
}
}
setupController(controller, resolvedModel) {
super.setupController(controller, resolvedModel);
const engine = engineDisplayData(resolvedModel.secretsEngine.type);
controller.typeDisplay = engine.displayName;
controller.isConfigurable = engine.isConfigurable ?? false;
controller.modelId = resolvedModel.secretsEngine.id;
}
}

View file

@ -26,7 +26,6 @@
@title={{concat this.model.secretsEngine.id " configuration"}}
@description={{engineDisplayData.displayName}}
@icon={{engineDisplayData.glyph}}
@subtitle={{engineDisplayData.typeDisplay}}
>
<:breadcrumbs>
<Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} />
@ -40,10 +39,9 @@
/>
</:actions>
<:tabs>
<SecretEngine::ConfigureTabs
<Mount::ConfigureTabs
@configRoute="configuration.edit"
@engineMetadata={{engineDisplayData}}
@isConfigured={{not (is-empty-value this.model.config)}}
@displayName={{engineDisplayData.displayName}}
@path={{this.model.secretsEngine.id}}
/>
</:tabs>

View file

@ -5,36 +5,41 @@
<SecretListHeader @model={{this.model.secretsEngine}} @isConfigure={{true}} />
{{#if this.isConfigurable}}
<Toolbar>
<ToolbarActions>
<ToolbarLink
@route="vault.cluster.secrets.backend.configuration.edit"
@model={{this.model.secretsEngine.id}}
data-test-secret-backend-configure
>
Configure
</ToolbarLink>
</ToolbarActions>
</Toolbar>
{{#let (engines-display-data @model.secretsEngine.type) as |engineMetadata|}}
{{#if engineMetadata.isConfigurable}}
<Toolbar>
<ToolbarActions>
<ToolbarLink
@route="vault.cluster.secrets.backend.configuration.edit"
@model={{this.model.secretsEngine.id}}
data-test-secret-backend-configure
>
Configure
</ToolbarLink>
</ToolbarActions>
</Toolbar>
<SecretEngine::ConfigurationDetails @config={{this.model.config}} @typeDisplay={{this.typeDisplay}} @id={{this.modelId}} />
<SecretEngine::ConfigurationDetails
@config={{this.model.config}}
@typeDisplay={{engineMetadata.displayName}}
@id={{this.model.secretsEngine.id}}
/>
<SecretsEngineMountConfig
@secretsEngine={{this.model.secretsEngine}}
class="has-top-margin-xl has-bottom-margin-xl"
data-test-mount-config
/>
{{else}}
<div class="box is-fullwidth is-sideless is-paddingless is-marginless">
{{#each this.displayFields as |field|}}
<InfoTableRow
@alwaysRender={{and (not (is-empty-value (get this.model.secretsEngine field))) (not-eq field "version")}}
@formatTtl={{includes field (array "config.default_lease_ttl" "config.max_lease_ttl")}}
@label={{this.label field}}
@value={{get this.model.secretsEngine field}}
/>
{{/each}}
</div>
{{/if}}
<SecretsEngineMountConfig
@secretsEngine={{this.model.secretsEngine}}
class="has-top-margin-xl has-bottom-margin-xl"
data-test-mount-config
/>
{{else}}
<div class="box is-fullwidth is-sideless is-paddingless is-marginless">
{{#each this.displayFields as |field|}}
<InfoTableRow
@alwaysRender={{and (not (is-empty-value (get this.model.secretsEngine field))) (not-eq field "version")}}
@formatTtl={{includes field (array "config.default_lease_ttl" "config.max_lease_ttl")}}
@label={{this.label field}}
@value={{get this.model.secretsEngine field}}
/>
{{/each}}
</div>
{{/if}}
{{/let}}

View file

@ -35,8 +35,7 @@ export interface EngineDisplayData {
isOldEngine?: boolean; // flag for engine views, if set to true, the engine will show pre-existing page design, if not, then the new views will be used. This is temporary until all engines have been migrated to the new design.
type: string;
value?: string;
configReadRoute?: string; // override for custom route if not "configuration.plugin-settings" (used for Ember engines)
configEditRoute?: string; // override for custom route if not "configuration.edit" (used for Ember engines)
configRoute?: string; // override for custom route if not "configuration.plugin-settings" (used for Ember engines)
}
/**
@ -215,8 +214,7 @@ export const ALL_ENGINES: EngineDisplayData[] = [
displayName: 'LDAP',
isConfigurable: true,
engineRoute: 'ldap.overview',
configEditRoute: 'ldap.configure',
configReadRoute: 'ldap.configuration',
configRoute: 'ldap.configuration',
glyph: 'folder-users',
mountCategory: ['auth', 'secret'],
type: 'ldap',

View file

@ -0,0 +1,35 @@
{{!
Copyright IBM Corp. 2016, 2025
SPDX-License-Identifier: BUSL-1.1
}}
<nav class="tabs" aria-label={{@displayName}}>
<ul>
<li data-test-tab="general-settings">
{{! General settings are the mount details for a secrets engine - all engines have these }}
{{#if @externalRoute}}
<LinkToExternal @route={{@externalRoute}} @model={{@path}}>
General settings
</LinkToExternal>
{{else}}
<LinkTo @route="vault.cluster.secrets.backend.configuration.general-settings" @model={{@path}}>
General settings
</LinkTo>
{{/if}}
</li>
{{! @configRoute is supplied if an engine is configurable - only some engines support additional configuration }}
{{#if @configRoute}}
<li data-test-tab="plugin-settings">
{{! if @externalRoute is provided this component is rendering in an ember engine and just @configRoute is sufficient
otherwise we need to provide the full route by concatenating the prefix. }}
<LinkTo
@route={{if @externalRoute @configRoute (concat "vault.cluster.secrets.backend." @configRoute)}}
@model={{@path}}
>
{{or @displayName "Plugin"}}
settings
</LinkTo>
</li>
{{/if}}
</ul>
</nav>

View file

@ -0,0 +1,6 @@
/**
* Copyright IBM Corp. 2016, 2025
* SPDX-License-Identifier: BUSL-1.1
*/
export { default } from 'core/components/mount/configure-tabs';

View file

@ -33,28 +33,22 @@
{{/if}}
</:actions>
<:tabs>
<div class="tabs-container box is-marginless is-fullwidth is-paddingless">
{{#if @configRoute}}
<Mount::ConfigureTabs
@configRoute={{@configRoute}}
@displayName="LDAP"
@path={{@model.id}}
@externalRoute="secretsGeneralSettingsConfiguration"
/>
{{else}}
<nav class="tabs" aria-label="ldap tabs">
<ul>
{{#if @configRoute}}
<li data-test-tab="general-settings">
<LinkToExternal @route="secretsGeneralSettingsConfiguration" @model={{@model.id}}>
General settings
</LinkToExternal>
</li>
<li data-test-tab="plugin-settings">
<LinkTo @route={{@configRoute}} @model={{@model.id}}>
LDAP settings
</LinkTo>
</li>
{{else}}
<li><LinkTo @route="overview" data-test-tab="overview">Overview</LinkTo></li>
<li><LinkTo @route="roles" data-test-tab="roles">Roles</LinkTo></li>
<li><LinkTo @route="libraries" data-test-tab="libraries">Libraries</LinkTo></li>
{{/if}}
<li><LinkTo @route="overview" data-test-tab="overview">Overview</LinkTo></li>
<li><LinkTo @route="roles" data-test-tab="roles">Roles</LinkTo></li>
<li><LinkTo @route="libraries" data-test-tab="libraries">Libraries</LinkTo></li>
</ul>
</nav>
</div>
{{/if}}
</:tabs>
</Page::Header>

View file

@ -31,6 +31,7 @@ module('Acceptance | ldap | overview', function (hooks) {
`write ${backend}/config binddn=foo bindpass=bar url=http://localhost:8208`,
]);
};
this.expectedConfigEditRoute = 'ldap.configure';
return login();
});

View file

@ -11,15 +11,14 @@ import { GENERAL } from 'vault/tests/helpers/general-selectors';
import { SECRET_ENGINE_SELECTORS as SES } from 'vault/tests/helpers/secret-engine/secret-engine-selectors';
// To use this helper for configurable engines
// define `this.mountAndConfig` in the beforeEach hook
// define `this.mountAndConfig` and this.expectedConfigEditRoute in the beforeEach hook
// (see "Acceptance | ldap | overview" as an example)
const BASE_ROUTE = 'vault.cluster.secrets.backend';
export default (test, type) => {
const {
isConfigurable = false,
configReadRoute = 'configuration.plugin-settings',
configEditRoute = 'configuration.edit',
configRoute = 'configuration.plugin-settings',
engineRoute = 'list-root',
} = engineDisplayData(type);
@ -35,7 +34,7 @@ export default (test, type) => {
await click(GENERAL.menuItem('View configuration'));
assert.strictEqual(
currentRouteName(),
`${BASE_ROUTE}.${configEditRoute}`,
`${BASE_ROUTE}.${this.expectedConfigEditRoute}`,
'it navigates to the configure route from the list view'
);
@ -57,7 +56,7 @@ export default (test, type) => {
await click(GENERAL.tabLink('plugin-settings'));
assert.strictEqual(
currentRouteName(),
`${BASE_ROUTE}.${configEditRoute}`,
`${BASE_ROUTE}.${this.expectedConfigEditRoute}`,
'clicking plugin settings navigates to edit route when not configured'
);
assert
@ -79,7 +78,7 @@ export default (test, type) => {
await click(GENERAL.menuItem('View configuration'));
assert.strictEqual(
currentRouteName(),
`${BASE_ROUTE}.${configReadRoute}`,
`${BASE_ROUTE}.${configRoute}`,
'it navigates to the configure route from the list view'
);
@ -102,7 +101,7 @@ export default (test, type) => {
// Confirm tabs after clicking plugin-settings
assert.strictEqual(
currentRouteName(),
`${BASE_ROUTE}.${configReadRoute}`,
`${BASE_ROUTE}.${configRoute}`,
'it navigates to the read route when configured'
);
assert
@ -114,7 +113,7 @@ export default (test, type) => {
await click(SES.configure);
assert.strictEqual(
currentRouteName(),
`${BASE_ROUTE}.${configEditRoute}`,
`${BASE_ROUTE}.${this.expectedConfigEditRoute}`,
'it navigates to the edit route'
);
assert
@ -135,7 +134,7 @@ export default (test, type) => {
await click(GENERAL.tabLink('plugin-settings'));
assert.strictEqual(
currentRouteName(),
`${BASE_ROUTE}.${configReadRoute}`,
`${BASE_ROUTE}.${configRoute}`,
'it navigates to the read route when configured'
);
await click(GENERAL.button('Exit configuration'));

View file

@ -0,0 +1,58 @@
/**
* Copyright IBM Corp. 2016, 2025
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { click, render } from '@ember/test-helpers';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | mount/configure-tabs', function (hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(function () {
this.configRoute = 'pki.configuration.create';
// This component also accepts @path but that's route related and irrelevant to an integration test
this.renderComponent = () => {
return render(
hbs`<Mount::ConfigureTabs
@displayName={{this.displayName}}
@configRoute={{this.configRoute}}
@path="my-pki-engine"
/>`
);
};
});
test('it renders when args are undefined', async function (assert) {
this.configRoute = undefined;
this.displayName = undefined;
await this.renderComponent();
assert.dom(GENERAL.tab('general-settings')).exists().hasText('General settings');
});
test('it renders plugin settings tab when @configRoute provided', async function (assert) {
this.displayName = 'PKI';
await this.renderComponent();
assert.dom(GENERAL.tab('general-settings')).exists().hasText('General settings');
await click(GENERAL.tab('plugin-settings'));
assert.dom(GENERAL.tab('plugin-settings')).exists().hasText('PKI settings');
});
test('it renders fallback when @displayName not provided', async function (assert) {
this.displayName = '';
await this.renderComponent();
assert.dom(GENERAL.tab('plugin-settings')).exists().hasText('Plugin settings');
});
test('it hides plugin settings when there is no @configRoute', async function (assert) {
this.configRoute = '';
await this.renderComponent();
assert.dom(GENERAL.tab('general-settings')).exists().hasText('General settings');
assert.dom(GENERAL.tab('plugin-settings')).doesNotExist();
});
});

View file

@ -1,72 +0,0 @@
/**
* Copyright IBM Corp. 2016, 2025
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { filterEnginesByMountCategory } from 'vault/utils/all-engines-metadata';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import hbs from 'htmlbars-inline-precompile';
import engineDisplayData from 'vault/helpers/engines-display-data';
// The `configurable` array is hardcoded to validate that ALL_ENGINES metadata is correctly
// defined to render the tabs correctly.
const configurable = ['aws', 'azure', 'gcp', 'ldap', 'ssh'];
module('Integration | Component | secret-engines/configure-tabs', function (hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(function () {
this.isConfigured = undefined;
// This component accepts more args, but they are route related and instead asserted by acceptance tests
this.renderComponent = (type) => {
this.engineMetadata = type ? engineDisplayData(type) : undefined;
return render(
hbs`<SecretEngine::ConfigureTabs
@engineMetadata={{this.engineMetadata}}
@isConfigured={{this.isConfigured}}
/>`
);
};
});
test('it renders when args are undefined', async function (assert) {
await this.renderComponent();
assert.dom(GENERAL.tab('general-settings')).exists().hasText('General settings');
});
for (const { type } of filterEnginesByMountCategory({ mountCategory: 'secret' })) {
if (configurable.includes(type)) {
test(`${type} (configurable): it renders expected tabs when not configured`, async function (assert) {
await this.renderComponent(type);
assert.dom(GENERAL.tab('general-settings')).exists().hasText('General settings');
assert
.dom(GENERAL.tab('plugin-settings'))
.exists()
.hasText(`${this.engineMetadata.displayName} settings`);
});
test(`${type} (configurable): it renders expected tabs when configured`, async function (assert) {
this.isConfigured = true;
await this.renderComponent(type);
assert.dom(GENERAL.tab('general-settings')).exists().hasText('General settings');
assert
.dom(GENERAL.tab('plugin-settings'))
.exists()
.hasText(`${this.engineMetadata.displayName} settings`);
});
} else {
// NON-CONFIGURABLE ENGINES
test(`${type} it hides plugin settings when not configurable`, async function (assert) {
await this.renderComponent(type);
assert.dom(GENERAL.tab('general-settings')).exists().hasText('General settings');
assert.dom(GENERAL.tab('plugin-settings')).doesNotExist();
});
}
}
});