From 6cc4eae735db96b06c0753deb074bf47e0a7392d Mon Sep 17 00:00:00 2001 From: Angel Garbarino Date: Mon, 23 Jun 2025 09:12:52 -0600 Subject: [PATCH] Address flaky tests: namespace and config-ui/messages (#31016) * namespace and ui-config, running out of time ahhh * fix some tests * triple back to back runs on namespace and we're solid * add cleanup test pollution on config-ui/messages and also remove the empty state check as we do that in the component test: * Fix test error: "Promise rejected during "it should show the list of custom messages": _generalSelectors.GENERAL.listItem is not a function" * fix more tests --------- Co-authored-by: Shannon Roberts --- ui/app/components/namespace-picker.hbs | 7 +- ui/app/components/namespace-picker.ts | 9 +- .../vault/cluster/access/namespaces/index.js | 26 +- .../vault/cluster/access/namespaces/index.hbs | 9 +- .../addon/components/messages/page/list.hbs | 2 +- .../components/filter-input-explicit.hbs | 2 +- .../access/namespaces/index-test.js | 377 ++++-------------- ui/tests/acceptance/auth/auth-test.js | 4 +- .../config-ui/messages/messages-test.js | 41 +- .../messages/messages-unauth-auth-test.js | 6 +- ui/tests/acceptance/dashboard-test.js | 6 +- .../acceptance/enterprise-namespaces-test.js | 237 ++--------- .../secrets/backend/kubernetes/roles-test.js | 2 +- .../sync/secrets/destination-test.js | 2 +- .../sync/secrets/destinations-test.js | 8 +- .../helpers/config-ui/message-selectors.ts | 1 - ui/tests/helpers/general-selectors.ts | 6 +- ui/tests/helpers/namespace-picker.js | 9 - .../config-ui/messages/page/list-test.js | 3 +- .../components/filter-input-explicit-test.js | 2 +- .../components/namespace-picker-test.js | 64 ++- .../components/sidebar/frame-test.js | 2 +- 22 files changed, 226 insertions(+), 599 deletions(-) delete mode 100644 ui/tests/helpers/namespace-picker.js diff --git a/ui/app/components/namespace-picker.hbs b/ui/app/components/namespace-picker.hbs index 33721c29f4..eaba060346 100644 --- a/ui/app/components/namespace-picker.hbs +++ b/ui/app/components/namespace-picker.hbs @@ -10,7 +10,7 @@ @icon="org" @text={{or this.selectedNamespace.id "-"}} @isFullWidth={{true}} - data-test-toggle-input="namespace-id" + data-test-toggle-input="namespace-picker" {{on "click" this.toggleNamespacePicker}} /> @@ -29,6 +29,7 @@ @type="search" aria-label="Search namespaces" placeholder="Search" + data-test-input="Search namespaces" {{on "keydown" this.onKeyDown}} {{on "input" this.onSearchInput}} {{did-insert this.focusSearchInput}} @@ -50,7 +51,7 @@ {{#if this.showNoNamespacesMessage}} - + {{this.noNamespacesMessage}} {{/if}} @@ -60,7 +61,7 @@ {{option.label}} diff --git a/ui/app/components/namespace-picker.ts b/ui/app/components/namespace-picker.ts index 5c8603a8fd..15b590c6aa 100644 --- a/ui/app/components/namespace-picker.ts +++ b/ui/app/components/namespace-picker.ts @@ -163,9 +163,12 @@ export default class NamespacePicker extends Component { element.style.display = 'none'; let maxWidth = 240; // Default minimum width - const namespaceLinks = document.querySelectorAll('[data-test-namespace-link]'); - namespaceLinks.forEach((checkmark: Element) => { - const width = (checkmark as HTMLElement).offsetWidth; + // Calculate the maximum width of the visible namespace options + // The namespace is displayed as an HDS::checkmark button, so we need to find the width of the checkmark element + this.visibleNamespaceOptions.forEach((namespace: NamespaceOption) => { + const checkmarkElement = document.querySelector(`[data-test-button="${namespace.label}"]`); + + const width = (checkmarkElement as HTMLElement).offsetWidth; if (width > maxWidth) { maxWidth = width; } diff --git a/ui/app/routes/vault/cluster/access/namespaces/index.js b/ui/app/routes/vault/cluster/access/namespaces/index.js index 5ed540c09f..c229c55a3e 100644 --- a/ui/app/routes/vault/cluster/access/namespaces/index.js +++ b/ui/app/routes/vault/cluster/access/namespaces/index.js @@ -6,8 +6,11 @@ import { service } from '@ember/service'; import Route from '@ember/routing/route'; import { action } from '@ember/object'; +import { buildWaiter } from '@ember/test-waiters'; import { hash } from 'rsvp'; +const waiter = buildWaiter('namespace-list-route'); + export default class NamespaceListRoute extends Route { @service pagination; @service store; @@ -30,20 +33,23 @@ export default class NamespaceListRoute extends Route { } async fetchNamespaces(params) { - return await this.pagination - .lazyPaginatedQuery('namespace', { + const waiterToken = waiter.beginAsync(); + try { + const model = await this.pagination.lazyPaginatedQuery('namespace', { responsePath: 'data.keys', page: Number(params?.page) || 1, pageFilter: params?.pageFilter, - }) - .then((model) => model) - .catch((err) => { - if (err.httpStatus === 404) { - return []; - } else { - throw err; - } }); + return model; + } catch (err) { + if (err.httpStatus === 404) { + return []; + } else { + throw err; + } + } finally { + waiter.endAsync(waiterToken); + } } model(params) { diff --git a/ui/app/templates/vault/cluster/access/namespaces/index.hbs b/ui/app/templates/vault/cluster/access/namespaces/index.hbs index 16736c0001..c9366fde06 100644 --- a/ui/app/templates/vault/cluster/access/namespaces/index.hbs +++ b/ui/app/templates/vault/cluster/access/namespaces/index.hbs @@ -12,7 +12,7 @@ -

+

Namespaces

@@ -51,7 +51,6 @@ as |list| > {{#if this.model.namespaces.length}} - {{list.item.id}} @@ -68,7 +67,11 @@ to namespace {{/if}} {{/let}} - Delete + Delete {{#if (eq this.nsToDelete list.item)}}
- + {{message.title}} diff --git a/ui/lib/core/addon/components/filter-input-explicit.hbs b/ui/lib/core/addon/components/filter-input-explicit.hbs index 2541ce2c40..c033171ed6 100644 --- a/ui/lib/core/addon/components/filter-input-explicit.hbs +++ b/ui/lib/core/addon/components/filter-input-explicit.hbs @@ -14,6 +14,6 @@ {{on "keydown" @handleKeyDown}} data-test-filter-input-explicit /> - + \ No newline at end of file diff --git a/ui/tests/acceptance/access/namespaces/index-test.js b/ui/tests/acceptance/access/namespaces/index-test.js index 65da29e80b..eca3e79cf6 100644 --- a/ui/tests/acceptance/access/namespaces/index-test.js +++ b/ui/tests/acceptance/access/namespaces/index-test.js @@ -3,373 +3,150 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { currentRouteName, visit, click, fillIn, currentURL, findAll, waitFor } from '@ember/test-helpers'; +import { click, currentRouteName, fillIn, visit, waitFor } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; 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'; +import { createNS, deleteNS, runCmd } from 'vault/tests/helpers/commands'; module('Acceptance | Enterprise | /access/namespaces', function (hooks) { setupApplicationTest(hooks); 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'); + }); + test('the route url navigates to namespace index page', async function (assert) { assert.strictEqual( currentRouteName(), 'vault.cluster.access.namespaces.index', 'navigates to the correct route' ); + + assert.dom(GENERAL.title).hasText('Namespaces', 'Page title is displayed correctly'); }); - test('it displays the breadcrumb trail', async function (assert) { - // Go to the manage namespaces page - await visit('/vault/access/namespaces'); - + test('the route displays the breadcrumb trail', async function (assert) { 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) { - const testNS = 'test-refresh-ns'; + test('the route should update namespace list after create/delete WITH manual refresh in the CLI', async function (assert) { + const testNS = 'test-refresh-ns-cli'; // Setup: Create namespace via the CLI - const namespaces = [testNS]; - await createNSFromPaths(namespaces); + await runCmd(createNS(testNS), false); - // 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 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'); + // Click the refresh list button on the namespace page await click(GENERAL.button('refresh-namespace-list')); + await fillIn(GENERAL.filterInputExplicit, testNS); + await click(GENERAL.button('Search')); + assert.dom('[data-test-list-item]').hasText(testNS, 'Namespace is displayed after refreshing the 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 + // Delete the created namespace via the CLI + await runCmd(deleteNS(testNS), false); await visit('/vault/access/namespaces'); - // Open the namespace picker - await click(GENERAL.toggleInput('namespace-id')); + // Search for the deleted namespace + await fillIn(GENERAL.filterInputExplicit, testNS); + await click(GENERAL.button('Search')); - // 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'); + // Click the refresh list button from the namespace page 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) { - // Go to the manage namespaces page - await visit('/vault/access/namespaces'); - assert - .dom(GENERAL.linkTo('create-namespace')) - .hasText('Create namespace', 'Create namespace button is rendered correctly'); - assert - .dom(GENERAL.linkTo('create-namespace')) - .hasAttribute( - 'href', - '/ui/vault/access/namespaces/create', - 'Create namespace button has the correct href attribute' + .dom(GENERAL.emptyStateTitle) + .hasText( + 'No namespaces yet', + 'Empty state is displayed when searching for the namespace we have created in the CLI but have not refreshed the list yet' ); }); - 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'); + test('the route should update namespace list after create/delete WITHOUT manual refresh in the UI', async function (assert) { + const testNS = 'test-create-ns-ui'; // 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 + await click(GENERAL.button('Search')); + await waitFor(GENERAL.emptyStateTitle, { + timeout: 2000, + timeoutMessage: 'timed out waiting for empty state title to render', + }); assert - .dom(GENERAL.linkTo('create-namespace')) - .hasText('Create namespace', 'Create namespace button is displayed'); + .dom(GENERAL.emptyStateTitle) + .hasText( + 'No namespaces yet', + 'Empty state is displayed when searching for the namespace we have created in the UI but have not refreshed the list yet' + ); + + // Create a new namespace in the UI 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(GENERAL.submitButton).exists('Save button is displayed'); await click(GENERAL.submitButton); - // Verify test-create-ns exists in the Manage Namespace page + // Verify test-create-ns-ui exists 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')); + await click(GENERAL.button('Search')); + assert.dom('[data-test-list-item]').hasText(testNS, 'Namespace is displayed after refreshing the list'); // 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.menuItem('delete')); 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 waitFor(NAMESPACE_PICKER_SELECTORS.searchInput); - 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(GENERAL.filterInputExplicit, 'other'); - assert.dom(GENERAL.filterInputExplicit).hasValue('other', 'Search input contains the entered text'); - - // Click the search button - 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('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=other', - 'URL query param is updated to reflect the search field as pageFilter' - ); - - // Clear the search input - await fillIn(GENERAL.filterInputExplicit, ''); - await click(GENERAL.filterInputExplicitSearch); - - assert.dom(GENERAL.filterInputExplicit).hasValue('', 'Search input is cleared'); - assert - .dom('.list-item-row') - .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'); - - // 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.'); - - // Verify that the user can switch to the namespace - const switchNamespaceButton = '.hds-dropdown-list-item:nth-of-type(1)'; + // Verify test-create-ns does not exist in the Manage Namespace page assert - .dom(switchNamespaceButton) - .hasText('Switch to namespace', 'Allow users to switch to different namespace'); - - // Verify that the user can delete the namespace - assert - .dom('.hds-dropdown-list-item:nth-of-type(2)') - .hasText('Delete', 'Delete namespace option is displayed'); - - // Cleanup: Delete namespace(s) via the CLI - await deleteNSFromPaths([namespace]); + .dom(GENERAL.emptyStateTitle) + .hasText('No namespaces yet', 'Empty state is displayed indicating the namespace was deleted'); }); - test('it should render updated namespace after switching from access page', async function (assert) { + test('the route should show "delete" option menu for each namespace', async function (assert) { // Setup: Create namespace(s) via the CLI - const testNS = 'test-create-ns'; - await createNSFromPaths([testNS]); + const testNS = 'asdf'; + await runCmd(createNS(testNS), false); - // Go to the manage namespaces page - await visit('/vault/access/namespaces'); + // Search for created namespace// Enter search text + await fillIn(GENERAL.filterInputExplicit, testNS); + await click(GENERAL.button('Search')); + await click(GENERAL.button('refresh-namespace-list')); - // Hack: Trigger refresh internal namespaces endpoint - await click(GENERAL.toggleInput('namespace-id')); - await click(GENERAL.button('Refresh list')); + // Verify the menu options + await waitFor(GENERAL.menuTrigger, { + timeout: 2000, + timeoutMessage: 'timed out waiting for menu trigger to render', + }); + await click(GENERAL.menuTrigger); + assert.dom(GENERAL.menuItem('delete')).exists('Delete namespace option is displayed'); + + // Cleanup: Delete namespace(s) via the CLI + await runCmd(deleteNS(testNS), false); + }); + + test('the route should switch to the selected namespace on click "Switch to namespace"', async function (assert) { + // Setup: Create namespace(s) via the CLI + const testNS = 'test-create-ns-switch'; + await runCmd(createNS(testNS), false); + + // Search for created namespace + await fillIn(GENERAL.filterInputExplicit, testNS); + await click(GENERAL.button('Search')); + await click(GENERAL.button('refresh-namespace-list')); // Switch namespace + await waitFor(GENERAL.menuTrigger); await click(GENERAL.menuTrigger); await click(GENERAL.menuItem('switch')); // Verify that we switched namespaces - await click(GENERAL.toggleInput('namespace-id')); - assert.dom('[data-test-badge-namespace]').hasText(testNS); + await click(GENERAL.toggleInput('namespace-picker')); + assert.dom('[data-test-badge-namespace]').hasText(testNS, 'Namespace badge shows the correct namespace'); assert.strictEqual(currentRouteName(), 'vault.cluster.dashboard', 'navigates to the correct route'); // Cleanup: Delete namespace(s) via the CLI - await deleteNSFromPaths([testNS]); + await runCmd(deleteNS(testNS), false); }); }); diff --git a/ui/tests/acceptance/auth/auth-test.js b/ui/tests/acceptance/auth/auth-test.js index 901d366e7c..63152a8f65 100644 --- a/ui/tests/acceptance/auth/auth-test.js +++ b/ui/tests/acceptance/auth/auth-test.js @@ -12,6 +12,7 @@ import VAULT_KEYS from 'vault/tests/helpers/vault-keys'; import { createNS, createPolicyCmd, + deleteNS, mountAuthCmd, mountEngineCmd, runCmd, @@ -380,7 +381,8 @@ module('Acceptance | auth login form', function (hooks) { await visit(`/vault/logout?namespace=${ns}`); await fillIn(GENERAL.inputByAttr('namespace'), ''); // clear login form namespace input await login(); - await runCmd([`delete sys/namespaces/${ns}`], false); + // clean up namespace pollution + await runCmd(deleteNS(ns)); }); test('it sets namespace header for sys/internal/ui/mounts request when namespace is inputted', async function (assert) { diff --git a/ui/tests/acceptance/config-ui/messages/messages-test.js b/ui/tests/acceptance/config-ui/messages/messages-test.js index b1f7352d4c..f2ae4655f7 100644 --- a/ui/tests/acceptance/config-ui/messages/messages-test.js +++ b/ui/tests/acceptance/config-ui/messages/messages-test.js @@ -6,7 +6,7 @@ import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; import { setupMirage } from 'ember-cli-mirage/test-support'; -import { click, visit, fillIn, findAll } from '@ember/test-helpers'; +import { click, visit, fillIn, findAll, waitFor } from '@ember/test-helpers'; import { login } from 'vault/tests/helpers/auth/auth-helpers'; import { runCmd } from 'vault/tests/helpers/commands'; import { format, addDays, startOfDay } from 'date-fns'; @@ -77,7 +77,7 @@ module('Acceptance | Enterprise | config-ui/message', function (hooks) { // The result will contain the message ID in the response, but the response is a giant string not an object. const match = result.match(/id\s+([a-f0-9-]+)/i); const messageId = match ? match[1] : null; - messageIdObject.title = messageId; + messageIdObject[title] = messageId; // visit the details page to ensure the message is created await visit(`/vault/config-ui/messages/${messageId}/details`); }; @@ -87,8 +87,9 @@ module('Acceptance | Enterprise | config-ui/message', function (hooks) { for (const id of Object.values(messageIdObject)) { await runCmd(`vault delete sys/config/ui/custom-messages/${id}`); } - await visit(`/vault/config-ui/messages/index`); // redirect to messages index after delete to ensure the state is refreshed + await visit(`/vault/config-ui/messages`); // redirect to messages index after delete to ensure the state is refreshed }; + this.createMessageBrowser = async ({ title, type = 'banner', @@ -126,15 +127,6 @@ module('Acceptance | Enterprise | config-ui/message', function (hooks) { await visit('/vault/logout'); }); - test('it should show an empty state when no messages are created', async function (assert) { - await click(CUSTOM_MESSAGES.navLink); - assert.dom(GENERAL.emptyStateTitle).exists(); - assert.dom(GENERAL.emptyStateTitle).hasText('No messages yet'); - await click(CUSTOM_MESSAGES.tab('On login page')); - assert.dom(GENERAL.emptyStateTitle).exists(); - assert.dom(GENERAL.emptyStateTitle).hasText('No messages yet'); - }); - test('authenticated it should create, edit, view, and delete a message', async function (assert) { // create first message await this.createMessageRepl({ title: 'new-message' }); @@ -350,4 +342,29 @@ module('Acceptance | Enterprise | config-ui/message', function (hooks) { assert.dom(CUSTOM_MESSAGES.modal('preview image')).doesNotExist('preview image does not show'); assert.dom(CUSTOM_MESSAGES.input('title')).hasClass('has-error-border', 'error around title shows'); }); + + test('cleanup message pollution', async function (assert) { + // Visit the messages page and delete any remaining messages. + await visit('/vault/config-ui/messages'); + const rows = findAll('.list-item-row'); + for (const row of rows) { + const trigger = row.querySelector('[data-test-popup-menu-trigger]'); + if (trigger) { + await click(GENERAL.menuTrigger); + await click(GENERAL.menuItem('delete')); + await click(GENERAL.confirmButton); + } + } + + // Redirect to the dashboard and revisit the messages page to refresh the state. + await visit('/vault/dashboard'); + await visit('/vault/config-ui/messages'); + + // Wait for the empty state to render and assert that no messages exist. + await waitFor(GENERAL.emptyStateTitle, { + timeout: 2000, + timeoutMessage: 'Timed out waiting for empty state title to render', + }); + assert.dom(GENERAL.emptyStateTitle).hasText('No messages yet', 'No messages exist after cleanup'); + }); }); diff --git a/ui/tests/acceptance/config-ui/messages/messages-unauth-auth-test.js b/ui/tests/acceptance/config-ui/messages/messages-unauth-auth-test.js index 059c60f353..a218ad15b8 100644 --- a/ui/tests/acceptance/config-ui/messages/messages-unauth-auth-test.js +++ b/ui/tests/acceptance/config-ui/messages/messages-unauth-auth-test.js @@ -13,7 +13,7 @@ import { setupMirage } from 'ember-cli-mirage/test-support'; import { login } from 'vault/tests/helpers/auth/auth-helpers'; import { datetimeLocalStringFormat } from 'core/utils/date-formatters'; import { format, addDays, startOfDay } from 'date-fns'; -import { createNS, runCmd } from '../../../helpers/commands'; +import { createNS, deleteNS, runCmd } from '../../../helpers/commands'; const unauthenticatedMessageResponse = { request_id: '664fbad0-fcd8-9023-4c5b-81a7962e9f4b', @@ -152,7 +152,7 @@ module('Acceptance | auth custom messages auth tests', function (hooks) { assert .dom('.hds-alert') .exists('active custom message displays on namespace authenticated from within a namespace'); - await click(CUSTOM_MESSAGES.listItem('active authenticated message title')); + await click(GENERAL.listItem('active authenticated message title')); await click(GENERAL.confirmTrigger); await click(GENERAL.confirmButton); assert.strictEqual( @@ -160,5 +160,7 @@ module('Acceptance | auth custom messages auth tests', function (hooks) { 'vault.cluster.config-ui.messages.index', 'redirects to messages page after delete' ); + // clean up namespace pollution + await runCmd(deleteNS('world')); }); }); diff --git a/ui/tests/acceptance/dashboard-test.js b/ui/tests/acceptance/dashboard-test.js index 15953c8e7b..fd277541b5 100644 --- a/ui/tests/acceptance/dashboard-test.js +++ b/ui/tests/acceptance/dashboard-test.js @@ -26,7 +26,7 @@ import { pollCluster } from 'vault/tests/helpers/poll-cluster'; import { disableReplication } from 'vault/tests/helpers/replication'; import connectionPage from 'vault/tests/pages/secrets/backend/database/connection'; import { v4 as uuidv4 } from 'uuid'; -import { runCmd, deleteEngineCmd, createNS } from 'vault/tests/helpers/commands'; +import { runCmd, deleteEngineCmd, createNS, deleteNS } from 'vault/tests/helpers/commands'; import { DASHBOARD } from 'vault/tests/helpers/components/dashboard/dashboard-selectors'; import { CUSTOM_MESSAGES } from 'vault/tests/helpers/config-ui/message-selectors'; @@ -227,6 +227,8 @@ module('Acceptance | landing page dashboard', function (hooks) { await runCmd(createNS('world'), false); await visit('/vault/dashboard?namespace=world'); assert.dom(DASHBOARD.cardName('configuration-details')).doesNotExist(); + // clean up namespace pollution + await runCmd(deleteNS('world')); }); test('shows the configuration details card', async function (assert) { @@ -450,6 +452,8 @@ module('Acceptance | landing page dashboard', function (hooks) { await runCmd(createNS('blah'), false); await visit('/vault/dashboard?namespace=blah'); assert.dom(DASHBOARD.cardName('replication')).doesNotExist(); + // clean up namespace pollution + await runCmd(deleteNS('blah')); }); test('it should show replication status if both dr and performance replication are enabled as features in enterprise', async function (assert) { diff --git a/ui/tests/acceptance/enterprise-namespaces-test.js b/ui/tests/acceptance/enterprise-namespaces-test.js index 791a6b9441..49c31a5145 100644 --- a/ui/tests/acceptance/enterprise-namespaces-test.js +++ b/ui/tests/acceptance/enterprise-namespaces-test.js @@ -9,7 +9,6 @@ import { visit, fillIn, currentURL, - findAll, triggerKeyEvent, find, waitFor, @@ -20,7 +19,6 @@ import { runCmd, createNSFromPaths, deleteNSFromPaths } from 'vault/tests/helper 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'; module('Acceptance | Enterprise | namespaces', function (hooks) { setupApplicationTest(hooks); @@ -30,10 +28,10 @@ module('Acceptance | Enterprise | namespaces', function (hooks) { }); test('it focuses the search input field when user toggles namespace picker', async function (assert) { - await click(GENERAL.toggleInput('namespace-id')); + await click(GENERAL.toggleInput('namespace-picker')); // Verify that the search input field is focused - const searchInput = find(NAMESPACE_PICKER_SELECTORS.searchInput); + const searchInput = find(GENERAL.inputByAttr('Search namespaces')); assert.strictEqual( document.activeElement, searchInput, @@ -46,19 +44,18 @@ module('Acceptance | Enterprise | namespaces', function (hooks) { const namespaces = ['beep/boop']; await createNSFromPaths(namespaces); - await click(GENERAL.toggleInput('namespace-id')); + await click(GENERAL.toggleInput('namespace-picker')); await click(GENERAL.button('Refresh list')); - assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists'); // Simulate typing into the search input - await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, 'beep/boop'); + await fillIn(GENERAL.inputByAttr('Search namespaces'), 'beep/boop'); assert - .dom(NAMESPACE_PICKER_SELECTORS.searchInput) + .dom(GENERAL.inputByAttr('Search namespaces')) .hasValue('beep/boop', 'The search input field has the correct value'); // Simulate pressing Enter - await triggerKeyEvent(NAMESPACE_PICKER_SELECTORS.searchInput, 'keydown', 'Enter'); + await triggerKeyEvent(GENERAL.inputByAttr('Search namespaces'), 'keydown', 'Enter'); // Verify navigation to the matching namespace assert.strictEqual( @@ -71,143 +68,6 @@ module('Acceptance | Enterprise | namespaces', function (hooks) { await deleteNSFromPaths(namespaces); }); - test('it filters namespaces based on search input', async function (assert) { - // Setup: Create namespace(s) via the CLI - const namespaces = ['beep/boop/bop']; - await createNSFromPaths(namespaces); - - await click(GENERAL.toggleInput('namespace-id')); - await click(GENERAL.button('Refresh list')); - - // Verify all namespaces are displayed initially - assert.dom(NAMESPACE_PICKER_SELECTORS.link()).exists('Namespace link(s) exist'); - const allNamespaces = findAll(NAMESPACE_PICKER_SELECTORS.link()); - - // Verify the search input field exists - assert.dom(NAMESPACE_PICKER_SELECTORS.searchInput).exists('The namespace search field exists'); - - // Verify 3 namespaces are displayed after searching for "beep" - await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, 'beep'); - assert.strictEqual( - findAll(NAMESPACE_PICKER_SELECTORS.link()).length, - 3, - 'Display 3 namespaces matching "beep" after searching' - ); - - // Verify 1 namespace is displayed after searching for "bop" - await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, 'bop'); - assert.strictEqual( - findAll(NAMESPACE_PICKER_SELECTORS.link()).length, - 1, - 'Display 1 namespace matching "bop" after searching' - ); - - // Verify no namespaces are displayed after searching for "other" - await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, 'other'); - assert.strictEqual( - findAll(NAMESPACE_PICKER_SELECTORS.link()).length, - 0, - 'No namespaces are displayed after searching for "other"' - ); - - // Clear the search input & verify all namespaces are displayed again - await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, ''); - assert.strictEqual( - findAll(NAMESPACE_PICKER_SELECTORS.link()).length, - allNamespaces.length, - 'All namespaces are displayed after clearing search input' - ); - - // Cleanup: Delete namespace(s) via the CLI - await deleteNSFromPaths(namespaces); - }); - - test('it updates the namespace list after clicking "Refresh list"', async function (assert) { - // 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 "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')); - - // 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 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( - findAll(NAMESPACE_PICKER_SELECTORS.link('beep')).length, - 1, - '1 namespace is displayed after searching for "beep"' - ); - - // 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) { - // Setup: Create namespace(s) via the CLI - const namespaces = ['beep']; - await createNSFromPaths(namespaces); - - // 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 - assert - .dom('[href="/ui/vault/access/namespaces"]') - .exists('The "Manage" button is displayed with the correct URL'); - - // 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 @@ -218,28 +78,24 @@ module('Acceptance | Enterprise | namespaces', function (hooks) { await login(token); // Open the namespace picker - await click(GENERAL.toggleInput('namespace-id')); + await click(GENERAL.toggleInput('namespace-picker')); // Verify that the root namespace is selected by default - assert.dom(NAMESPACE_PICKER_SELECTORS.link()).hasText('root', 'root renders as current namespace'); + assert.dom(GENERAL.button('root')).hasAttribute('aria-selected', 'true', 'root is selected by default'); assert - .dom(`${NAMESPACE_PICKER_SELECTORS.link()} svg${GENERAL.icon('check')}`) - .exists('The root namespace is selected'); + .dom(`${GENERAL.button('root')} svg${GENERAL.icon('check')}`) + .exists('The root namespace has a check icon indicating it is selected'); // 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'); + assert.dom(GENERAL.button(namespace)).doesNotExist('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'); + await click(GENERAL.toggleInput('namespace-picker')); + assert.dom(GENERAL.button(namespace)).exists('foo should exist in the namespace picker'); // Cleanup: Delete namespace(s) via the CLI await deleteNSFromPaths([namespace]); @@ -251,7 +107,7 @@ module('Acceptance | Enterprise | namespaces', function (hooks) { const namespaces = ['beep/boop/bop']; await createNSFromPaths(namespaces); - await click(GENERAL.toggleInput('namespace-id')); + await click(GENERAL.toggleInput('namespace-picker')); await click(GENERAL.button('Refresh list')); // Login with a namespace prefixed with / @@ -259,27 +115,16 @@ module('Acceptance | Enterprise | namespaces', function (hooks) { await settled(); assert - .dom(GENERAL.toggleInput('namespace-id')) + .dom(GENERAL.toggleInput('namespace-picker')) .hasText('boop', `shows the namespace 'boop' in the toggle component`); - // Open the namespace picker & wait for it to render - await click(GENERAL.toggleInput('namespace-id')); - assert.dom(`svg${GENERAL.icon('check')}`).exists('The check icon is rendered'); + // Open the namespace picker + await click(GENERAL.toggleInput('namespace-picker')); // Find the selected element with the check icon & ensure it exists - const checkIcon = find(`${NAMESPACE_PICKER_SELECTORS.link()} ${GENERAL.icon('check')}`); - assert.dom(checkIcon).exists('A selected namespace link with the check icon exists'); - - // Get the selected namespace with the data-test-namespace-link attribute & ensure it exists - const selectedNamespace = checkIcon?.closest(NAMESPACE_PICKER_SELECTORS.link()); - assert.dom(selectedNamespace).exists('The selected namespace link exists'); - - // Verify that the selected namespace has the correct data-test-namespace-link attribute and path value - assert.strictEqual( - selectedNamespace.getAttribute('data-test-namespace-link'), - 'beep/boop', - 'The current namespace does not begin or end with /' - ); + assert + .dom(`${GENERAL.button('beep/boop')} ${GENERAL.icon('check')}`) + .exists('The selected namespace link exists with the check icon'); // Cleanup: Delete namespace(s) via the CLI await deleteNSFromPaths(namespaces); @@ -290,8 +135,8 @@ module('Acceptance | Enterprise | namespaces', function (hooks) { await logout(); assert.strictEqual(currentURL(), '/vault/auth', 'Does not redirect'); assert.dom(AUTH_FORM.managedNsRoot).doesNotExist('Managed namespace indicator does not exist'); - assert.dom('input[name="namespace"]').hasAttribute('placeholder', '/ (root)'); - await fillIn('input[name="namespace"]', '/foo/bar '); + assert.dom(GENERAL.inputByAttr('namespace')).hasAttribute('placeholder', '/ (root)'); + await fillIn(GENERAL.inputByAttr('namespace'), '/foo/bar '); const encodedNamespace = encodeURIComponent('foo/bar'); assert.strictEqual( currentURL(), @@ -308,50 +153,38 @@ module('Acceptance | Enterprise | namespaces', function (hooks) { await visit('/vault/access/namespaces'); // Verify that the namespace exists in the namespace picker - await click(GENERAL.toggleInput('namespace-id')); + await click(GENERAL.toggleInput('namespace-picker')); await click(GENERAL.button('Refresh list')); - await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, namespace); + await fillIn(GENERAL.inputByAttr('Search namespaces'), namespace); - assert - .dom(NAMESPACE_PICKER_SELECTORS.link(namespace)) - .exists({ count: 1 }, 'Namespace exists in the namespace picker'); + assert.dom(GENERAL.button(namespace)).exists('Namespace exists in the namespace picker'); // Close the namespace picker - await click(GENERAL.toggleInput('namespace-id')); + await click(GENERAL.toggleInput('namespace-picker')); // 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); + await click(GENERAL.button('Search')); // 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', 'Confirm namespace deletion button is shown'); + await click(GENERAL.menuTrigger); + await click(GENERAL.menuItem('delete')); 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=${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 no longer exists on the namespace page + assert.dom(GENERAL.emptyStateTitle).hasText('No namespaces yet', 'Namespace deletion successful'); // Verify that the namespace does not exist in the namespace picker - await click(GENERAL.toggleInput('namespace-id')); + await click(GENERAL.toggleInput('namespace-picker')); await waitFor(GENERAL.button('Refresh list')); await click(GENERAL.button('Refresh list')); - await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, namespace); + await fillIn(GENERAL.inputByAttr('Search namespaces'), namespace); assert - .dom(NAMESPACE_PICKER_SELECTORS.link()) - .exists({ count: 0 }, 'Deleted namespace does not exist in the namespace picker'); + .dom(GENERAL.button(namespace)) + .doesNotExist('Deleted namespace does not exist in the namespace picker'); }); }); diff --git a/ui/tests/acceptance/secrets/backend/kubernetes/roles-test.js b/ui/tests/acceptance/secrets/backend/kubernetes/roles-test.js index 70da8c7280..9697360711 100644 --- a/ui/tests/acceptance/secrets/backend/kubernetes/roles-test.js +++ b/ui/tests/acceptance/secrets/backend/kubernetes/roles-test.js @@ -32,7 +32,7 @@ module('Acceptance | kubernetes | roles', function (hooks) { await this.visitRoles(); assert.dom('[data-test-list-item-link]').exists({ count: 3 }, 'Roles list renders'); await fillIn(GENERAL.filterInputExplicit, '1'); - await click(GENERAL.filterInputExplicitSearch); + await click(GENERAL.button('Search')); assert.dom('[data-test-list-item-link]').exists({ count: 1 }, 'Filtered roles list renders'); assert.ok(currentURL().includes('pageFilter=1'), 'pageFilter query param value is set'); }); diff --git a/ui/tests/acceptance/sync/secrets/destination-test.js b/ui/tests/acceptance/sync/secrets/destination-test.js index 2ad5aebffc..d071159942 100644 --- a/ui/tests/acceptance/sync/secrets/destination-test.js +++ b/ui/tests/acceptance/sync/secrets/destination-test.js @@ -41,7 +41,7 @@ module('Acceptance | sync | destination (singular)', function (hooks) { test('it should transition to correct routes when performing actions', async function (assert) { await click(ts.navLink('Secrets Sync')); await click(GENERAL.tab('Destinations')); - await click(ts.listItem); + await click(GENERAL.listItemLink); assert.dom(GENERAL.tab('Secrets')).hasClass('active', 'Secrets hdsTab is active'); await click(GENERAL.tab('Details')); diff --git a/ui/tests/acceptance/sync/secrets/destinations-test.js b/ui/tests/acceptance/sync/secrets/destinations-test.js index 0914c94ef9..c7a1ba9913 100644 --- a/ui/tests/acceptance/sync/secrets/destinations-test.js +++ b/ui/tests/acceptance/sync/secrets/destinations-test.js @@ -85,15 +85,15 @@ module('Acceptance | sync | destinations (plural)', function (hooks) { test('it should filter destinations list', async function (assert) { await visit('vault/sync/secrets/destinations'); - assert.dom(ts.listItem).exists({ count: 6 }, 'All destinations render'); + assert.dom(GENERAL.listItemLink).exists({ count: 6 }, 'All destinations render'); await click(`${ts.filter('type')} .ember-basic-dropdown-trigger`); await click(ts.searchSelect.option()); - assert.dom(ts.listItem).exists({ count: 2 }, 'Destinations are filtered by type'); + assert.dom(GENERAL.listItemLink).exists({ count: 2 }, 'Destinations are filtered by type'); await fillIn(ts.filter('name'), 'new'); - assert.dom(ts.listItem).exists({ count: 1 }, 'Destinations are filtered by type and name'); + assert.dom(GENERAL.listItemLink).exists({ count: 1 }, 'Destinations are filtered by type and name'); await click(ts.searchSelect.removeSelected); await fillIn(ts.filter('name'), 'gcp'); - assert.dom(ts.listItem).exists({ count: 1 }, 'Destinations are filtered by name'); + assert.dom(GENERAL.listItemLink).exists({ count: 1 }, 'Destinations are filtered by name'); }); test('it should transition to correct routes when performing actions', async function (assert) { diff --git a/ui/tests/helpers/config-ui/message-selectors.ts b/ui/tests/helpers/config-ui/message-selectors.ts index 739fee07bf..4b6b55592e 100644 --- a/ui/tests/helpers/config-ui/message-selectors.ts +++ b/ui/tests/helpers/config-ui/message-selectors.ts @@ -21,5 +21,4 @@ export const CUSTOM_MESSAGES = { alertAction: (name: string) => `[data-test-custom-alert-action="${name}"]`, badge: (name: string) => `[data-test-badge="${name}"]`, tab: (name: string) => `[data-test-custom-messages-tab="${name}"]`, - listItem: (name: string) => `[data-test-list-item="${name}"]`, }; diff --git a/ui/tests/helpers/general-selectors.ts b/ui/tests/helpers/general-selectors.ts index 198597634c..7045ada04b 100644 --- a/ui/tests/helpers/general-selectors.ts +++ b/ui/tests/helpers/general-selectors.ts @@ -30,12 +30,13 @@ export const GENERAL = { // there should only be one save button per view (e.g. one per form) so this does not need to be dynamic // this button should be used for any kind of "submit" on a form or "save" action. submitButton: '[data-test-submit]', - button: (label: string) => `[data-test-button="${label}"]`, + button: (label: string) => (label ? `[data-test-button="${label}"]` : '[data-test-button]'), /* ────── Menus & Lists ────── */ menuTrigger: '[data-test-popup-menu-trigger]', menuItem: (name: string) => `[data-test-popup-menu="${name}"]`, - listItem: '[data-test-list-item-link]', + listItem: (label: string) => `[data-test-list-item="${label}"]`, + listItemLink: '[data-test-list-item-link]', linkedBlock: (item: string) => `[data-test-linked-block="${item}"]`, /* ────── Inputs / Form Fields ────── */ @@ -57,7 +58,6 @@ export const GENERAL = { inputSearch: (attr: string) => `[data-test-input-search="${attr}"]`, filterInput: '[data-test-filter-input]', filterInputExplicit: '[data-test-filter-input-explicit]', - filterInputExplicitSearch: '[data-test-filter-input-explicit-search]', labelById: (id: string) => `label[id="${id}"]`, labelByGroupControlIndex: (index: number) => `.hds-form-group__control-field:nth-of-type(${index}) label`, radioByAttr: (attr: string) => `[data-test-radio="${attr}"]`, diff --git a/ui/tests/helpers/namespace-picker.js b/ui/tests/helpers/namespace-picker.js deleted file mode 100644 index bc41ed376e..0000000000 --- a/ui/tests/helpers/namespace-picker.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -export const NAMESPACE_PICKER_SELECTORS = { - link: (link) => (link ? `[data-test-namespace-link="${link}"]` : '[data-test-namespace-link]'), - searchInput: 'input[type="search"]', -}; diff --git a/ui/tests/integration/components/config-ui/messages/page/list-test.js b/ui/tests/integration/components/config-ui/messages/page/list-test.js index 8e08606f4c..3af1673b5d 100644 --- a/ui/tests/integration/components/config-ui/messages/page/list-test.js +++ b/ui/tests/integration/components/config-ui/messages/page/list-test.js @@ -97,8 +97,7 @@ module('Integration | Component | messages/page/list', function (hooks) { await this.renderComponent(); assert.dom('[data-test-icon="message-circle"]').exists(); for (const message of this.messages) { - assert.dom(CUSTOM_MESSAGES.listItem('Message title 1')).exists(); - assert.dom(`[data-linked-block-title="${message.id}"]`).hasText(message.title); + assert.dom(GENERAL.listItem(message.title)).exists('Message title is displayed'); } }); diff --git a/ui/tests/integration/components/filter-input-explicit-test.js b/ui/tests/integration/components/filter-input-explicit-test.js index 3dff54736c..2eaf01db7c 100644 --- a/ui/tests/integration/components/filter-input-explicit-test.js +++ b/ui/tests/integration/components/filter-input-explicit-test.js @@ -46,7 +46,7 @@ module('Integration | Component | filter-input-explicit', function (hooks) { test('it should call handleSearch on submit', async function (assert) { await this.renderComponent(); await typeIn(GENERAL.filterInputExplicit, 'bar'); - await click(GENERAL.filterInputExplicitSearch); + await click(GENERAL.button('Search')); assert.ok(this.handleSearch.calledOnce, 'handleSearch was called once'); }); diff --git a/ui/tests/integration/components/namespace-picker-test.js b/ui/tests/integration/components/namespace-picker-test.js index ba0798e016..b1e4056c28 100644 --- a/ui/tests/integration/components/namespace-picker-test.js +++ b/ui/tests/integration/components/namespace-picker-test.js @@ -5,11 +5,10 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; -import { render, fillIn, findAll, waitFor, click, find } from '@ember/test-helpers'; +import { render, fillIn, findAll, click, find } from '@ember/test-helpers'; import sinon from 'sinon'; import hbs from 'htmlbars-inline-precompile'; import Service from '@ember/service'; -import { NAMESPACE_PICKER_SELECTORS } from 'vault/tests/helpers/namespace-picker'; import { GENERAL } from 'vault/tests/helpers/general-selectors'; class AuthService extends Service { @@ -51,6 +50,8 @@ function getMockCapabilitiesModel(canList) { }; } +const INITIALIZED_NAMESPACES = ['root', 'parent1', 'parent1/child1']; + module('Integration | Component | namespace-picker', function (hooks) { setupRenderingTest(hooks); @@ -62,10 +63,10 @@ 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(GENERAL.toggleInput('namespace-id')); + await click(GENERAL.toggleInput('namespace-picker')); // Verify that the search input field is focused - const searchInput = find(NAMESPACE_PICKER_SELECTORS.searchInput); + const searchInput = find(GENERAL.inputByAttr('Search namespaces')); assert.strictEqual( document.activeElement, searchInput, @@ -75,39 +76,34 @@ module('Integration | Component | namespace-picker', function (hooks) { test('it filters namespace options based on search input', async function (assert) { await render(hbs``); - await click(GENERAL.toggleInput('namespace-id')); - - // Verify all namespaces are displayed initially - await waitFor(NAMESPACE_PICKER_SELECTORS.link()); - assert.strictEqual( - findAll(NAMESPACE_PICKER_SELECTORS.link()).length, - 3, - 'All namespaces are displayed initially' - ); + await click(GENERAL.toggleInput('namespace-picker')); + // Verify all namespaces are displayed initially which are pre-populated in the NamespaceService + for (const namespace of INITIALIZED_NAMESPACES) { + assert.dom(GENERAL.button(namespace)).exists(`Namespace "${namespace}" is displayed initially`); + } // Simulate typing into the search input - await waitFor(NAMESPACE_PICKER_SELECTORS.searchInput); - await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, 'child1'); + await fillIn(GENERAL.inputByAttr('Search namespaces'), 'child1'); // Verify that only namespaces matching the search input are displayed assert.strictEqual( - findAll(NAMESPACE_PICKER_SELECTORS.link()).length, + findAll(GENERAL.inputByAttr('Search namespaces')).length, 1, 'Only matching namespaces are displayed after filtering' ); // Clear the search input - await fillIn(NAMESPACE_PICKER_SELECTORS.searchInput, ''); + await fillIn(GENERAL.inputByAttr('Search namespaces'), ''); // Verify all namespaces are displayed after clearing the search input assert.strictEqual( - findAll(NAMESPACE_PICKER_SELECTORS.link()).length, + findAll(GENERAL.button()).length, 3, 'All namespaces are displayed after clearing the search input' ); }); - test('it shows both action buttons when canList is true', async function (assert) { + test('it shows both "Manage" and "Refresh list" action buttons when canList is true', async function (assert) { const storeStub = this.owner.lookup('service:store'); sinon.stub(storeStub, 'findRecord').callsFake((modelType, id) => { if (modelType === 'capabilities' && id === 'sys/namespaces/') { @@ -117,7 +113,7 @@ module('Integration | Component | namespace-picker', function (hooks) { }); await render(hbs``); - await click(GENERAL.toggleInput('namespace-id')); + await click(GENERAL.toggleInput('namespace-picker')); // Verify that the "Refresh List" button is visible assert.dom(GENERAL.button('Refresh list')).exists('Refresh List button is visible'); @@ -134,7 +130,7 @@ module('Integration | Component | namespace-picker', function (hooks) { }); await render(hbs``); - await click(GENERAL.toggleInput('namespace-id')); + await click(GENERAL.toggleInput('namespace-picker')); // Verify that the buttons are hidden assert.dom(GENERAL.button('Refresh list')).doesNotExist('Refresh List button is hidden'); @@ -148,7 +144,7 @@ module('Integration | Component | namespace-picker', function (hooks) { }); await render(hbs``); - await click(GENERAL.toggleInput('namespace-id')); + await click(GENERAL.toggleInput('namespace-picker')); // Verify that the buttons are hidden assert.dom(GENERAL.button('Refresh list')).doesNotExist('Refresh List button is hidden'); @@ -167,7 +163,7 @@ module('Integration | Component | namespace-picker', function (hooks) { }); await render(hbs``); - await click(GENERAL.toggleInput('namespace-id')); + await click(GENERAL.toggleInput('namespace-picker')); // Dynamically modify the `findNamespacesForUser.perform` method for this test const namespaceService = this.owner.lookup('service:namespace'); @@ -183,25 +179,19 @@ module('Integration | Component | namespace-picker', function (hooks) { }); // Verify initial namespaces are displayed - assert.strictEqual( - findAll(NAMESPACE_PICKER_SELECTORS.link()).length, - 3, - 'Initially, three namespaces are displayed' - ); + assert.dom(GENERAL.button('parent1')).exists('Namespace "parent1" is displayed'); + assert.dom(GENERAL.button('parent1/child1')).exists('Namespace "parent1/child1" is displayed'); + assert.dom(GENERAL.button('root')).exists('Namespace "root" is displayed'); + assert + .dom(GENERAL.button('new-namespace')) + .doesNotExist('Namespace "new-namespace" is not displayed initially'); // Click the "Refresh list" button await click(GENERAL.button('Refresh list')); // Verify the new namespace is displayed - assert.strictEqual( - findAll(NAMESPACE_PICKER_SELECTORS.link()).length, - 4, - 'After refreshing, four namespaces are displayed' - ); - - // Verify the new namespace is specifically shown assert - .dom(NAMESPACE_PICKER_SELECTORS.link('new-namespace')) - .exists('The new namespace "new-namespace" is displayed after refreshing'); + .dom(GENERAL.button('new-namespace')) + .exists('Namespace "new-namespace" is displayed after refreshing'); }); }); diff --git a/ui/tests/integration/components/sidebar/frame-test.js b/ui/tests/integration/components/sidebar/frame-test.js index caf9fab0a6..49066ff2ce 100644 --- a/ui/tests/integration/components/sidebar/frame-test.js +++ b/ui/tests/integration/components/sidebar/frame-test.js @@ -89,6 +89,6 @@ module('Integration | Component | sidebar-frame', function (hooks) { `); - assert.dom(GENERAL.toggleInput('namespace-id')).exists('Namespace picker renders in sidebar footer'); + assert.dom(GENERAL.toggleInput('namespace-picker')).exists('Namespace picker renders in sidebar footer'); }); });