/** * Copyright (c) HashiCorp, Inc. * SPDX-License-Identifier: BUSL-1.1 */ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { click, fillIn, findAll, render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { GENERAL } from 'vault/tests/helpers/general-selectors'; import { CLIENT_COUNT } from 'vault/tests/helpers/clients/client-count-selectors'; const MOCK_DATA = [ { island: 'Maldives', visit_length: 5, is_booked: false, trip_date: '2025-06-22T00:00:00.000Z' }, { island: 'Bora Bora', visit_length: 7, is_booked: true, trip_date: '2025-03-15T00:00:00.000Z' }, { island: 'Fiji', visit_length: 10, is_booked: true, trip_date: '2025-09-08T00:00:00.000Z' }, { island: 'Seychelles', visit_length: 6, is_booked: false, trip_date: '2025-12-03T00:00:00.000Z' }, { island: 'Maui', visit_length: 8, is_booked: true, trip_date: '2026-01-18T00:00:00.000Z' }, ]; module('Integration | Component | clients/table', function (hooks) { setupRenderingTest(hooks); hooks.beforeEach(async function () { this.data = undefined; this.columns = [ { key: 'island', label: 'Islands', isSortable: true }, { key: 'visit_length', label: 'Visit length', isSortable: true }, { key: 'is_booked', label: 'Vacation booking status', isSortable: true }, { key: 'trip_date', label: 'Date trip starts', isSortable: true }, ]; this; this.initiallySortBy = undefined; this.setPageSize = undefined; this.showPaginationSizeSelector = undefined; // helper function to setup table with X number of simple objects for pagination tests this.mockMoreData = (recordNumber) => { const record = (i) => ({ id: i, name: `record-${i}` }); this.columns = [ { key: 'id', label: 'ID', isSortable: true }, { key: 'name', label: 'Name', isSortable: true }, ]; this.data = Array.from({ length: recordNumber }, (_, i) => record(i)); }; this.renderComponent = async () => { return render(hbs` `); }; }); test('it renders default empty state when no data exists', async function (assert) { await this.renderComponent(); assert.dom(CLIENT_COUNT.card('table empty state')).hasText('No data to display'); }); test('it renders yielded empty state block when no data exists', async function (assert) { await render(hbs` <:emptyState>Oh no, there's no data! `); assert.dom(CLIENT_COUNT.card('table empty state')).hasText("Oh no, there's no data!"); }); test('it renders and paginates data', async function (assert) { this.data = MOCK_DATA; await this.renderComponent(); assert.dom(CLIENT_COUNT.card('table empty state')).doesNotExist(); assert.dom(GENERAL.paginationInfo).hasText(`1–3 of ${this.data.length}`); await click(GENERAL.nextPage); assert.dom(GENERAL.tableData(0, 'island')).hasText('Seychelles', 'it paginates the data'); }); test('it sorts table data', async function (assert) { this.data = MOCK_DATA; const assertSortOrder = (expectedValues, { column, page }) => { expectedValues.forEach((value, idx) => { assert .dom(GENERAL.tableData(idx, column)) .hasText(value, `page ${page}, row ${idx} has ${column}: ${value}`); }); }; await this.renderComponent(); const [firstColumn, secondColumn, thirdColumn, fourthColumn] = findAll(GENERAL.icon('swap-vertical')); await click(firstColumn); assertSortOrder(['Bora Bora', 'Fiji', 'Maldives'], { column: 'island', page: 1 }); await click(GENERAL.nextPage); assertSortOrder(['Maui', 'Seychelles'], { column: 'island', page: 2 }); await click(GENERAL.prevPage); await click(secondColumn); assertSortOrder(['5', '6', '7'], { column: 'visit_length', page: 1 }); await click(GENERAL.nextPage); assertSortOrder(['8', '10'], { column: 'visit_length', page: 2 }); await click(GENERAL.prevPage); await click(thirdColumn); assertSortOrder(['false', 'false', 'true'], { column: 'is_booked', page: 1 }); await click(GENERAL.nextPage); assertSortOrder(['true', 'true'], { column: 'is_booked', page: 2 }); await click(GENERAL.prevPage); await click(fourthColumn); assertSortOrder(['2025-03-15T00:00:00.000Z', '2025-06-22T00:00:00.000Z', '2025-09-08T00:00:00.000Z'], { column: 'trip_date', page: 1, }); await click(GENERAL.nextPage); assertSortOrder(['2025-12-03T00:00:00.000Z', '2026-01-18T00:00:00.000Z'], { column: 'trip_date', page: 2, }); }); test('it pre-sorts table data if @initiallySortBy is set', async function (assert) { this.data = MOCK_DATA; this.initiallySortBy = { column: 'visit_length', direction: 'desc' }; await this.renderComponent(); assert.dom(GENERAL.tableColumnHeader(2, { isAdvanced: true })).hasAttribute('aria-sort', 'descending'); assert .dom(`${GENERAL.tableColumnHeader(2, { isAdvanced: true })} ${GENERAL.icon('arrow-down')}`) .exists(); const firstPage = ['Fiji', 'Maui', 'Bora Bora']; firstPage.forEach((value, idx) => { assert.dom(GENERAL.tableData(idx, 'island')).hasText(value, `page 1, row ${idx} has ${value}`); }); await click(GENERAL.nextPage); const secondPage = ['Seychelles', 'Maldives']; secondPage.forEach((value, idx) => { assert.dom(GENERAL.tableData(idx, 'island')).hasText(value, `page 2, row ${idx} has ${value}`); }); }); test('it sets page size if @setPageSize has a value', async function (assert) { this.mockMoreData(15); this.setPageSize = 8; // component default for testing is 3, so set to anything but 3 await this.renderComponent(); assert.dom(GENERAL.paginationInfo).hasText(`1–${this.setPageSize} of ${this.data.length}`); let idx = 7; // 8th item, table items are 0-indexed assert .dom(GENERAL.tableData(idx, 'name')) .hasText(this.data[idx].name, 'last row is 8th item in dataset'); await click(GENERAL.nextPage); idx = 8; // 9th item, table items are 0-indexed assert .dom(GENERAL.tableData(0, 'name')) .hasText(this.data[idx].name, 'first row on page 2 is 9th item in dataset'); }); test('it renders size selector if @showPaginationSizeSelector is true', async function (assert) { this.mockMoreData(10); this.setPageSize = 5; this.showPaginationSizeSelector = true; await this.renderComponent(); assert.dom(GENERAL.tableRow()).exists({ count: 5 }, '5 rows render'); assert.dom(GENERAL.paginationInfo).hasText(`1–${this.setPageSize} of ${this.data.length}`); assert.dom(GENERAL.paginationSizeSelector).hasValue('5'); let idx = 4; // rows and ids are 0-indexed assert.dom(GENERAL.tableData(idx, 'id')).hasText(`${idx}`, `last row is ${idx + 1}th item in dataset`); await fillIn(GENERAL.paginationSizeSelector, '10'); idx = 9; assert.dom(GENERAL.tableRow()).exists({ count: 10 }, '10 rows render'); assert.dom(GENERAL.paginationSizeSelector).hasValue('10', 'it updates the size selector to 10'); assert.dom(GENERAL.tableData(idx, 'id')).hasText(`${idx}`, `last row is ${idx + 1}th item in dataset`); }); test('it renders "Deleted" badge for "mount_type" keys if the value is "deleted mount"', async function (assert) { this.columns = [ { key: 'mount_type', label: 'Mount type' }, { key: 'mount_path', label: 'Mount path' }, ]; this.data = [ { mount_type: 'deleted mount', mount_path: 'auth/userpass/' }, { mount_type: 'ns_token', mount_path: 'auth/token/' }, ]; await this.renderComponent(); assert.dom('.hds-badge').exists({ count: 1 }, 'only one badge renders'); assert .dom(`${GENERAL.tableData(0, 'mount_type')} .hds-badge`) .exists('it renders a badge for the deleted mount') .hasText('Deleted'); }); });