feat: 🎸 add search capability for host sources (#2861)
* feat: 🎸 add search capability for host sources ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17094 * refactor: 💡 use Rose::Pagination component ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17094 * refactor: 💡 move target detail body to index route to fix whole page reload issue ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17094 * Add missing copyright headers * refactor: 💡 PR feedback ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17094 --------- Co-authored-by: cameronperera <29386339+cameronperera@users.noreply.github.com>
This commit is contained in:
1 parent
738fa1f15f
commit
57e614e8db
9 files changed
+394
-173
No files matched your search
@@ -55,9 +55,8 @@ test.afterEach(async ({ request }) => {
|
||||
}
|
||||
});
|
||||
|
||||
test.describe('Target with many hosts', () => {
|
||||
// TODO: Add tests for searching host sources
|
||||
test('should display all hosts in the target', async ({ authedPage }) => {
|
||||
test.describe('Search and Pagination', () => {
|
||||
test('target details', async ({ authedPage }) => {
|
||||
await authedPage
|
||||
.getByRole('link', { name: targetWithManyHosts.name })
|
||||
.click();
|
||||
@@ -76,7 +75,7 @@ test.describe('Target with many hosts', () => {
|
||||
// Navigate to the second page. The last host should now be visible.
|
||||
await authedPage
|
||||
.getByRole('navigation', { name: 'pagination' })
|
||||
.getByRole('button', { name: 'page 2' })
|
||||
.getByRole('link', { name: 'page 2' })
|
||||
.click();
|
||||
await expect(
|
||||
authedPage.getByRole('cell', { name: hosts[0].name, exact: true }),
|
||||
@@ -91,7 +90,7 @@ test.describe('Target with many hosts', () => {
|
||||
// Use the "previous page" button to navigate back to the first page.
|
||||
await authedPage
|
||||
.getByRole('navigation', { name: 'pagination' })
|
||||
.getByRole('button', { name: 'Previous page' })
|
||||
.getByRole('link', { name: 'Previous page' })
|
||||
.click();
|
||||
await expect(
|
||||
authedPage.getByRole('cell', { name: hosts[0].name, exact: true }),
|
||||
@@ -106,7 +105,7 @@ test.describe('Target with many hosts', () => {
|
||||
// Use the "next page" button to navigate to the second page again.
|
||||
await authedPage
|
||||
.getByRole('navigation', { name: 'pagination' })
|
||||
.getByRole('button', { name: 'Next page' })
|
||||
.getByRole('link', { name: 'Next page' })
|
||||
.click();
|
||||
await expect(
|
||||
authedPage.getByRole('cell', { name: hosts[0].name, exact: true }),
|
||||
@@ -137,6 +136,52 @@ test.describe('Target with many hosts', () => {
|
||||
.getByRole('row')
|
||||
.filter({ hasNot: authedPage.getByRole('columnheader') }),
|
||||
).toHaveCount(10);
|
||||
|
||||
// Search for a specific host
|
||||
const host = hosts[12];
|
||||
await authedPage.getByRole('searchbox', { name: 'Search' }).fill(host.name);
|
||||
|
||||
await expect(
|
||||
authedPage
|
||||
.getByRole('row')
|
||||
.filter({ hasNot: authedPage.getByRole('columnheader') }),
|
||||
).toHaveCount(1);
|
||||
|
||||
// Clear the search box
|
||||
await authedPage.getByRole('searchbox', { name: 'Search' }).fill('');
|
||||
|
||||
await expect(
|
||||
authedPage
|
||||
.getByRole('row')
|
||||
.filter({ hasNot: authedPage.getByRole('columnheader') }),
|
||||
).toHaveCount(10);
|
||||
await expect(authedPage.getByText('1–10 of 15')).toBeVisible();
|
||||
|
||||
// Search with query that matches multiple hosts
|
||||
await authedPage.getByRole('searchbox', { name: 'Search' }).fill('Host 1');
|
||||
await expect(
|
||||
authedPage.getByRole('cell', {
|
||||
name: hosts[0].name,
|
||||
exact: true,
|
||||
}),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
authedPage.getByRole('cell', {
|
||||
name: hosts[10].name,
|
||||
exact: true,
|
||||
}),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
authedPage
|
||||
.getByRole('row')
|
||||
.filter({ hasNot: authedPage.getByRole('columnheader') }),
|
||||
).toHaveCount(7);
|
||||
|
||||
// Search for a host that does not exist
|
||||
await authedPage
|
||||
.getByRole('searchbox', { name: 'Search' })
|
||||
.fill('Nonexistent Host');
|
||||
await expect(authedPage.getByRole('table')).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -20,21 +20,10 @@ export default class ScopesScopeProjectsTargetsTargetController extends Controll
|
||||
|
||||
queryParams = [{ isConnecting: { type: 'boolean' } }];
|
||||
|
||||
@tracked page = 1;
|
||||
@tracked pageSize = 10;
|
||||
@tracked totalItems = this.model.hosts.length;
|
||||
|
||||
@tracked isConnecting = false;
|
||||
isConnectionError = false;
|
||||
|
||||
// =methods
|
||||
|
||||
get paginatedHosts() {
|
||||
return this.model.hosts.slice(
|
||||
(this.page - 1) * this.pageSize,
|
||||
this.page * this.pageSize,
|
||||
);
|
||||
}
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Connect method that calls parent connect method and handles
|
||||
@@ -59,15 +48,4 @@ export default class ScopesScopeProjectsTargetsTargetController extends Controll
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
async handlePageChange(page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
@action
|
||||
async handlePageSizeChange(pageSize) {
|
||||
this.page = 1;
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import { debounce } from 'core/decorators/debounce';
|
||||
|
||||
export default class ScopesScopeProjectsTargetsTargetIndexController extends Controller {
|
||||
// standard naming for importing a controller would be 'target' but
|
||||
// this is a special case where using 'target' conflicts with the
|
||||
// target route, so we use 'targetController' instead
|
||||
@controller('scopes/scope/projects/targets/target') targetController;
|
||||
|
||||
// =attributes
|
||||
|
||||
queryParams = ['page', 'pageSize'];
|
||||
|
||||
@tracked search;
|
||||
@tracked page = 1;
|
||||
@tracked pageSize = 10;
|
||||
|
||||
// =methods
|
||||
|
||||
get paginatedHosts() {
|
||||
return this.hosts.slice(
|
||||
(this.page - 1) * this.pageSize,
|
||||
this.page * this.pageSize,
|
||||
);
|
||||
}
|
||||
|
||||
get hosts() {
|
||||
let filteredHosts = this.model.hosts;
|
||||
if (this.search) {
|
||||
filteredHosts = this.model.hosts.filter((host) => {
|
||||
const searchTerm = this.search?.toLowerCase() ?? '';
|
||||
return (
|
||||
host.displayName.toLowerCase().includes(searchTerm) ||
|
||||
host.description?.toLowerCase().includes(searchTerm) ||
|
||||
host.address.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return filteredHosts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the search query is empty
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get noResults() {
|
||||
return this.model.hosts.length > 0 && this.hosts.length === 0;
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Handles input on each keystroke and the search queryParam
|
||||
* @param {object} event
|
||||
*/
|
||||
@action
|
||||
@debounce(250)
|
||||
handleSearchInput(event) {
|
||||
const { value } = event.target;
|
||||
this.search = value;
|
||||
this.page = 1;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,14 @@ export default class ScopesScopeProjectsTargetsTargetRoute extends Route {
|
||||
|
||||
@service store;
|
||||
|
||||
// =attributes
|
||||
queryParams = {
|
||||
isConnecting: {
|
||||
type: 'boolean',
|
||||
refreshModel: true,
|
||||
},
|
||||
};
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,12 +4,28 @@
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeProjectsTargetsTargetIndexRoute extends Route {
|
||||
// =services
|
||||
// =attributes
|
||||
|
||||
@service router;
|
||||
queryParams = {
|
||||
page: {
|
||||
refreshModel: true,
|
||||
},
|
||||
pageSize: {
|
||||
refreshModel: true,
|
||||
},
|
||||
};
|
||||
|
||||
// =methods
|
||||
model() {
|
||||
return this.modelFor('scopes.scope.projects.targets.target');
|
||||
}
|
||||
|
||||
resetController(controller, isExiting) {
|
||||
if (isExiting) {
|
||||
controller.set('search', '');
|
||||
controller.set('page', 1);
|
||||
controller.set('pageSize', 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,143 +32,7 @@
|
||||
</page.header>
|
||||
|
||||
<page.body class='details-screen-body'>
|
||||
<Rose::Layout::BodyContent as |bc|>
|
||||
<bc.Body class='target-details-body'>
|
||||
{{#if (gt @model.hosts.length 1)}}
|
||||
<Hds::Table
|
||||
@model={{this.paginatedHosts}}
|
||||
@columns={{array
|
||||
(hash label=(t 'resources.target.host-source.title_plural'))
|
||||
(hash label=(t 'form.address.label'))
|
||||
(hash
|
||||
label=(t 'titles.actions') isVisuallyHidden=true align='right'
|
||||
)
|
||||
}}
|
||||
@valign='middle'
|
||||
>
|
||||
<:body as |B|>
|
||||
<B.Tr>
|
||||
<B.Td>
|
||||
<Hds::Text::Body @weight='semibold' @tag='p'>
|
||||
{{B.data.displayName}}
|
||||
</Hds::Text::Body>
|
||||
<Hds::Text::Body @tag='p'>
|
||||
{{B.data.description}}
|
||||
</Hds::Text::Body>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<Hds::Text::Body @tag='p'>
|
||||
{{B.data.address}}
|
||||
</Hds::Text::Body>
|
||||
</B.Td>
|
||||
|
||||
<B.Td>
|
||||
<Hds::Button
|
||||
data-test-target-connect-button={{B.data.id}}
|
||||
@text={{t 'resources.session.actions.connect'}}
|
||||
@color='secondary'
|
||||
{{on 'click' (fn this.connect @model.target B.data)}}
|
||||
/>
|
||||
</B.Td>
|
||||
|
||||
</B.Tr>
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
<Hds::Pagination::Numbered
|
||||
data-test-pagination
|
||||
@totalItems={{this.totalItems}}
|
||||
@currentPage={{this.page}}
|
||||
@currentPageSize={{this.pageSize}}
|
||||
@onPageChange={{this.handlePageChange}}
|
||||
@onPageSizeChange={{this.handlePageSizeChange}}
|
||||
/>
|
||||
{{else}}
|
||||
<Hds::ApplicationState as |A|>
|
||||
<A.Header
|
||||
@title={{t 'resources.target.messages.connect-more-info.title'}}
|
||||
/>
|
||||
<A.Body
|
||||
@text={{t
|
||||
'resources.target.messages.connect-more-info.description'
|
||||
}}
|
||||
/>
|
||||
</Hds::ApplicationState>
|
||||
{{/if}}
|
||||
</bc.Body>
|
||||
<bc.Sidebar>
|
||||
{{#if @model.target.aliases}}
|
||||
<div class='details-sidebar aliases'>
|
||||
<div
|
||||
class='title hds-typography-display-200 hds-font-weight-semibold'
|
||||
>
|
||||
{{t 'resources.alias.details'}}
|
||||
</div>
|
||||
<Rose::MetadataList @orientation='vertical' as |list|>
|
||||
{{#each @model.target.aliases as |alias|}}
|
||||
<list.Item @color='faint'>
|
||||
<Hds::Copy::Snippet
|
||||
@textToCopy={{alias.value}}
|
||||
@color='secondary'
|
||||
/>
|
||||
</list.Item>
|
||||
|
||||
{{/each}}
|
||||
|
||||
</Rose::MetadataList>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class='details-sidebar'>
|
||||
<div>
|
||||
<div
|
||||
class='title hds-typography-display-200 hds-font-weight-semibold'
|
||||
>
|
||||
{{t 'resources.target.details'}}
|
||||
</div>
|
||||
<Rose::MetadataList @orientation='vertical' as |list|>
|
||||
{{! Target name }}
|
||||
<list.Item @icon='target' @color='faint'>
|
||||
{{@model.target.name}}
|
||||
</list.Item>
|
||||
{{! Project name }}
|
||||
<list.Item @icon='grid' @color='faint'>
|
||||
{{@model.target.project.displayName}}
|
||||
</list.Item>
|
||||
{{! Target description }}
|
||||
{{#if @model.target.description}}
|
||||
<list.Item @icon='message-circle-fill' @color='faint'>
|
||||
{{@model.target.description}}
|
||||
</list.Item>
|
||||
{{/if}}
|
||||
{{! Target type }}
|
||||
<list.Item class='resource-type' @icon='terminal' @color='faint'>
|
||||
{{#if @model.target.isSSH}}
|
||||
{{t 'resources.target.types.ssh'}}
|
||||
{{else}}
|
||||
{{t 'resources.target.types.tcp'}}
|
||||
{{/if}}
|
||||
</list.Item>
|
||||
{{! Target address }}
|
||||
{{#if @model.target.address}}
|
||||
<list.Item
|
||||
class='resource-type'
|
||||
@icon='network-alt'
|
||||
@color='faint'
|
||||
>
|
||||
{{@model.target.address}}
|
||||
</list.Item>
|
||||
{{/if}}
|
||||
{{! Target ID }}
|
||||
<list.Item @icon='hash' @color='faint'>
|
||||
<Hds::Copy::Snippet
|
||||
@textToCopy={{@model.target.id}}
|
||||
@color='secondary'
|
||||
/>
|
||||
</list.Item>
|
||||
</Rose::MetadataList>
|
||||
</div>
|
||||
</div>
|
||||
</bc.Sidebar>
|
||||
</Rose::Layout::BodyContent>
|
||||
{{outlet}}
|
||||
</page.body>
|
||||
|
||||
</Rose::Layout::Page>
|
||||
@@ -3,4 +3,157 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
{{outlet}}
|
||||
<Rose::Layout::BodyContent as |bc|>
|
||||
<bc.Body class='search-filtering'>
|
||||
{{#if (gt @model.hosts.length 1)}}
|
||||
<Hds::SegmentedGroup as |S|>
|
||||
<S.TextInput
|
||||
@value={{this.search}}
|
||||
@type='search'
|
||||
placeholder={{t 'actions.search'}}
|
||||
aria-label={{t 'actions.search'}}
|
||||
{{on 'input' this.handleSearchInput}}
|
||||
/>
|
||||
</Hds::SegmentedGroup>
|
||||
{{#if this.paginatedHosts.length}}
|
||||
<Hds::Table
|
||||
@model={{this.paginatedHosts}}
|
||||
@columns={{array
|
||||
(hash label=(t 'resources.target.host-source.title_plural'))
|
||||
(hash label=(t 'form.address.label'))
|
||||
(hash
|
||||
label=(t 'titles.actions') isVisuallyHidden=true align='right'
|
||||
)
|
||||
}}
|
||||
@valign='middle'
|
||||
>
|
||||
<:body as |B|>
|
||||
<B.Tr>
|
||||
<B.Td>
|
||||
<Hds::Text::Body @weight='semibold' @tag='p'>
|
||||
{{B.data.displayName}}
|
||||
</Hds::Text::Body>
|
||||
<Hds::Text::Body @tag='p'>
|
||||
{{B.data.description}}
|
||||
</Hds::Text::Body>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<Hds::Text::Body @tag='p'>
|
||||
{{B.data.address}}
|
||||
</Hds::Text::Body>
|
||||
</B.Td>
|
||||
|
||||
<B.Td>
|
||||
<Hds::Button
|
||||
data-test-target-connect-button={{B.data.id}}
|
||||
@text={{t 'resources.session.actions.connect'}}
|
||||
@color='secondary'
|
||||
{{on
|
||||
'click'
|
||||
(fn this.targetController.connect @model.target B.data)
|
||||
}}
|
||||
/>
|
||||
</B.Td>
|
||||
|
||||
</B.Tr>
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
<Rose::Pagination
|
||||
@totalItems={{this.hosts.length}}
|
||||
@currentPage={{this.page}}
|
||||
@currentPageSize={{this.pageSize}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if this.noResults}}
|
||||
<Hds::ApplicationState data-test-no-host-source-results as |A|>
|
||||
<A.Header @title={{t 'titles.no-results-found'}} />
|
||||
<A.Body
|
||||
@text={{t
|
||||
(if
|
||||
this.search
|
||||
'descriptions.no-search-results'
|
||||
'descriptions.no-filter-results'
|
||||
)
|
||||
query=this.search
|
||||
resource=(t 'resources.target.host-source.title_plural')
|
||||
}}
|
||||
/>
|
||||
</Hds::ApplicationState>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<Hds::ApplicationState as |A|>
|
||||
<A.Header
|
||||
@title={{t 'resources.target.messages.connect-more-info.title'}}
|
||||
/>
|
||||
<A.Body
|
||||
@text={{t 'resources.target.messages.connect-more-info.description'}}
|
||||
/>
|
||||
</Hds::ApplicationState>
|
||||
{{/if}}
|
||||
</bc.Body>
|
||||
<bc.Sidebar>
|
||||
{{#if @model.target.aliases}}
|
||||
<div class='details-sidebar aliases'>
|
||||
<div class='title hds-typography-display-200 hds-font-weight-semibold'>
|
||||
{{t 'resources.alias.details'}}
|
||||
</div>
|
||||
<Rose::MetadataList @orientation='vertical' as |list|>
|
||||
{{#each @model.target.aliases as |alias|}}
|
||||
<list.Item @color='faint'>
|
||||
<Hds::Copy::Snippet
|
||||
@textToCopy={{alias.value}}
|
||||
@color='secondary'
|
||||
/>
|
||||
</list.Item>
|
||||
|
||||
{{/each}}
|
||||
|
||||
</Rose::MetadataList>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class='details-sidebar'>
|
||||
<div>
|
||||
<div class='title hds-typography-display-200 hds-font-weight-semibold'>
|
||||
{{t 'resources.target.details'}}
|
||||
</div>
|
||||
<Rose::MetadataList @orientation='vertical' as |list|>
|
||||
{{! Target name }}
|
||||
<list.Item @icon='target' @color='faint'>
|
||||
{{@model.target.name}}
|
||||
</list.Item>
|
||||
{{! Project name }}
|
||||
<list.Item @icon='grid' @color='faint'>
|
||||
{{@model.target.project.displayName}}
|
||||
</list.Item>
|
||||
{{! Target description }}
|
||||
{{#if @model.target.description}}
|
||||
<list.Item @icon='message-circle-fill' @color='faint'>
|
||||
{{@model.target.description}}
|
||||
</list.Item>
|
||||
{{/if}}
|
||||
{{! Target type }}
|
||||
<list.Item class='resource-type' @icon='terminal' @color='faint'>
|
||||
{{#if @model.target.isSSH}}
|
||||
{{t 'resources.target.types.ssh'}}
|
||||
{{else}}
|
||||
{{t 'resources.target.types.tcp'}}
|
||||
{{/if}}
|
||||
</list.Item>
|
||||
{{! Target address }}
|
||||
{{#if @model.target.address}}
|
||||
<list.Item class='resource-type' @icon='network-alt' @color='faint'>
|
||||
{{@model.target.address}}
|
||||
</list.Item>
|
||||
{{/if}}
|
||||
{{! Target ID }}
|
||||
<list.Item @icon='hash' @color='faint'>
|
||||
<Hds::Copy::Snippet
|
||||
@textToCopy={{@model.target.id}}
|
||||
@color='secondary'
|
||||
/>
|
||||
</list.Item>
|
||||
</Rose::MetadataList>
|
||||
</div>
|
||||
</div>
|
||||
</bc.Sidebar>
|
||||
</Rose::Layout::BodyContent>
|
||||
@@ -4,7 +4,15 @@
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { visit, currentURL, click, find, select } from '@ember/test-helpers';
|
||||
import {
|
||||
visit,
|
||||
currentURL,
|
||||
click,
|
||||
find,
|
||||
select,
|
||||
fillIn,
|
||||
waitFor,
|
||||
} from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
@@ -30,6 +38,10 @@ module('Acceptance | projects | targets | target', function (hooks) {
|
||||
const HDS_DIALOG_CANCEL_BUTTON =
|
||||
'.hds-modal__footer .hds-button--color-secondary';
|
||||
const TABLE_ROWS = 'tbody tr';
|
||||
const FIRST_ROW = 'tbody tr:first-child';
|
||||
const SECOND_ROW = 'tbody tr:nth-child(2)';
|
||||
const SEARCH_INPUT = 'input[type="search"]';
|
||||
const NO_RESULTS_MSG = '[data-test-no-host-source-results]';
|
||||
|
||||
const instances = {
|
||||
scopes: {
|
||||
@@ -240,6 +252,58 @@ module('Acceptance | projects | targets | target', function (hooks) {
|
||||
assert.dom(APP_STATE_TITLE).hasText('Connected');
|
||||
});
|
||||
|
||||
test('user can search for a host source by name, description, and address', async function (assert) {
|
||||
const host1 =
|
||||
instances.targetWithTwoHosts.hostSets.models[0].hosts.models[0];
|
||||
const host2 =
|
||||
instances.targetWithTwoHosts.hostSets.models[0].hosts.models[1];
|
||||
await visit(urls.targets);
|
||||
await click(`[href="${urls.targetWithTwoHosts}"]`);
|
||||
|
||||
assert.dom(TABLE_ROWS).exists({ count: 2 });
|
||||
assert.dom(FIRST_ROW).includesText(host1.name);
|
||||
assert.dom(SECOND_ROW).includesText(host2.name);
|
||||
|
||||
await fillIn(SEARCH_INPUT, host2.name);
|
||||
await waitFor(SECOND_ROW, { count: 0 });
|
||||
|
||||
assert.dom(TABLE_ROWS).exists({ count: 1 });
|
||||
assert.dom(FIRST_ROW).includesText(host2.name);
|
||||
assert.dom(SECOND_ROW).doesNotExist();
|
||||
|
||||
await fillIn(SEARCH_INPUT, '');
|
||||
await waitFor(TABLE_ROWS, { count: 2 });
|
||||
|
||||
await fillIn(SEARCH_INPUT, host1.description);
|
||||
await waitFor(SECOND_ROW, { count: 0 });
|
||||
|
||||
assert.dom(TABLE_ROWS).exists({ count: 1 });
|
||||
assert.dom(FIRST_ROW).includesText(host1.description);
|
||||
assert.dom(SECOND_ROW).doesNotExist();
|
||||
|
||||
await fillIn(SEARCH_INPUT, '');
|
||||
await waitFor(TABLE_ROWS, { count: 2 });
|
||||
|
||||
await fillIn(SEARCH_INPUT, host2.attributes.address);
|
||||
await waitFor(SECOND_ROW, { count: 0 });
|
||||
|
||||
assert.dom(TABLE_ROWS).exists({ count: 1 });
|
||||
assert.dom(FIRST_ROW).includesText(host2.attributes.address);
|
||||
assert.dom(SECOND_ROW).doesNotExist();
|
||||
});
|
||||
|
||||
test('user can search for host sources and get no results', async function (assert) {
|
||||
await visit(urls.targets);
|
||||
await click(`[href="${urls.targetWithTwoHosts}"]`);
|
||||
|
||||
assert.dom(TABLE_ROWS).exists({ count: 2 });
|
||||
|
||||
await fillIn(SEARCH_INPUT, 'non-existing-host');
|
||||
await waitFor(TABLE_ROWS, { count: 0 });
|
||||
|
||||
assert.dom(NO_RESULTS_MSG).includesText('No results found');
|
||||
});
|
||||
|
||||
test('user can see host source table when visiting a target via resource link', async function (assert) {
|
||||
const targetId = instances.targetWithTwoHosts.id;
|
||||
|
||||
@@ -310,7 +374,7 @@ module('Acceptance | projects | targets | target', function (hooks) {
|
||||
assert.dom(TABLE_ROWS).exists({ count: 10 });
|
||||
assert.dom('[data-test-pagination]').isVisible();
|
||||
|
||||
await click('button[aria-label="Next page"]');
|
||||
await click('a[aria-label="Next page"]');
|
||||
|
||||
assert.dom(TABLE_ROWS).exists({ count: 5 });
|
||||
});
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'desktop/tests/helpers';
|
||||
|
||||
module(
|
||||
'Unit | Controller | scopes/scope/projects/targets/target/index',
|
||||
function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let controller = this.owner.lookup(
|
||||
'controller:scopes/scope/projects/targets/target/index',
|
||||
);
|
||||
assert.ok(controller);
|
||||
});
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user