diff --git a/ee/app/assets/javascripts/related_items_tree/components/tree_root.vue b/ee/app/assets/javascripts/related_items_tree/components/tree_root.vue index 0b57296ccd381c3f8f976a35b20cd747e64738d5..dd7bb9460c90a47bf8b413f8fa6e68ee2f2b1765 100644 --- a/ee/app/assets/javascripts/related_items_tree/components/tree_root.vue +++ b/ee/app/assets/javascripts/related_items_tree/components/tree_root.vue @@ -91,6 +91,7 @@ export default { v-bind="treeRootOptions" class="list-unstyled related-items-list tree-root gl-pb-0 gl-pt-0" :move="onMove" + data-testid="tree-root" @start="handleDragOnStart" @end="handleDragOnEnd" > diff --git a/ee/spec/frontend/related_items_tree/components/tree_root_spec.js b/ee/spec/frontend/related_items_tree/components/tree_root_spec.js index b634dd7c43ff6c3c7a67677ac9d1c2a447d1637c..28e9769cad4d964f80829c16d8aa3343e930a021 100644 --- a/ee/spec/frontend/related_items_tree/components/tree_root_spec.js +++ b/ee/spec/frontend/related_items_tree/components/tree_root_spec.js @@ -1,15 +1,16 @@ import { GlButton } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import Draggable from 'vuedraggable'; // eslint-disable-next-line no-restricted-imports import Vuex from 'vuex'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { ESC_KEY_CODE } from '~/lib/utils/keycodes'; import { DRAG_DELAY } from '~/sortable/constants'; import TreeRoot from 'ee/related_items_tree/components/tree_root.vue'; import { treeItemChevronBtnClassName } from 'ee/related_items_tree/constants'; import createDefaultStore from 'ee/related_items_tree/store'; import * as epicUtils from 'ee/related_items_tree/utils/epic_utils'; +import { gqClient } from 'ee/related_items_tree/utils/epic_utils'; import { mockQueryResponse, mockInitialConfig, @@ -21,51 +22,54 @@ import { const { epic } = mockQueryResponse.data.group; Vue.use(Vuex); -let store; - -const createComponent = ({ - parentItem = mockParentItem, - epicPageInfo = epic.children.pageInfo, - issuesPageInfo = epic.issues.pageInfo, -} = {}) => { - store = createDefaultStore(); - const children = epicUtils.processQueryResponse(mockQueryResponse.data.group); - - store.dispatch('setInitialParentItem', mockParentItem); - store.dispatch('setInitialConfig', mockInitialConfig); - store.dispatch('setItemChildrenFlags', { - isSubItem: false, - children, - }); - store.dispatch('setEpicPageInfo', { - parentItem, - pageInfo: epicPageInfo, - }); +describe('RelatedItemsTree', () => { + let wrapper; + let store; + + const createComponent = ({ + parentItem = mockParentItem, + epicPageInfo = epic.children.pageInfo, + issuesPageInfo = epic.issues.pageInfo, + } = {}) => { + store = createDefaultStore(); + const children = epicUtils.processQueryResponse(mockQueryResponse.data.group); + + store.dispatch('setInitialParentItem', mockParentItem); + store.dispatch('setInitialConfig', mockInitialConfig); + store.dispatch('setItemChildrenFlags', { + isSubItem: false, + children, + }); - store.dispatch('setIssuePageInfo', { - parentItem, - pageInfo: issuesPageInfo, - }); + store.dispatch('setEpicPageInfo', { + parentItem, + pageInfo: epicPageInfo, + }); - return shallowMount(TreeRoot, { - store, - stubs: { - 'tree-item': true, - }, - propsData: { + store.dispatch('setIssuePageInfo', { parentItem, - children, - }, - }); -}; + pageInfo: issuesPageInfo, + }); -describe('RelatedItemsTree', () => { - describe('TreeRoot', () => { - let wrapper; + wrapper = shallowMountExtended(TreeRoot, { + store, + stubs: { + 'tree-item': true, + }, + propsData: { + parentItem, + children, + }, + }); + }; + const findTreeRoot = () => wrapper.findByTestId('tree-root'); + const findMoreChildren = () => findTreeRoot().find('li'); + + describe('TreeRoot', () => { beforeEach(() => { - wrapper = createComponent(); + createComponent(); }); describe('mixins', () => { @@ -87,46 +91,48 @@ describe('RelatedItemsTree', () => { describe('computed', () => { describe('treeRootWrapper', () => { it('should return Draggable reference when userSignedIn prop is true', () => { - expect(wrapper.vm.treeRootWrapper).toBe(Draggable); + expect(findTreeRoot().element.tagName).toBe('DRAGGABLE-STUB'); }); - it('should return string "ul" when userSignedIn prop is false', () => { - store.dispatch('setInitialConfig', { + it('should return string "ul" when userSignedIn prop is false', async () => { + await store.dispatch('setInitialConfig', { ...mockInitialConfig, userSignedIn: false, }); - expect(wrapper.vm.treeRootWrapper).toBe('ul'); + expect(findTreeRoot().element.tagName).toBe('UL'); }); }); describe('treeRootOptions', () => { it('should return object containing Vue.Draggable config extended from `defaultSortableOptions` when userSignedIn prop is true', () => { - expect(wrapper.vm.treeRootOptions).toEqual( + expect(findTreeRoot().attributes()).toEqual( expect.objectContaining({ - animation: 200, - forceFallback: true, - fallbackClass: 'is-dragging', - fallbackOnBody: false, - ghostClass: 'is-ghost', + animation: '200', + delay: '100', + forcefallback: 'true', + fallbackclass: 'is-dragging', + ghostclass: 'is-ghost', group: 'gl-new-card-body', tag: 'ul', - 'ghost-class': 'tree-item-drag-active', 'data-parent-reference': mockParentItem.reference, 'data-parent-id': mockParentItem.id, - value: wrapper.vm.children, filter: `.${treeItemChevronBtnClassName}`, }), ); }); - it('should return an empty object when userSignedIn prop is false', () => { - store.dispatch('setInitialConfig', { + it('should return an empty object when userSignedIn prop is false', async () => { + await store.dispatch('setInitialConfig', { ...mockInitialConfig, userSignedIn: false, }); - expect(wrapper.vm.treeRootOptions).toEqual(expect.objectContaining({})); + expect(Object.keys(findTreeRoot().attributes())).toEqual([ + 'move', + 'data-testid', + 'class', + ]); }); }); }); @@ -374,49 +380,7 @@ describe('RelatedItemsTree', () => { }); }); - describe('computed', () => { - describe('hasMoreChildren', () => { - it('returns `true` when either `hasMoreEpics` or `hasMoreIssues` is true', () => { - expect(wrapper.vm.hasMoreChildren).toBe(true); - }); - - it('returns `false` when both `hasMoreEpics` and `hasMoreIssues` is false', () => { - const wrapperNoMoreChild = createComponent({ - epicPageInfo: { - hasNextPage: false, - endCursor: 'abc', - }, - issuesPageInfo: { - hasNextPage: false, - endCursor: 'def', - }, - }); - - expect(wrapperNoMoreChild.vm.hasMoreChildren).toBe(false); - - wrapperNoMoreChild.destroy(); - }); - }); - }); - describe('methods', () => { - describe('handleShowMoreClick', () => { - it('sets `fetchInProgress` to true and calls `fetchNextPageItems` action with parentItem as param', () => { - jest - .spyOn(wrapper.vm, 'fetchNextPageItems') - .mockImplementation(() => new Promise(() => {})); - - wrapper.vm.handleShowMoreClick(); - - expect(wrapper.vm.fetchInProgress).toBe(true); - expect(wrapper.vm.fetchNextPageItems).toHaveBeenCalledWith( - expect.objectContaining({ - parentItem: mockParentItem, - }), - ); - }); - }); - describe('onMove', () => { let mockEvt; let mockOriginalEvt; @@ -482,6 +446,27 @@ describe('RelatedItemsTree', () => { }); describe('template', () => { + describe('Children', () => { + it('displays children when either when either `hasMoreEpics` or `hasMoreIssues` are true', () => { + expect(findMoreChildren().exists()).toBe(true); + }); + + it('does not display children when both `hasMoreEpics` and `hasMoreIssues` are false', () => { + createComponent({ + epicPageInfo: { + hasNextPage: false, + endCursor: 'abc', + }, + issuesPageInfo: { + hasNextPage: false, + endCursor: 'def', + }, + }); + + expect(findMoreChildren().exists()).toBe(false); + }); + }); + it('renders tree item component', () => { expect(wrapper.html()).toContain('tree-item-stub'); }); @@ -490,12 +475,22 @@ describe('RelatedItemsTree', () => { expect(wrapper.findComponent(GlButton).text()).toBe('Show more'); }); - it('calls `handleShowMoreClick` when `Show more` link is clicked', () => { - jest.spyOn(wrapper.vm, 'handleShowMoreClick').mockImplementation(() => {}); - - wrapper.findComponent(GlButton).vm.$emit('click'); - - expect(wrapper.vm.handleShowMoreClick).toHaveBeenCalled(); + it('fetches more when `Show more` link is clicked and disables the button', async () => { + jest.spyOn(gqClient, 'query').mockResolvedValue({ data: {} }); + await wrapper.findComponent(GlButton).vm.$emit('click'); + expect(gqClient.query).toHaveBeenCalled(); + expect(wrapper.findComponent(GlButton).exists()).toBe(false); + const { fullPath, iid } = mockParentItem; + expect(gqClient.query).toHaveBeenCalledWith( + expect.objectContaining({ + variables: { + epicEndCursor: 'abc', + fullPath, + iid, + issueEndCursor: 'def', + }, + }), + ); }); }); });