import { GlSkeletonLoading, GlPagination } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import { TEST_HOST } from 'helpers/test_constants'; import IssuableItem from '~/issuable_list/components/issuable_item.vue'; import IssuableListRoot from '~/issuable_list/components/issuable_list_root.vue'; import IssuableTabs from '~/issuable_list/components/issuable_tabs.vue'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; import { mockIssuableListProps, mockIssuables } from '../mock_data'; const createComponent = ({ props = mockIssuableListProps, data = {} } = {}) => mount(IssuableListRoot, { propsData: props, data() { return data; }, slots: { 'nav-actions': ` `, 'empty-state': `

Issuable empty state

`, }, }); describe('IssuableListRoot', () => { let wrapper; beforeEach(() => { wrapper = createComponent(); }); afterEach(() => { wrapper.destroy(); }); describe('computed', () => { const mockCheckedIssuables = { [mockIssuables[0].iid]: { checked: true, issuable: mockIssuables[0] }, [mockIssuables[1].iid]: { checked: true, issuable: mockIssuables[1] }, [mockIssuables[2].iid]: { checked: true, issuable: mockIssuables[2] }, }; const mIssuables = [mockIssuables[0], mockIssuables[1], mockIssuables[2]]; describe('skeletonItemCount', () => { it.each` totalItems | defaultPageSize | currentPage | returnValue ${100} | ${20} | ${1} | ${20} ${105} | ${20} | ${6} | ${5} ${7} | ${20} | ${1} | ${7} ${0} | ${20} | ${1} | ${5} `( 'returns $returnValue when totalItems is $totalItems, defaultPageSize is $defaultPageSize and currentPage is $currentPage', async ({ totalItems, defaultPageSize, currentPage, returnValue }) => { wrapper.setProps({ totalItems, defaultPageSize, currentPage, }); await wrapper.vm.$nextTick(); expect(wrapper.vm.skeletonItemCount).toBe(returnValue); }, ); }); describe('allIssuablesChecked', () => { it.each` checkedIssuables | issuables | specTitle | returnValue ${mockCheckedIssuables} | ${mIssuables} | ${'same as'} | ${true} ${{}} | ${mIssuables} | ${'not same as'} | ${false} `( 'returns $returnValue when bulkEditIssuables count is $specTitle issuables count', async ({ checkedIssuables, issuables, returnValue }) => { wrapper.setProps({ issuables, }); await wrapper.vm.$nextTick(); wrapper.setData({ checkedIssuables, }); await wrapper.vm.$nextTick(); expect(wrapper.vm.allIssuablesChecked).toBe(returnValue); }, ); }); describe('bulkEditIssuables', () => { it('returns array of issuables which have `checked` set to true within checkedIssuables map', async () => { wrapper.setData({ checkedIssuables: mockCheckedIssuables, }); await wrapper.vm.$nextTick(); expect(wrapper.vm.bulkEditIssuables).toHaveLength(mIssuables.length); }); }); }); describe('watch', () => { describe('issuables', () => { it('populates `checkedIssuables` prop with all issuables', async () => { wrapper.setProps({ issuables: [mockIssuables[0]], }); await wrapper.vm.$nextTick(); expect(Object.keys(wrapper.vm.checkedIssuables)).toHaveLength(1); expect(wrapper.vm.checkedIssuables[mockIssuables[0].iid]).toEqual({ checked: false, issuable: mockIssuables[0], }); }); }); describe('urlParams', () => { it('updates window URL reflecting props within `urlParams`', async () => { const urlParams = { state: 'closed', sort: 'updated_asc', page: 1, search: 'foo', }; wrapper.setProps({ urlParams, }); await wrapper.vm.$nextTick(); expect(global.window.location.href).toBe( `${TEST_HOST}/?state=${urlParams.state}&sort=${urlParams.sort}&page=${urlParams.page}&search=${urlParams.search}`, ); }); }); }); describe('methods', () => { describe('issuableId', () => { it('returns id value from provided issuable object', () => { expect(wrapper.vm.issuableId({ id: 1 })).toBe(1); expect(wrapper.vm.issuableId({ iid: 1 })).toBe(1); expect(wrapper.vm.issuableId({})).toBeDefined(); }); }); describe('issuableChecked', () => { it('returns boolean value representing checked status of issuable item', async () => { wrapper.setData({ checkedIssuables: { [mockIssuables[0].iid]: { checked: true, issuable: mockIssuables[0] }, }, }); await wrapper.vm.$nextTick(); expect(wrapper.vm.issuableChecked(mockIssuables[0])).toBe(true); }); }); }); describe('template', () => { it('renders component container element with class "issuable-list-container"', () => { expect(wrapper.classes()).toContain('issuable-list-container'); }); it('renders issuable-tabs component', () => { const tabsEl = wrapper.find(IssuableTabs); expect(tabsEl.exists()).toBe(true); expect(tabsEl.props()).toMatchObject({ tabs: wrapper.vm.tabs, tabCounts: wrapper.vm.tabCounts, currentTab: wrapper.vm.currentTab, }); }); it('renders contents for slot "nav-actions" within issuable-tab component', () => { const buttonEl = wrapper.find(IssuableTabs).find('button.js-new-issuable'); expect(buttonEl.exists()).toBe(true); expect(buttonEl.text()).toBe('New issuable'); }); it('renders filtered-search-bar component', () => { const searchEl = wrapper.find(FilteredSearchBar); const { namespace, recentSearchesStorageKey, searchInputPlaceholder, searchTokens, sortOptions, initialFilterValue, initialSortBy, } = wrapper.vm; expect(searchEl.exists()).toBe(true); expect(searchEl.props()).toMatchObject({ namespace, recentSearchesStorageKey, searchInputPlaceholder, tokens: searchTokens, sortOptions, initialFilterValue, initialSortBy, }); }); it('renders gl-loading-icon when `issuablesLoading` prop is true', async () => { wrapper.setProps({ issuablesLoading: true, }); await wrapper.vm.$nextTick(); expect(wrapper.findAll(GlSkeletonLoading)).toHaveLength(wrapper.vm.skeletonItemCount); }); it('renders issuable-item component for each item within `issuables` array', () => { const itemsEl = wrapper.findAll(IssuableItem); const mockIssuable = mockIssuableListProps.issuables[0]; expect(itemsEl).toHaveLength(mockIssuableListProps.issuables.length); expect(itemsEl.at(0).props()).toMatchObject({ issuableSymbol: wrapper.vm.issuableSymbol, issuable: mockIssuable, }); }); it('renders contents for slot "empty-state" when `issuablesLoading` is false and `issuables` is empty', async () => { wrapper.setProps({ issuables: [], }); await wrapper.vm.$nextTick(); expect(wrapper.find('p.js-issuable-empty-state').exists()).toBe(true); expect(wrapper.find('p.js-issuable-empty-state').text()).toBe('Issuable empty state'); }); it('renders gl-pagination when `showPaginationControls` prop is true', async () => { wrapper.setProps({ showPaginationControls: true, totalItems: 10, }); await wrapper.vm.$nextTick(); const paginationEl = wrapper.find(GlPagination); expect(paginationEl.exists()).toBe(true); expect(paginationEl.props()).toMatchObject({ perPage: 20, value: 1, prevPage: 0, nextPage: 2, totalItems: 10, align: 'center', }); }); }); describe('events', () => { let wrapperChecked; beforeEach(() => { wrapperChecked = createComponent({ data: { checkedIssuables: { [mockIssuables[0].iid]: { checked: true, issuable: mockIssuables[0] }, }, }, }); }); afterEach(() => { wrapperChecked.destroy(); }); it('issuable-tabs component emits `click-tab` event on `click-tab` event', () => { wrapper.find(IssuableTabs).vm.$emit('click'); expect(wrapper.emitted('click-tab')).toBeTruthy(); }); it('sets all issuables as checked when filtered-search-bar component emits `checked-input` event', async () => { const searchEl = wrapperChecked.find(FilteredSearchBar); searchEl.vm.$emit('checked-input', true); await wrapperChecked.vm.$nextTick(); expect(searchEl.emitted('checked-input')).toBeTruthy(); expect(searchEl.emitted('checked-input').length).toBe(1); expect(wrapperChecked.vm.checkedIssuables[mockIssuables[0].iid]).toEqual({ checked: true, issuable: mockIssuables[0], }); }); it('filtered-search-bar component emits `filter` event on `onFilter` & `sort` event on `onSort` events', () => { const searchEl = wrapper.find(FilteredSearchBar); searchEl.vm.$emit('onFilter'); expect(wrapper.emitted('filter')).toBeTruthy(); searchEl.vm.$emit('onSort'); expect(wrapper.emitted('sort')).toBeTruthy(); }); it('sets an issuable as checked when issuable-item component emits `checked-input` event', async () => { const issuableItem = wrapperChecked.findAll(IssuableItem).at(0); issuableItem.vm.$emit('checked-input', true); await wrapperChecked.vm.$nextTick(); expect(issuableItem.emitted('checked-input')).toBeTruthy(); expect(issuableItem.emitted('checked-input').length).toBe(1); expect(wrapperChecked.vm.checkedIssuables[mockIssuables[0].iid]).toEqual({ checked: true, issuable: mockIssuables[0], }); }); it('gl-pagination component emits `page-change` event on `input` event', async () => { wrapper.setProps({ showPaginationControls: true, }); await wrapper.vm.$nextTick(); wrapper.find(GlPagination).vm.$emit('input'); expect(wrapper.emitted('page-change')).toBeTruthy(); }); }); });