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 <shannon.roberts@hashicorp.com>
This commit is contained in:
Angel Garbarino 2025-06-23 09:12:52 -06:00 committed by GitHub
parent fe668f9bbc
commit 6cc4eae735
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 226 additions and 599 deletions

View File

@ -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 @@
</D.Header>
{{#if this.showNoNamespacesMessage}}
<D.Generic class="sub-text is-marginless" {{did-insert this.adjustElementWidth}}>
<D.Generic class="sub-text is-marginless" {{did-insert this.adjustElementWidth}} data-test-help-text="no namespaces">
{{this.noNamespacesMessage}}
</D.Generic>
{{/if}}
@ -60,7 +61,7 @@
<D.Checkmark
@selected={{eq option.id this.selectedNamespace.id}}
{{on "click" (fn this.onChange option)}}
data-test-namespace-link={{option.path}}
data-test-button={{option.label}}
>
<span class="is-fullwidth is-word-break right-padding-4">{{option.label}}</span>
</D.Checkmark>

View File

@ -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;
}

View File

@ -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) {

View File

@ -12,7 +12,7 @@
</p.top>
<p.levelLeft>
<h1 class="title is-3">
<h1 class="title is-3" data-test-page-title>
Namespaces
</h1>
</p.levelLeft>
@ -51,7 +51,6 @@
as |list|
>
{{#if this.model.namespaces.length}}
<ListItem as |Item|>
<Item.content>
{{list.item.id}}
@ -68,7 +67,11 @@
to namespace</dd.Interactive>
{{/if}}
{{/let}}
<dd.Interactive @color="critical" {{on "click" (fn (mut this.nsToDelete) list.item)}}>Delete</dd.Interactive>
<dd.Interactive
@color="critical"
{{on "click" (fn (mut this.nsToDelete) list.item)}}
data-test-popup-menu="delete"
>Delete</dd.Interactive>
</Hds::Dropdown>
{{#if (eq this.nsToDelete list.item)}}
<ConfirmModal

View File

@ -92,7 +92,7 @@
<div class="level is-mobile">
<div class="level-left">
<div>
<Hds::Text::Display @tag="h2" data-linked-block-title={{message.id}}>
<Hds::Text::Display @tag="h2">
<Icon @name="message-circle" class="auto-width" aria-label="message" />
{{message.title}}
</Hds::Text::Display>

View File

@ -14,6 +14,6 @@
{{on "keydown" @handleKeyDown}}
data-test-filter-input-explicit
/>
<S.Button @color="secondary" @text="Search" @icon="search" type="submit" data-test-filter-input-explicit-search />
<S.Button @color="secondary" @text="Search" @icon="search" type="submit" data-test-button="Search" />
</Hds::SegmentedGroup>
</form>

View File

@ -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);
});
});

View File

@ -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) {

View File

@ -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');
});
});

View File

@ -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'));
});
});

View File

@ -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) {

View File

@ -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');
});
});

View File

@ -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');
});

View File

@ -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'));

View File

@ -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) {

View File

@ -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}"]`,
};

View File

@ -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}"]`,

View File

@ -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"]',
};

View File

@ -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');
}
});

View File

@ -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');
});

View File

@ -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`<NamespacePicker />`);
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`<NamespacePicker/>`);
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`<NamespacePicker />`);
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`<NamespacePicker />`);
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`<NamespacePicker />`);
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`<NamespacePicker />`);
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');
});
});

View File

@ -89,6 +89,6 @@ module('Integration | Component | sidebar-frame', function (hooks) {
<Sidebar::Frame @showSidebar={{true}} />
`);
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');
});
});