summaryrefslogtreecommitdiff
path: root/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-10-21 07:08:36 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-10-21 07:08:36 +0000
commit48aff82709769b098321c738f3444b9bdaa694c6 (patch)
treee00c7c43e2d9b603a5a6af576b1685e400410dee /spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs
parent879f5329ee916a948223f8f43d77fba4da6cd028 (diff)
downloadgitlab-ce-4d844e2fbf8315eaf3fddb9a0b241a909be3ecbf.tar.gz
Add latest changes from gitlab-org/gitlab@13-5-stable-eev13.5.0-rc42
Diffstat (limited to 'spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs')
-rw-r--r--spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/mocks/items.json15
-rw-r--r--spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/mocks/items_filters.json14
-rw-r--r--spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js350
3 files changed, 379 insertions, 0 deletions
diff --git a/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/mocks/items.json b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/mocks/items.json
new file mode 100644
index 00000000000..0d85b2bc68a
--- /dev/null
+++ b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/mocks/items.json
@@ -0,0 +1,15 @@
+[
+ {
+ "iid": "1527542",
+ "title": "SyntaxError: Invalid or unexpected token",
+ "createdAt": "2020-04-17T23:18:14.996Z",
+ "assignees": { "nodes": [] }
+ },
+ {
+ "iid": "1527543",
+ "title": "SyntaxError: Invalid or unexpected token by root",
+ "createdAt": "2020-04-17T23:19:14.996Z",
+ "assignees": { "nodes": [] }
+ }
+ ]
+ \ No newline at end of file
diff --git a/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/mocks/items_filters.json b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/mocks/items_filters.json
new file mode 100644
index 00000000000..b42ec42d8b8
--- /dev/null
+++ b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/mocks/items_filters.json
@@ -0,0 +1,14 @@
+[
+ {
+ "type": "assignee_username",
+ "value": { "data": "root2" }
+ },
+ {
+ "type": "author_username",
+ "value": { "data": "root" }
+ },
+ {
+ "type": "filtered-search-term",
+ "value": { "data": "bar" }
+ }
+ ] \ No newline at end of file
diff --git a/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js
new file mode 100644
index 00000000000..d943aaf3e5f
--- /dev/null
+++ b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js
@@ -0,0 +1,350 @@
+import { mount } from '@vue/test-utils';
+import { GlAlert, GlBadge, GlPagination, GlTabs, GlTab } from '@gitlab/ui';
+import PageWrapper from '~/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue';
+import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
+import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
+import Tracking from '~/tracking';
+import mockItems from './mocks/items.json';
+import mockFilters from './mocks/items_filters.json';
+
+const EmptyStateSlot = {
+ template: '<div class="empty-state">Empty State</div>',
+};
+
+const HeaderActionsSlot = {
+ template: '<div class="header-actions"><button>Action Button</button></div>',
+};
+
+const TitleSlot = {
+ template: '<div>Page Wrapper Title</div>',
+};
+
+const TableSlot = {
+ template: '<table class="gl-table"></table>',
+};
+
+const itemsCount = {
+ opened: 24,
+ closed: 10,
+ all: 34,
+};
+
+const ITEMS_STATUS_TABS = [
+ {
+ title: 'Opened items',
+ status: 'OPENED',
+ filters: ['opened'],
+ },
+ {
+ title: 'Closed items',
+ status: 'CLOSED',
+ filters: ['closed'],
+ },
+ {
+ title: 'All items',
+ status: 'ALL',
+ filters: ['all'],
+ },
+];
+
+describe('AlertManagementEmptyState', () => {
+ let wrapper;
+
+ function mountComponent({ props = {} } = {}) {
+ wrapper = mount(PageWrapper, {
+ provide: {
+ projectPath: '/link',
+ },
+ propsData: {
+ items: [],
+ itemsCount: {},
+ pageInfo: {},
+ statusTabs: [],
+ loading: false,
+ showItems: false,
+ showErrorMsg: false,
+ trackViewsOptions: {},
+ i18n: {},
+ serverErrorMessage: '',
+ filterSearchKey: '',
+ ...props,
+ },
+ slots: {
+ 'emtpy-state': EmptyStateSlot,
+ 'header-actions': HeaderActionsSlot,
+ title: TitleSlot,
+ table: TableSlot,
+ },
+ });
+ }
+
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ const EmptyState = () => wrapper.find('.empty-state');
+ const ItemsTable = () => wrapper.find('.gl-table');
+ const ErrorAlert = () => wrapper.find(GlAlert);
+ const Pagination = () => wrapper.find(GlPagination);
+ const Tabs = () => wrapper.find(GlTabs);
+ const ActionButton = () => wrapper.find('.header-actions > button');
+ const Filters = () => wrapper.find(FilteredSearchBar);
+ const findPagination = () => wrapper.find(GlPagination);
+ const findStatusFilterTabs = () => wrapper.findAll(GlTab);
+ const findStatusTabs = () => wrapper.find(GlTabs);
+ const findStatusFilterBadge = () => wrapper.findAll(GlBadge);
+
+ describe('Snowplow tracking', () => {
+ beforeEach(() => {
+ jest.spyOn(Tracking, 'event');
+ mountComponent({
+ props: { trackViewsOptions: { category: 'category', action: 'action' } },
+ });
+ });
+
+ it('should track the items list page views', () => {
+ const { category, action } = wrapper.vm.trackViewsOptions;
+ expect(Tracking.event).toHaveBeenCalledWith(category, action);
+ });
+ });
+
+ describe('Page wrapper with no items', () => {
+ it('renders the empty state if there are no items present', () => {
+ expect(EmptyState().exists()).toBe(true);
+ });
+ });
+
+ describe('Page wrapper with items', () => {
+ it('renders the tabs selection with valid tabs', () => {
+ mountComponent({
+ props: {
+ statusTabs: [{ status: 'opened', title: 'Open' }, { status: 'closed', title: 'Closed' }],
+ },
+ });
+
+ expect(Tabs().exists()).toBe(true);
+ });
+
+ it('renders the header action buttons if present', () => {
+ expect(ActionButton().exists()).toBe(true);
+ });
+
+ it('renders a error alert if there are errors', () => {
+ mountComponent({
+ props: { showErrorMsg: true },
+ });
+
+ expect(ErrorAlert().exists()).toBe(true);
+ });
+
+ it('renders a table of items if items are present', () => {
+ mountComponent({
+ props: { showItems: true, items: mockItems },
+ });
+
+ expect(ItemsTable().exists()).toBe(true);
+ });
+
+ it('renders pagination if there the pagination info object has a next or previous page', () => {
+ mountComponent({
+ props: { pageInfo: { hasNextPage: true } },
+ });
+
+ expect(Pagination().exists()).toBe(true);
+ });
+
+ it('renders the filter set with the tokens according to the prop filterSearchTokens', () => {
+ mountComponent({
+ props: { filterSearchTokens: ['assignee_username'] },
+ });
+
+ expect(Filters().exists()).toBe(true);
+ });
+ });
+
+ describe('Status Filter Tabs', () => {
+ beforeEach(() => {
+ mountComponent({
+ props: { items: mockItems, itemsCount, statusTabs: ITEMS_STATUS_TABS },
+ });
+ });
+
+ it('should display filter tabs', () => {
+ const tabs = findStatusFilterTabs().wrappers;
+
+ tabs.forEach((tab, i) => {
+ expect(tab.attributes('data-testid')).toContain(ITEMS_STATUS_TABS[i].status);
+ });
+ });
+
+ it('should display filter tabs with items count badge for each status', () => {
+ const tabs = findStatusFilterTabs().wrappers;
+ const badges = findStatusFilterBadge();
+
+ tabs.forEach((tab, i) => {
+ const status = ITEMS_STATUS_TABS[i].status.toLowerCase();
+ expect(tab.attributes('data-testid')).toContain(ITEMS_STATUS_TABS[i].status);
+ expect(badges.at(i).text()).toContain(itemsCount[status]);
+ });
+ });
+ });
+
+ describe('Pagination', () => {
+ beforeEach(() => {
+ mountComponent({
+ props: {
+ items: mockItems,
+ itemsCount,
+ statusTabs: ITEMS_STATUS_TABS,
+ pageInfo: { hasNextPage: true },
+ },
+ });
+ });
+
+ it('should render pagination', () => {
+ expect(wrapper.find(GlPagination).exists()).toBe(true);
+ });
+
+ describe('prevPage', () => {
+ it('returns prevPage button', async () => {
+ findPagination().vm.$emit('input', 3);
+
+ await wrapper.vm.$nextTick();
+ expect(
+ findPagination()
+ .findAll('.page-item')
+ .at(0)
+ .text(),
+ ).toBe('Prev');
+ });
+
+ it('returns prevPage number', async () => {
+ findPagination().vm.$emit('input', 3);
+
+ await wrapper.vm.$nextTick();
+ expect(wrapper.vm.previousPage).toBe(2);
+ });
+
+ it('returns 0 when it is the first page', async () => {
+ findPagination().vm.$emit('input', 1);
+
+ await wrapper.vm.$nextTick();
+ expect(wrapper.vm.previousPage).toBe(0);
+ });
+ });
+
+ describe('nextPage', () => {
+ it('returns nextPage button', async () => {
+ findPagination().vm.$emit('input', 3);
+
+ await wrapper.vm.$nextTick();
+ expect(
+ findPagination()
+ .findAll('.page-item')
+ .at(1)
+ .text(),
+ ).toBe('Next');
+ });
+
+ it('returns nextPage number', async () => {
+ mountComponent({
+ props: {
+ items: mockItems,
+ itemsCount,
+ statusTabs: ITEMS_STATUS_TABS,
+ pageInfo: { hasNextPage: true },
+ },
+ });
+ findPagination().vm.$emit('input', 1);
+
+ await wrapper.vm.$nextTick();
+ expect(wrapper.vm.nextPage).toBe(2);
+ });
+
+ it('returns `null` when currentPage is already last page', async () => {
+ findStatusTabs().vm.$emit('input', 1);
+ findPagination().vm.$emit('input', 1);
+ await wrapper.vm.$nextTick();
+ expect(wrapper.vm.nextPage).toBeNull();
+ });
+ });
+ });
+
+ describe('Filtered search component', () => {
+ beforeEach(() => {
+ mountComponent({
+ props: {
+ items: mockItems,
+ itemsCount,
+ statusTabs: ITEMS_STATUS_TABS,
+ filterSearchKey: 'items',
+ },
+ });
+ });
+
+ it('renders the search component for incidents', () => {
+ expect(Filters().props('searchInputPlaceholder')).toBe('Search or filter results…');
+ expect(Filters().props('tokens')).toEqual([
+ {
+ type: 'author_username',
+ icon: 'user',
+ title: 'Author',
+ unique: true,
+ symbol: '@',
+ token: AuthorToken,
+ operators: [{ value: '=', description: 'is', default: 'true' }],
+ fetchPath: '/link',
+ fetchAuthors: expect.any(Function),
+ },
+ {
+ type: 'assignee_username',
+ icon: 'user',
+ title: 'Assignee',
+ unique: true,
+ symbol: '@',
+ token: AuthorToken,
+ operators: [{ value: '=', description: 'is', default: 'true' }],
+ fetchPath: '/link',
+ fetchAuthors: expect.any(Function),
+ },
+ ]);
+ expect(Filters().props('recentSearchesStorageKey')).toBe('items');
+ });
+
+ it('returns correctly applied filter search values', async () => {
+ const searchTerm = 'foo';
+ wrapper.setData({
+ searchTerm,
+ });
+
+ await wrapper.vm.$nextTick();
+ expect(wrapper.vm.filteredSearchValue).toEqual([searchTerm]);
+ });
+
+ it('updates props tied to getIncidents GraphQL query', () => {
+ wrapper.vm.handleFilterItems(mockFilters);
+
+ expect(wrapper.vm.authorUsername).toBe('root');
+ expect(wrapper.vm.assigneeUsername).toEqual('root2');
+ expect(wrapper.vm.searchTerm).toBe(mockFilters[2].value.data);
+ });
+
+ it('updates props `searchTerm` and `authorUsername` with empty values when passed filters param is empty', () => {
+ wrapper.setData({
+ authorUsername: 'foo',
+ searchTerm: 'bar',
+ });
+
+ wrapper.vm.handleFilterItems([]);
+
+ expect(wrapper.vm.authorUsername).toBe('');
+ expect(wrapper.vm.searchTerm).toBe('');
+ });
+ });
+});