From b54aba9fd84241871b1314e3d3c5a221cdee2ef8 Mon Sep 17 00:00:00 2001 From: "Shannon Roberts (Beagin)" Date: Wed, 11 Jun 2025 10:59:16 -0700 Subject: [PATCH] [VAULT-35715] UI: Namespace picker is updated after add/delete namespace (#30737) * [VAULT-35715] UI: Namespace picker is updated after add/delete namespace * + changelog * pairing session updates * fixing tests (wip) * fix tests! * remove meep lol * fix one more test * add missing test coverage * address PR comments, enterprise tests are passing again!! * fix lint issue --- changelog/30737.txt | 3 + ui/app/components/namespace-picker.hbs | 10 +- ui/app/components/namespace-picker.ts | 39 ++- .../addon/components/page/credentials.hbs | 2 +- ui/mirage/handlers/base.js | 8 - .../access/namespaces/index-test.js | 300 ++++++++++++++---- .../acceptance/enterprise-namespaces-test.js | 246 +++++++------- .../acceptance/enterprise-replication-test.js | 3 +- .../backend/kubernetes/credentials-test.js | 5 +- ui/tests/helpers/commands.js | 162 ++++++---- ui/tests/helpers/namespace-picker.js | 1 - .../integration/components/form-field-test.js | 2 +- .../kubernetes/page/credentials-test.js | 5 +- .../components/namespace-picker-test.js | 12 +- .../components/sidebar/frame-test.js | 4 +- 15 files changed, 529 insertions(+), 273 deletions(-) create mode 100644 changelog/30737.txt diff --git a/changelog/30737.txt b/changelog/30737.txt new file mode 100644 index 0000000000..2ee6cc9322 --- /dev/null +++ b/changelog/30737.txt @@ -0,0 +1,3 @@ +```release-note:bug +ui: Automatically refresh namespace list inside the namespace picker after creating or deleting a namespace in the UI. +``` diff --git a/ui/app/components/namespace-picker.hbs b/ui/app/components/namespace-picker.hbs index 506a8c15f9..33721c29f4 100644 --- a/ui/app/components/namespace-picker.hbs +++ b/ui/app/components/namespace-picker.hbs @@ -6,7 +6,13 @@
- + {{#if this.errorLoadingNamespaces}} @@ -52,7 +58,7 @@
{{#each this.visibleNamespaceOptions as |option|}} diff --git a/ui/app/components/namespace-picker.ts b/ui/app/components/namespace-picker.ts index 20a05a3e89..4e0a79e02f 100644 --- a/ui/app/components/namespace-picker.ts +++ b/ui/app/components/namespace-picker.ts @@ -39,7 +39,6 @@ export default class NamespacePicker extends Component { // Load 200 namespaces in the namespace picker at a time @tracked batchSize = 200; - @tracked allNamespaces: NamespaceOption[] = []; @tracked canManageNamespaces = false; // Show/hide manage namespaces button @tracked canRefreshNamespaces = false; // Show/hide refresh list button @tracked errorLoadingNamespaces = ''; @@ -47,13 +46,24 @@ export default class NamespacePicker extends Component { @tracked searchInput = ''; @tracked searchInputHelpText = "Enter a full path in the search bar and hit the 'Enter' ↵ key to navigate faster."; - @tracked selected: NamespaceOption | null = null; constructor(owner: unknown, args: Record) { super(owner, args); this.loadOptions(); } + get allNamespaces(): NamespaceOption[] { + return this.getOptions( + this.namespace?.accessibleNamespaces, + this.namespace?.currentNamespace, + this.namespace?.path + ); + } + + get selectedNamespace(): NamespaceOption | null { + return this.getSelected(this.allNamespaces, this.namespace?.path) ?? null; + } + private matchesPath(option: NamespaceOption, currentPath: string): boolean { return option?.path === currentPath; } @@ -62,7 +72,11 @@ export default class NamespacePicker extends Component { return options.find((option) => this.matchesPath(option, currentPath)); } - private getOptions(namespace: NamespaceService): NamespaceOption[] { + private getOptions( + accessibleNamespaces: string[], + currentNamespace: string, + path: string + ): NamespaceOption[] { /* Each namespace option has 3 properties: { id, path, and label } * - id: node / namespace name (displayed when the namespace picker is closed) * - path: full namespace path (used to navigate to the namespace) @@ -76,7 +90,7 @@ export default class NamespacePicker extends Component { * | 'child' | 'parent/child' | 'parent/child' | */ const options = [ - ...(namespace?.accessibleNamespaces || []).map((ns: string) => { + ...(accessibleNamespaces || []).map((ns: string) => { const parts = ns.split('/'); return { id: parts[parts.length - 1] || '', path: ns, label: ns }; }), @@ -91,9 +105,9 @@ export default class NamespacePicker extends Component { // to the list of options. This is a fallback for when the user has access to a single namespace. if (options.length === 0) { options.push({ - id: namespace.currentNamespace, - path: namespace.path, - label: namespace.path, + id: currentNamespace, + path: path, + label: path, }); } @@ -187,9 +201,6 @@ export default class NamespacePicker extends Component { this.errorLoadingNamespaces = errorMessage(error); } - this.allNamespaces = this.getOptions(this.namespace); - this.selected = this.getSelected(this.allNamespaces, this.namespace?.path) ?? null; - await this.fetchListCapability(); } @@ -216,7 +227,6 @@ export default class NamespacePicker extends Component { @action async onChange(selected: NamespaceOption): Promise { - this.selected = selected; this.searchInput = ''; this.router.transitionTo('vault.cluster.dashboard', { queryParams: { namespace: selected.path } }); } @@ -227,7 +237,6 @@ export default class NamespacePicker extends Component { const matchingNamespace = this.allNamespaces.find((ns) => ns.label === this.searchInput.trim()); if (matchingNamespace) { - this.selected = matchingNamespace; this.searchInput = ''; this.router.transitionTo('vault.cluster.dashboard', { queryParams: { namespace: matchingNamespace.path }, @@ -247,4 +256,10 @@ export default class NamespacePicker extends Component { this.searchInput = ''; await this.loadOptions(); } + + @action + toggleNamespacePicker() { + // Reset the search input when the dropdown is toggled + this.searchInput = ''; + } } diff --git a/ui/lib/kubernetes/addon/components/page/credentials.hbs b/ui/lib/kubernetes/addon/components/page/credentials.hbs index 498d2b6628..55b1107193 100644 --- a/ui/lib/kubernetes/addon/components/page/credentials.hbs +++ b/ui/lib/kubernetes/addon/components/page/credentials.hbs @@ -75,7 +75,7 @@

ClusterRoleBinding

diff --git a/ui/mirage/handlers/base.js b/ui/mirage/handlers/base.js index 99e5eea7db..f057dea4bb 100644 --- a/ui/mirage/handlers/base.js +++ b/ui/mirage/handlers/base.js @@ -94,12 +94,4 @@ export default function (server) { }, }; }); - - server.get('sys/internal/ui/namespaces', function () { - return { - data: { - keys: ['ns1/', 'ns2/', 'ns3/', 'ns4/', 'ns5/', 'ns6/', 'ns7/', 'ns8/', 'ns9/', 'ns10/'], - }, - }; - }); } diff --git a/ui/tests/acceptance/access/namespaces/index-test.js b/ui/tests/acceptance/access/namespaces/index-test.js index 854cb6f00a..a02a51b59f 100644 --- a/ui/tests/acceptance/access/namespaces/index-test.js +++ b/ui/tests/acceptance/access/namespaces/index-test.js @@ -3,27 +3,27 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { currentRouteName, visit, click, fillIn, currentURL } from '@ember/test-helpers'; +import { currentRouteName, visit, click, fillIn, currentURL, findAll } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; -import { setupMirage } from 'ember-cli-mirage/test-support'; import { login } from 'vault/tests/helpers/auth/auth-helpers'; import { GENERAL } from 'vault/tests/helpers/general-selectors'; +import { createNSFromPaths, deleteNSFromPaths } from 'vault/tests/helpers/commands'; +import { NAMESPACE_PICKER_SELECTORS } from 'vault/tests/helpers/namespace-picker'; module('Acceptance | Enterprise | /access/namespaces', function (hooks) { setupApplicationTest(hooks); - setupMirage(hooks); - const searchInput = GENERAL.filterInputExplicit; - const searchButton = GENERAL.filterInputExplicitSearch; - - hooks.beforeEach(function () { - return login(); + hooks.beforeEach(async () => { + await login(); }); test('it navigates to namespaces page', async function (assert) { assert.expect(1); + + // Go to the manage namespaces page await visit('/vault/access/namespaces'); + assert.strictEqual( currentRouteName(), 'vault.cluster.access.namespaces.index', @@ -32,52 +32,159 @@ module('Acceptance | Enterprise | /access/namespaces', function (hooks) { }); test('it displays the breadcrumb trail', async function (assert) { + // Go to the manage namespaces page await visit('/vault/access/namespaces'); + assert.dom(GENERAL.breadcrumb).exists({ count: 1 }, 'Only one breadcrumb is displayed'); assert.dom(GENERAL.breadcrumb).hasText('Namespaces', 'Breadcrumb trail is displayed correctly'); }); test('it should render correct number of namespaces', async function (assert) { + // Setup: Create namespace(s) via the CLI + const namespaces = [ + 'ns1', + 'ns2', + 'ns3', + 'ns4', + 'ns5', + 'ns6', + 'ns7', + 'ns8', + 'ns9', + 'ns10', + 'ns11', + 'ns12', + 'ns13', + 'ns14', + 'ns15', + 'ns16', + 'ns17', + 'ns18', + ]; + await createNSFromPaths(namespaces); + assert.expect(3); + + // Go to the manage namespaces page await visit('/vault/access/namespaces'); + const store = this.owner.lookup('service:store'); + // Default page size is 15 assert.strictEqual(store.peekAll('namespace').length, 15, 'Store has 15 namespaces records'); assert.dom('.list-item-row').exists({ count: 15 }, 'Should display 15 namespaces'); assert.dom('.hds-pagination').exists(); + + // Cleanup: Delete namespace(s) via the CLI + await deleteNSFromPaths(namespaces); }); test('it should show button to refresh namespace list', async function (assert) { - let refreshNetworkRequestTriggered; - const refreshNamespaceButton = GENERAL.button('refresh-namespace-list'); + const testNS = 'test-refresh-ns'; - this.server.get('/sys/internal/ui/namespaces', () => { - refreshNetworkRequestTriggered = true; - return; - }); + // Setup: Create namespace via the CLI + const namespaces = [testNS]; + await createNSFromPaths(namespaces); + // Go to the manage namespaces page await visit('/vault/access/namespaces'); - assert.dom(refreshNamespaceButton).hasText('Refresh list', 'Refresh button is rendered correctly'); + // Open the namespace picker + await click(GENERAL.toggleInput('namespace-id')); - refreshNetworkRequestTriggered = false; - await click(refreshNamespaceButton); - assert.true( - refreshNetworkRequestTriggered, - 'Get namespaces network request was made when refresh button was clicked' + // Verify the search input field exists + assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists'); + + // Verify 0 namespaces are displayed after searching for "test-refresh-ns" + await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, testNS); + assert.strictEqual( + findAll(NAMESPACE_PICKER_SELECTORS.link()).length, + 0, + `No namespaces are displayed after searching for "${testNS}"` ); + + // Close the namespace picker + await click(GENERAL.toggleInput('namespace-id')); + + // Click the refresh list button + assert + .dom(GENERAL.button('refresh-namespace-list')) + .hasText('Refresh list', 'Refresh button is rendered correctly'); + await click(GENERAL.button('refresh-namespace-list')); + + // Open the namespace picker + await click(GENERAL.toggleInput('namespace-id')); + + // Verify the search input field exists + assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists'); + + // Verify 1 namespace is displayed after searching for "test-refresh-ns" + await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, testNS); + assert.strictEqual( + findAll(NAMESPACE_PICKER_SELECTORS.link()).length, + 1, + `1 namespace is displayed after searching for "${testNS}"` + ); + + // Close the namespace picker + await click(GENERAL.toggleInput('namespace-id')); + + // Cleanup: Delete namespace via the CLI + await deleteNSFromPaths(namespaces); + + // Go to the manage namespaces page + await visit('/vault/access/namespaces'); + + // Open the namespace picker + await click(GENERAL.toggleInput('namespace-id')); + + // Verify the search input field exists + assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists'); + + // Verify 1 namespace is displayed after searching for "test-refresh-ns" + await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, testNS); + assert.strictEqual( + findAll(NAMESPACE_PICKER_SELECTORS.link()).length, + 1, + `1 namespace is displayed after searching for "${testNS}"` + ); + + // Close the namespace picker + await click(GENERAL.toggleInput('namespace-id')); + + // Click the refresh list button + assert + .dom(GENERAL.button('refresh-namespace-list')) + .hasText('Refresh list', 'Refresh button is rendered correctly'); + await click(GENERAL.button('refresh-namespace-list')); + + // Open the namespace picker + await click(GENERAL.toggleInput('namespace-id')); + + // Verify the search input field exists + assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists'); + + // Verify 0 namespaces are displayed after searching for "test-refresh-ns" + await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, testNS); + assert.strictEqual( + findAll(NAMESPACE_PICKER_SELECTORS.link()).length, + 0, + `No namespaces are displayed after searching for "${testNS}"` + ); + + // Close the namespace picker + await click(GENERAL.toggleInput('namespace-id')); }); test('it should show button to create new namespace', async function (assert) { - const createNamespaceLink = GENERAL.linkTo('create-namespace'); - + // Go to the manage namespaces page await visit('/vault/access/namespaces'); assert - .dom(createNamespaceLink) + .dom(GENERAL.linkTo('create-namespace')) .hasText('Create namespace', 'Create namespace button is rendered correctly'); assert - .dom(createNamespaceLink) + .dom(GENERAL.linkTo('create-namespace')) .hasAttribute( 'href', '/ui/vault/access/namespaces/create', @@ -85,44 +192,143 @@ module('Acceptance | Enterprise | /access/namespaces', function (hooks) { ); }); + test('it should update namespace list after create/delete without manual refresh', async function (assert) { + const testNS = 'test-create-ns'; + + // Go to the manage namespaces page + await visit('/vault/access/namespaces'); + + // Verify test-create-ns does not exist in the Manage Namespace page + await fillIn(GENERAL.filterInputExplicit, testNS); + await click(GENERAL.filterInputExplicitSearch); + assert.dom('.list-item-row').exists({ count: 0 }, `"${testNS}" namespace is not displayed on the page`); + + // Verify test-create-ns does not exist in the Namespace Picker + await click(GENERAL.toggleInput('namespace-id')); + await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, testNS); + assert.strictEqual( + findAll(NAMESPACE_PICKER_SELECTORS.link()).length, + 0, + `"${testNS}" is not displayed in the namespace picker` + ); + await click(GENERAL.toggleInput('namespace-id')); + + // Create a new namespace + assert + .dom(GENERAL.linkTo('create-namespace')) + .hasText('Create namespace', 'Create namespace button is displayed'); + await click(GENERAL.linkTo('create-namespace')); + assert.dom(GENERAL.inputByAttr('path')).exists('Create namespace input field is displayed'); + await fillIn(GENERAL.inputByAttr('path'), testNS); + assert.dom('[data-test-edit-form-submit]').exists('Save button is displayed'); + await click('[data-test-edit-form-submit]'); + + // Verify test-create-ns does not exist in the Manage Namespace page + await fillIn(GENERAL.filterInputExplicit, testNS); + await click(GENERAL.filterInputExplicitSearch); + assert.dom('.list-item-row').exists({ count: 1 }, `"${testNS}" namespace is displayed on the page`); + + // Verify test-create-ns exists in the Namespace Picker without refresh + await click(GENERAL.toggleInput('namespace-id')); + await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, testNS); + assert.strictEqual( + findAll(NAMESPACE_PICKER_SELECTORS.link()).length, + 1, + `"${testNS}" is displayed in the namespace picker` + ); + await click(GENERAL.toggleInput('namespace-id')); + + // Delete the created namespace + assert.dom(GENERAL.menuTrigger).exists('Namespace options menu is displayed'); + await click(GENERAL.menuTrigger); + assert + .dom('.hds-dropdown-list-item:nth-of-type(2)') + .hasText('Delete', 'Delete namespace option is displayed'); + await click('.hds-dropdown-list-item:nth-of-type(2) button'); + assert.dom(GENERAL.confirmButton).hasText('Confirm', 'Confirm namespace deletion button is shown'); + await click(GENERAL.confirmButton); + + // Verify test-create-ns does not exist in the Manage Namespace page + await fillIn(GENERAL.filterInputExplicit, testNS); + await click(GENERAL.filterInputExplicitSearch); + assert.dom('.list-item-row').exists({ count: 0 }, `"${testNS}" namespace is not displayed on the page`); + + // Verify test-create-ns does not exist in the Namespace Picker + await click(GENERAL.toggleInput('namespace-id')); + await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, testNS); + assert.strictEqual( + findAll(NAMESPACE_PICKER_SELECTORS.link()).length, + 0, + `"${testNS}" is not displayed in the namespace picker` + ); + await click(GENERAL.toggleInput('namespace-id')); + }); + test('it should filter namespaces based on search input', async function (assert) { + // Setup: Create namespace(s) via the CLI + const namespaces = ['parent', 'other-parent']; + await createNSFromPaths(namespaces); + + // Go to the manage namespaces page await visit('/vault/access/namespaces'); // Enter search text - await fillIn(searchInput, 'ns4'); - assert.dom(searchInput).hasValue('ns4', 'Search input contains the entered text'); + await fillIn(GENERAL.filterInputExplicit, 'other'); + assert.dom(GENERAL.filterInputExplicit).hasValue('other', 'Search input contains the entered text'); // Click the search button - await click(searchButton); + await click(GENERAL.filterInputExplicitSearch); // Verify the filtered results assert.dom('.list-item-row').exists({ count: 1 }, 'Filtered results are displayed correctly'); - assert.dom('.list-item-row').hasText('ns4', 'Correct namespace is displayed in the filtered results'); + assert + .dom('.list-item-row') + .hasText('other-parent', 'Correct namespace is displayed in the filtered results'); // Verify the URL query param is updated assert.strictEqual( currentURL(), - '/vault/access/namespaces?page=1&pageFilter=ns4', + '/vault/access/namespaces?page=1&pageFilter=other', 'URL query param is updated to reflect the search field as pageFilter' ); // Clear the search input - await fillIn(searchInput, ''); - await click(searchButton); - assert.dom(searchInput).hasValue('', 'Search input is cleared'); + await fillIn(GENERAL.filterInputExplicit, ''); + await click(GENERAL.filterInputExplicitSearch); + + assert.dom(GENERAL.filterInputExplicit).hasValue('', 'Search input is cleared'); assert .dom('.list-item-row') - .exists({ count: 15 }, 'All namespaces are displayed after clearing the search input'); + .exists({ count: 2 }, 'All namespaces are displayed after clearing the search input'); assert.strictEqual( currentURL(), '/vault/access/namespaces?page=1', 'URL query param is updated to remove pageFilter' ); + + // Cleanup: Delete namespace(s) via the CLI + await deleteNSFromPaths(namespaces); }); test('it should show options menu for each namespace', async function (assert) { + // Setup: Create namespace(s) via the CLI + const namespace = 'asdf'; + await createNSFromPaths([namespace]); + + // Go to the manage namespaces page await visit('/vault/access/namespaces'); - assert.dom(GENERAL.menuTrigger).exists(); + + // Hack: Trigger refresh internal namespaces endpoint + await click(GENERAL.toggleInput('namespace-id')); + await click(GENERAL.button('Refresh list')); + + // Enter search text + await fillIn(GENERAL.filterInputExplicit, namespace); + await click(GENERAL.filterInputExplicitSearch); + + await click(GENERAL.button('refresh-namespace-list')); + + assert.dom(GENERAL.menuTrigger).exists('Namespace options menu is displayed'); await click(GENERAL.menuTrigger); assert.dom('.hds-dropdown-list-item').exists({ count: 2 }, 'Should display 2 options in the menu.'); @@ -135,30 +341,16 @@ module('Acceptance | Enterprise | /access/namespaces', function (hooks) { .dom(`${switchNamespaceButton} a`) .hasAttribute( 'href', - 'http://localhost:7357/ui/vault/dashboard?namespace=ns1', + `http://localhost:7357/ui/vault/dashboard?namespace=${namespace}`, 'Switch namespace button has the correct href attribute' ); // Verify that the user can delete the namespace - const deleteNamespaceButton = '.hds-dropdown-list-item:nth-of-type(2)'; - assert.dom(deleteNamespaceButton).hasText('Delete', 'Allow users to delete the namespace'); - }); + assert + .dom('.hds-dropdown-list-item:nth-of-type(2)') + .hasText('Delete', 'Delete namespace option is displayed'); - test('it should hide the switch to namespace option for unaccessible namespaces', async function (assert) { - await visit('/vault/access/namespaces'); - - // Search for a namespace that is not accessible - await fillIn(searchInput, 'ns12'); - await click(searchButton); - - assert.dom(GENERAL.menuTrigger).exists(); - await click(GENERAL.menuTrigger); - - // Verify that only the delete option is available for the unaccessible namespace - assert.dom('.hds-dropdown-list-item').exists({ count: 1 }, 'Should display 1 option in the menu.'); - - // Verify that the user can delete the namespace - const deleteNamespaceButton = '.hds-dropdown-list-item:nth-of-type(1)'; - assert.dom(deleteNamespaceButton).hasText('Delete', 'Allow users to delete the namespace'); + // Cleanup: Delete namespace(s) via the CLI + await deleteNSFromPaths([namespace]); }); }); diff --git a/ui/tests/acceptance/enterprise-namespaces-test.js b/ui/tests/acceptance/enterprise-namespaces-test.js index 034255c939..e5194559ff 100644 --- a/ui/tests/acceptance/enterprise-namespaces-test.js +++ b/ui/tests/acceptance/enterprise-namespaces-test.js @@ -15,67 +15,21 @@ import { } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; -import { runCmd, createNS, deleteNS } from 'vault/tests/helpers/commands'; +import { runCmd, createNSFromPaths, deleteNSFromPaths } from 'vault/tests/helpers/commands'; import { login, loginNs, logout } from 'vault/tests/helpers/auth/auth-helpers'; import { AUTH_FORM } from 'vault/tests/helpers/auth/auth-form-selectors'; import { GENERAL } from 'vault/tests/helpers/general-selectors'; import { NAMESPACE_PICKER_SELECTORS } from '../helpers/namespace-picker'; -import sinon from 'sinon'; - -async function createNamespaces(namespaces) { - for (const ns of namespaces) { - // Note: iterate through the namespace parts to create the full namespace path - const parts = ns.split('/'); - let currentPath = ''; - - for (const part of parts) { - // Visit the parent namespace - const url = `/vault/dashboard${currentPath && `?namespace=${currentPath.replaceAll('/', '%2F')}`}`; - await visit(url); - - currentPath = currentPath ? `${currentPath}/${part}` : part; - - // Create the current namespace - await runCmd(createNS(part), false); - await settled(); - } - - // Reset to the root namespace - const url = '/vault/dashboard'; - await visit(url); - } -} - -async function deleteNamespaces(namespaces) { - // Reset to the root namespace - const url = '/vault/dashboard'; - await visit(url); - - for (const ns of namespaces) { - // Note: delete the parent namespace to delete all child namespaces - const part = ns.split('/')[0]; - await runCmd(deleteNS(part), false); - await settled(); - } -} - module('Acceptance | Enterprise | namespaces', function (hooks) { setupApplicationTest(hooks); - let fetchSpy; - - hooks.beforeEach(() => { - fetchSpy = sinon.spy(window, 'fetch'); - return login(); - }); - - hooks.afterEach(() => { - fetchSpy.restore(); + hooks.beforeEach(async () => { + await login(); }); test('it focuses the search input field when user toggles namespace picker', async function (assert) { - await click(NAMESPACE_PICKER_SELECTORS.toggle); + await click(GENERAL.toggleInput('namespace-id')); // Verify that the search input field is focused const searchInput = find(NAMESPACE_PICKER_SELECTORS.searchInput); @@ -87,11 +41,11 @@ module('Acceptance | Enterprise | namespaces', function (hooks) { }); test('it navigates to the matching namespace when Enter is pressed', async function (assert) { - // Test Setup + // Setup: Create namespace(s) via the CLI const namespaces = ['beep/boop']; - await createNamespaces(namespaces); + await createNSFromPaths(namespaces); - await click(NAMESPACE_PICKER_SELECTORS.toggle); + await click(GENERAL.toggleInput('namespace-id')); await click(GENERAL.button('Refresh list')); assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists'); @@ -112,16 +66,16 @@ module('Acceptance | Enterprise | namespaces', function (hooks) { 'Navigates to the correct namespace when Enter is pressed' ); - // Test Cleanup - await deleteNamespaces(namespaces); + // Cleanup: Delete namespace(s) via the CLI + await deleteNSFromPaths(namespaces); }); test('it filters namespaces based on search input', async function (assert) { - // Test Setup + // Setup: Create namespace(s) via the CLI const namespaces = ['beep/boop/bop']; - await createNamespaces(namespaces); + await createNSFromPaths(namespaces); - await click(NAMESPACE_PICKER_SELECTORS.toggle); + await click(GENERAL.toggleInput('namespace-id')); await click(GENERAL.button('Refresh list')); // Verify all namespaces are displayed initially @@ -163,51 +117,85 @@ module('Acceptance | Enterprise | namespaces', function (hooks) { 'All namespaces are displayed after clearing search input' ); - // Test Cleanup - await deleteNamespaces(namespaces); + // Cleanup: Delete namespace(s) via the CLI + await deleteNSFromPaths(namespaces); }); test('it updates the namespace list after clicking "Refresh list"', async function (assert) { - // Test Setup - const namespaces = ['beep']; - await createNamespaces(namespaces); + // Open the namespace picker + await click(GENERAL.toggleInput('namespace-id')); - await click(NAMESPACE_PICKER_SELECTORS.toggle); + // Verify the search input field exists + assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists'); - // Verify that the namespace list was fetched on load - let listNamespaceRequests = fetchSpy - .getCalls() - .filter((call) => call.args[0].includes('/v1/sys/internal/ui/namespaces')); + // Verify 0 namespaces are displayed after searching for "beep" + await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, 'beep'); assert.strictEqual( - listNamespaceRequests.length, - 1, - 'The network call to the specific endpoint was made twice (once on load, once on refresh)' + findAll(NAMESPACE_PICKER_SELECTORS.link()).length, + 0, + 'No namespaces are displayed after searching for "beep"' ); + // Close the namespace picker + await click(GENERAL.toggleInput('namespace-id')); + + // Create 'beep' namespace via the CLI + const namespaces = ['beep']; + await createNSFromPaths(namespaces); + + // Open the namespace picker + await click(GENERAL.toggleInput('namespace-id')); + // Refresh the list of namespaces assert.dom(GENERAL.button('Refresh list')).exists('Refresh list button exists'); await click(GENERAL.button('Refresh list')); - // Verify that the namespace list was fetched on refresh - listNamespaceRequests = fetchSpy - .getCalls() - .filter((call) => call.args[0].includes('/v1/sys/internal/ui/namespaces')); + // Verify the search input field exists + assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists'); + + // Verify 1 namespace is displayed after searching for "beep" + await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, 'beep'); assert.strictEqual( - listNamespaceRequests.length, - 2, - 'The network call to the specific endpoint was made twice (once on load, once on refresh)' + findAll(NAMESPACE_PICKER_SELECTORS.link('beep')).length, + 1, + '1 namespace is displayed after searching for "beep"' ); - // Test Cleanup - await deleteNamespaces(namespaces); + // Close the namespace picker + await click(GENERAL.toggleInput('namespace-id')); + + // Delete the 'beep' namespace via the CLI + await deleteNSFromPaths(namespaces); + + // Open the namespace picker + await click(GENERAL.toggleInput('namespace-id')); + + // Refresh the list of namespaces + assert.dom(GENERAL.button('Refresh list')).exists('Refresh list button exists'); + await click(GENERAL.button('Refresh list')); + + // Verify the search input field exists + assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists'); + + // Verify 0 namespaces are displayed after searching for "beep" + await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, 'beep'); + assert.strictEqual( + findAll(NAMESPACE_PICKER_SELECTORS.link()).length, + 0, + 'No namespaces are displayed after searching for "beep"' + ); + + // Close the namespace picker + await click(GENERAL.toggleInput('namespace-id')); }); test('it displays the "Manage" button with the correct URL', async function (assert) { - // Test Setup + // Setup: Create namespace(s) via the CLI const namespaces = ['beep']; - await createNamespaces(namespaces); + await createNSFromPaths(namespaces); - await click(NAMESPACE_PICKER_SELECTORS.toggle); + // Open the namespace picker & refresh the list of namespaces + await click(GENERAL.toggleInput('namespace-id')); await click(GENERAL.button('Refresh list')); // Verify the "Manage" button is rendered and has the correct URL @@ -215,37 +203,54 @@ module('Acceptance | Enterprise | namespaces', function (hooks) { .dom('[href="/ui/vault/access/namespaces"]') .exists('The "Manage" button is displayed with the correct URL'); - // Test Cleanup - await deleteNamespaces(namespaces); + // Cleanup: Delete namespace(s) via the CLI + await deleteNSFromPaths(namespaces); }); // This test originated from this PR: https://github.com/hashicorp/vault/pull/7186 test('it clears namespaces when you log out', async function (assert) { // Test Setup - const namespaces = ['foo']; - await createNamespaces(namespaces); + const namespace = 'foo'; + await createNSFromPaths([namespace]); - const ns = 'foo'; - await runCmd(createNS(ns), false); const token = await runCmd(`write -field=client_token auth/token/create policies=default`); await login(token); - await click(NAMESPACE_PICKER_SELECTORS.toggle); + + // Open the namespace picker + await click(GENERAL.toggleInput('namespace-id')); + + // Verify that the root namespace is selected by default assert.dom(NAMESPACE_PICKER_SELECTORS.link()).hasText('root', 'root renders as current namespace'); assert .dom(`${NAMESPACE_PICKER_SELECTORS.link()} svg${GENERAL.icon('check')}`) .exists('The root namespace is selected'); - // Test Cleanup - await deleteNamespaces(namespaces); + // Verify that the foo namespace does not exist in the namespace picker + assert + .dom(NAMESPACE_PICKER_SELECTORS.link(namespace)) + .exists({ count: 0 }, 'foo should not exist in the namespace picker'); + + // Logout and log back into root + await logout(); + await login(); + + // Open the namespace picker & verify that the foo namespace does exist + await click(GENERAL.toggleInput('namespace-id')); + assert + .dom(NAMESPACE_PICKER_SELECTORS.link(namespace)) + .exists({ count: 1 }, 'foo should exist in the namespace picker'); + + // Cleanup: Delete namespace(s) via the CLI + await deleteNSFromPaths([namespace]); }); // This test originated from this PR: https://github.com/hashicorp/vault/pull/7186 test('it displays namespaces whether you log in with a namespace prefixed with / or not', async function (assert) { - // Test Setup + // Setup: Create namespace(s) via the CLI const namespaces = ['beep/boop/bop']; - await createNamespaces(namespaces); + await createNSFromPaths(namespaces); - await click(NAMESPACE_PICKER_SELECTORS.toggle); + await click(GENERAL.toggleInput('namespace-id')); await click(GENERAL.button('Refresh list')); // Login with a namespace prefixed with / @@ -253,11 +258,11 @@ module('Acceptance | Enterprise | namespaces', function (hooks) { await settled(); assert - .dom(NAMESPACE_PICKER_SELECTORS.toggle) + .dom(GENERAL.toggleInput('namespace-id')) .hasText('boop', `shows the namespace 'boop' in the toggle component`); // Open the namespace picker & wait for it to render - await click(NAMESPACE_PICKER_SELECTORS.toggle); + await click(GENERAL.toggleInput('namespace-id')); assert.dom(`svg${GENERAL.icon('check')}`).exists('The check icon is rendered'); // Find the selected element with the check icon & ensure it exists @@ -275,8 +280,8 @@ module('Acceptance | Enterprise | namespaces', function (hooks) { 'The current namespace does not begin or end with /' ); - // Test Cleanup - await deleteNamespaces(namespaces); + // Cleanup: Delete namespace(s) via the CLI + await deleteNSFromPaths(namespaces); }); test('it shows the regular namespace toolbar when not managed', async function (assert) { @@ -295,37 +300,56 @@ module('Acceptance | Enterprise | namespaces', function (hooks) { }); test('it should allow the user to delete a namespace', async function (assert) { - // Test Setup - const namespaces = ['test-delete-me']; - await createNamespaces(namespaces); + // Setup: Create namespace(s) via the CLI + const namespace = 'test-delete-me'; + await createNSFromPaths([namespace]); await visit('/vault/access/namespaces'); - const searchInput = GENERAL.filterInputExplicit; - const searchButton = GENERAL.filterInputExplicitSearch; + // Verify that the namespace exists in the namespace picker + await click(GENERAL.toggleInput('namespace-id')); + await click(GENERAL.button('Refresh list')); + await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, namespace); - await fillIn(searchInput, 'test-delete-me'); - await click(searchButton); + assert + .dom(NAMESPACE_PICKER_SELECTORS.link(namespace)) + .exists({ count: 1 }, 'Namespace exists in the namespace picker'); + + // Close the namespace picker + await click(GENERAL.toggleInput('namespace-id')); + + // Verify that the namespace exists in the manage namespaces page + await fillIn(GENERAL.filterInputExplicit, namespace); + await click(GENERAL.filterInputExplicitSearch); assert.dom(GENERAL.menuTrigger).exists(); await click(GENERAL.menuTrigger); - // Verify that the user can delete the namespace - const deleteNamespaceButton = '.hds-dropdown-list-item:nth-of-type(1)'; - assert.dom(deleteNamespaceButton).hasText('Delete', 'Allow users to delete the namespace'); + // Delete the namespace + const deleteNamespaceButton = '.hds-dropdown-list-item:nth-of-type(2)'; + assert.dom(deleteNamespaceButton).hasText('Delete', 'Delete namespace button exists'); await click(`${deleteNamespaceButton} button`); - assert.dom(GENERAL.confirmButton).hasText('Confirm', 'Allow users to delete the namespace'); + assert.dom(GENERAL.confirmButton).hasText('Confirm', 'Confirm namespace deletion button is shown'); await click(GENERAL.confirmButton); + // Verify that the namespace does not exist in the nmanage namespace page assert.strictEqual( currentURL(), - '/vault/access/namespaces?page=1&pageFilter=test-delete-me', + `/vault/access/namespaces?page=1&pageFilter=${namespace}`, 'Should remain on the manage namespaces page after deletion' ); assert .dom('.list-item-row') .exists({ count: 0 }, 'Namespace should be deleted and not displayed in the list'); + + // Verify that the namespace does not exist in the namespace picker + await click(GENERAL.toggleInput('namespace-id')); + await click(GENERAL.button('Refresh list')); + await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, namespace); + assert + .dom(NAMESPACE_PICKER_SELECTORS.link()) + .exists({ count: 0 }, 'Deleted namespace does not exist in the namespace picker'); }); }); diff --git a/ui/tests/acceptance/enterprise-replication-test.js b/ui/tests/acceptance/enterprise-replication-test.js index 7282b0ba00..51b5afcbe1 100644 --- a/ui/tests/acceptance/enterprise-replication-test.js +++ b/ui/tests/acceptance/enterprise-replication-test.js @@ -23,6 +23,7 @@ import { create } from 'ember-cli-page-object'; import flashMessage from 'vault/tests/pages/components/flash-message'; import ss from 'vault/tests/pages/components/search-select'; import { disableReplication } from 'vault/tests/helpers/replication'; +import { GENERAL } from '../helpers/general-selectors'; const searchSelect = create(ss); const flash = create(flashMessage); @@ -272,7 +273,7 @@ module('Acceptance | Enterprise | replication', function (hooks) { await click('[data-test-secondary-add]'); await fillIn('[data-test-replication-secondary-id]', secondaryNameSecond); - await click('[data-test-toggle-input]'); + await click(GENERAL.toggleInput('Time to Live (TTL) for generated secondary token')); await fillIn('[data-test-ttl-value]', 3); await click('[data-test-secondary-add]'); diff --git a/ui/tests/acceptance/secrets/backend/kubernetes/credentials-test.js b/ui/tests/acceptance/secrets/backend/kubernetes/credentials-test.js index dcb523b60a..5fdf61afcd 100644 --- a/ui/tests/acceptance/secrets/backend/kubernetes/credentials-test.js +++ b/ui/tests/acceptance/secrets/backend/kubernetes/credentials-test.js @@ -10,6 +10,7 @@ import kubernetesScenario from 'vault/mirage/scenarios/kubernetes'; import kubernetesHandlers from 'vault/mirage/handlers/kubernetes'; import { login } from 'vault/tests/helpers/auth/auth-helpers'; import { fillIn, visit, click, currentRouteName } from '@ember/test-helpers'; +import { GENERAL } from 'vault/tests/helpers/general-selectors'; module('Acceptance | kubernetes | credentials', function (hooks) { setupApplicationTest(hooks); @@ -66,8 +67,8 @@ module('Acceptance | kubernetes | credentials', function (hooks) { }; }); await fillIn('[data-test-kubernetes-namespace]', 'kubernetes-test'); - await click('[data-test-toggle-input]'); - await click('[data-test-toggle-input="Time-to-Live (TTL)"]'); + await click(GENERAL.toggleInput('kubernetes-clusterRoleBinding')); + await click(GENERAL.toggleInput('Time-to-Live (TTL)')); await fillIn('[data-test-ttl-value="Time-to-Live (TTL)"]', 2); await click('[data-test-generate-credentials-button]'); await click('[data-test-generate-credentials-done]'); diff --git a/ui/tests/helpers/commands.js b/ui/tests/helpers/commands.js index a585b4d0ef..e1f1db346e 100644 --- a/ui/tests/helpers/commands.js +++ b/ui/tests/helpers/commands.js @@ -3,8 +3,9 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { click, fillIn, findAll, triggerKeyEvent } from '@ember/test-helpers'; +import { click, fillIn, findAll, triggerKeyEvent, visit } from '@ember/test-helpers'; +// REPL selectors const REPL = { toggle: '[data-test-console-toggle]', consoleInput: '[data-test-component="console/command-input"] input', @@ -13,49 +14,33 @@ const REPL = { /** * Helper functions to run common commands in the consoleComponent during tests. - * Please note that a user must be logged in during the test context for the commands to run. - * By default runCmd throws an error if the last log includes "Error". To override this, - * pass boolean false to run the commands and not throw errors + * Note: A user must be logged in during the test context for the commands to run. * * Example: - * * import { v4 as uuidv4 } from 'uuid'; * import { runCmd, mountEngineCmd } from 'vault/tests/helpers/commands'; * - * - * async function mountEngineExitOnError() { - * const backend = `pki-${uuidv4()}`; - * await runCmd(mountEngineCmd('pki', backend)); - * return backend; - * } - * - * async function mountEngineSquashErrors() { - * const backend = `pki-${uuidv4()}`; - * await runCmd(mountEngineCmd('pki', backend), false); - * return backend; + * async function mountEngine() { + * const backend = `pki-${uuidv4()}`; + * await runCmd(mountEngineCmd('pki', backend)); + * return backend; * } */ -/** - * runCmd is used to run commands and throw an error if the output includes "Error" - * @param {string || string[]} commands array of commands that should run - * @param {boolean} throwErrors - * @returns the last log output. Throws an error if it includes an error - */ +// Command execution helpers export const runCmd = async (commands, throwErrors = true) => { - if (!commands) { - throw new Error('runCmd requires commands array passed in'); - } - if (!Array.isArray(commands)) { - commands = [commands]; - } + if (!commands) throw new Error('runCmd requires commands array passed in'); + if (!Array.isArray(commands)) commands = [commands]; + await click(REPL.toggle); await enterCommands(commands); const lastOutput = await lastLogOutput(); await click(REPL.toggle); + if (throwErrors && lastOutput.includes('Error')) { throw new Error(`Error occurred while running commands: "${commands.join('; ')}" - ${lastOutput}`); } + return lastOutput; }; @@ -69,54 +54,93 @@ export const enterCommands = async (commands) => { export const lastLogOutput = async () => { const items = findAll(REPL.logOutputItems); - const count = items.length; - if (count === 0) { - // If no logOutput items are found, we can assume the response is empty - return ''; - } - const outputItemText = items[count - 1].innerText; - return outputItemText; + if (!items.length) return ''; + return items[items.length - 1].innerText; }; -// Common commands -export function mountEngineCmd(type, customName = '') { +// Command builders +export const mountEngineCmd = (type, customName = '') => { const name = customName || type; - if (type === 'kv-v2') { - return `write sys/mounts/${name} type=kv options=version=2`; - } - return `write sys/mounts/${name} type=${type}`; -} + return type === 'kv-v2' + ? `write sys/mounts/${name} type=kv options=version=2` + : `write sys/mounts/${name} type=${type}`; +}; -export function deleteEngineCmd(name) { - return `delete sys/mounts/${name}`; -} +export const deleteEngineCmd = (name) => `delete sys/mounts/${name}`; -export function mountAuthCmd(type, customName = '') { +export const mountAuthCmd = (type, customName = '') => { const name = customName || type; return `write sys/auth/${name} type=${type}`; -} - -export function deleteAuthCmd(name) { - return `delete sys/auth/${name}`; -} - -export function createPolicyCmd(name, contents) { - const policyContent = window.btoa(contents); - return `write sys/policies/acl/${name} policy=${policyContent}`; -} - -export function createTokenCmd(policyName = 'default') { - return `write -field=client_token auth/token/create policies=${policyName} ttl=1h`; -} - -export const tokenWithPolicyCmd = function (name, policy) { - return [createPolicyCmd(name, policy), createTokenCmd(name)]; }; -export function createNS(namespace) { - return `write sys/namespaces/${namespace} -f`; -} +export const deleteAuthCmd = (name) => `delete sys/auth/${name}`; -export function deleteNS(namespace) { - return `delete sys/namespaces/${namespace} -f`; -} +export const createPolicyCmd = (name, contents) => { + const policyContent = window.btoa(contents); + return `write sys/policies/acl/${name} policy=${policyContent}`; +}; + +export const createTokenCmd = (policyName = 'default') => + `write -field=client_token auth/token/create policies=${policyName} ttl=1h`; + +export const tokenWithPolicyCmd = (name, policy) => [createPolicyCmd(name, policy), createTokenCmd(name)]; + +export const createNS = (namespace) => `write sys/namespaces/${namespace} -f`; + +export const deleteNS = (namespace) => `delete sys/namespaces/${namespace} -f`; + +/** + * @description + * Iterates over an array of namespace paths and ensures each nested level is created. + * It visits the root namespace before attempting to create the next segment. + * + * @example input: ['foo/bar', 'baz/qux/quux'] + * This will create: foo, foo/bar, baz, baz/qux, baz/qux/quux + * + * @param {string[]} namespaces - Array of strings of namespace paths (containing backslashes) + */ +export const createNSFromPaths = async (namespaces) => { + for (const ns of namespaces) { + const parts = ns.split('/'); + let currentPath = ''; + + for (const part of parts) { + const url = `/vault/dashboard${currentPath && `?namespace=${currentPath.replaceAll('/', '%2F')}`}`; + await visit(url); + + currentPath = currentPath ? `${currentPath}/${part}` : part; + await runCmd(createNS(part), false); + } + + // Reset to root namespace after creating each path + await visit('/vault/dashboard'); + } +}; + +/** + * @description + * Deletes namespaces by removing each segment of the path from deepest to top. + * + * @example input: ['foo/bar', 'baz/qux'] + * This will delete: foo, baz + * + * @param {string[]} namespaces - Array of strings of namespace paths (containing backslashes) + */ +export const deleteNSFromPaths = async (namespaces) => { + for (const ns of namespaces) { + const parts = ns.split('/'); + // Work from deepest child up to the top-level namespace + for (let i = parts.length - 1; i >= 0; i--) { + const parentPath = parts.slice(0, i).join('/'); + const toDelete = parts[i]; + // Build the URL for the parent namespace (or root if none) + const url = parentPath + ? `/vault/dashboard?namespace=${parentPath.replaceAll('/', '%2F')}` + : '/vault/dashboard'; + await visit(url); + await runCmd(deleteNS(toDelete), false); + } + // Reset to root namespace after deleting each path + await visit('/vault/dashboard'); + } +}; diff --git a/ui/tests/helpers/namespace-picker.js b/ui/tests/helpers/namespace-picker.js index d69c54e1c6..bc41ed376e 100644 --- a/ui/tests/helpers/namespace-picker.js +++ b/ui/tests/helpers/namespace-picker.js @@ -5,6 +5,5 @@ export const NAMESPACE_PICKER_SELECTORS = { link: (link) => (link ? `[data-test-namespace-link="${link}"]` : '[data-test-namespace-link]'), - toggle: '[data-test-namespace-toggle]', searchInput: 'input[type="search"]', }; diff --git a/ui/tests/integration/components/form-field-test.js b/ui/tests/integration/components/form-field-test.js index 691fc6b1b8..4a6877b950 100644 --- a/ui/tests/integration/components/form-field-test.js +++ b/ui/tests/integration/components/form-field-test.js @@ -178,7 +178,7 @@ module('Integration | Component | form field', function (hooks) { }) ); assert.ok(component.hasToggleButton, 'renders a toggle button'); - assert.dom('[data-test-toggle-input]').isNotChecked(); + assert.dom(GENERAL.toggleInput('toggle-foobar')).isNotChecked(); assert.dom('[data-test-toggle-subtext]').hasText('Toggled off'); await component.fields.objectAt(0).toggleButton(); diff --git a/ui/tests/integration/components/kubernetes/page/credentials-test.js b/ui/tests/integration/components/kubernetes/page/credentials-test.js index 83c29f2093..d966ff00f3 100644 --- a/ui/tests/integration/components/kubernetes/page/credentials-test.js +++ b/ui/tests/integration/components/kubernetes/page/credentials-test.js @@ -109,9 +109,8 @@ module('Integration | Component | kubernetes | Page::Credentials', function (hoo await this.renderComponent(); await fillIn('[data-test-kubernetes-namespace]', 'kubernetes-test'); assert.dom('[data-test-kubernetes-namespace]').hasValue('kubernetes-test', 'kubernetes-test'); - - await click('[data-test-toggle-input]'); - await click('[data-test-toggle-input="Time-to-Live (TTL)"]'); + await click(GENERAL.toggleInput('kubernetes-clusterRoleBinding')); + await click(GENERAL.toggleInput('Time-to-Live (TTL)')); await fillIn('[data-test-ttl-value="Time-to-Live (TTL)"]', 2); await click('[data-test-generate-credentials-button]'); diff --git a/ui/tests/integration/components/namespace-picker-test.js b/ui/tests/integration/components/namespace-picker-test.js index 6b81eb626d..ba0798e016 100644 --- a/ui/tests/integration/components/namespace-picker-test.js +++ b/ui/tests/integration/components/namespace-picker-test.js @@ -62,7 +62,7 @@ module('Integration | Component | namespace-picker', function (hooks) { test('it focuses the search input field when the component is loaded', async function (assert) { await render(hbs``); - await click(NAMESPACE_PICKER_SELECTORS.toggle); + await click(GENERAL.toggleInput('namespace-id')); // Verify that the search input field is focused const searchInput = find(NAMESPACE_PICKER_SELECTORS.searchInput); @@ -75,7 +75,7 @@ module('Integration | Component | namespace-picker', function (hooks) { test('it filters namespace options based on search input', async function (assert) { await render(hbs``); - await click(NAMESPACE_PICKER_SELECTORS.toggle); + await click(GENERAL.toggleInput('namespace-id')); // Verify all namespaces are displayed initially await waitFor(NAMESPACE_PICKER_SELECTORS.link()); @@ -117,7 +117,7 @@ module('Integration | Component | namespace-picker', function (hooks) { }); await render(hbs``); - await click(NAMESPACE_PICKER_SELECTORS.toggle); + await click(GENERAL.toggleInput('namespace-id')); // Verify that the "Refresh List" button is visible assert.dom(GENERAL.button('Refresh list')).exists('Refresh List button is visible'); @@ -134,7 +134,7 @@ module('Integration | Component | namespace-picker', function (hooks) { }); await render(hbs``); - await click(NAMESPACE_PICKER_SELECTORS.toggle); + await click(GENERAL.toggleInput('namespace-id')); // Verify that the buttons are hidden assert.dom(GENERAL.button('Refresh list')).doesNotExist('Refresh List button is hidden'); @@ -148,7 +148,7 @@ module('Integration | Component | namespace-picker', function (hooks) { }); await render(hbs``); - await click(NAMESPACE_PICKER_SELECTORS.toggle); + await click(GENERAL.toggleInput('namespace-id')); // Verify that the buttons are hidden assert.dom(GENERAL.button('Refresh list')).doesNotExist('Refresh List button is hidden'); @@ -167,7 +167,7 @@ module('Integration | Component | namespace-picker', function (hooks) { }); await render(hbs``); - await click(NAMESPACE_PICKER_SELECTORS.toggle); + await click(GENERAL.toggleInput('namespace-id')); // Dynamically modify the `findNamespacesForUser.perform` method for this test const namespaceService = this.owner.lookup('service:namespace'); diff --git a/ui/tests/integration/components/sidebar/frame-test.js b/ui/tests/integration/components/sidebar/frame-test.js index 70d7835875..caf9fab0a6 100644 --- a/ui/tests/integration/components/sidebar/frame-test.js +++ b/ui/tests/integration/components/sidebar/frame-test.js @@ -9,7 +9,7 @@ import { render, click } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; import sinon from 'sinon'; import { setRunOptions } from 'ember-a11y-testing/test-support'; -import { NAMESPACE_PICKER_SELECTORS } from 'vault/tests/helpers/namespace-picker'; +import { GENERAL } from 'vault/tests/helpers/general-selectors'; module('Integration | Component | sidebar-frame', function (hooks) { setupRenderingTest(hooks); @@ -89,6 +89,6 @@ module('Integration | Component | sidebar-frame', function (hooks) { `); - assert.dom(NAMESPACE_PICKER_SELECTORS.toggle).exists('Namespace picker renders in sidebar footer'); + assert.dom(GENERAL.toggleInput('namespace-id')).exists('Namespace picker renders in sidebar footer'); }); });