diff options
Diffstat (limited to 'spec/frontend/work_items/components/work_item_links')
3 files changed, 95 insertions, 119 deletions
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_children_wrapper_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_children_wrapper_spec.js index b06be6c8083..cd077fbf705 100644 --- a/spec/frontend/work_items/components/work_item_links/work_item_children_wrapper_spec.js +++ b/spec/frontend/work_items/components/work_item_links/work_item_children_wrapper_spec.js @@ -6,16 +6,28 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import WorkItemChildrenWrapper from '~/work_items/components/work_item_links/work_item_children_wrapper.vue'; import WorkItemLinkChild from '~/work_items/components/work_item_links/work_item_link_child.vue'; +import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql'; import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; -import { childrenWorkItems, workItemByIidResponseFactory } from '../../mock_data'; +import { + changeWorkItemParentMutationResponse, + childrenWorkItems, + updateWorkItemMutationErrorResponse, + workItemByIidResponseFactory, +} from '../../mock_data'; describe('WorkItemChildrenWrapper', () => { let wrapper; + const $toast = { + show: jest.fn(), + }; const getWorkItemQueryHandler = jest.fn().mockResolvedValue(workItemByIidResponseFactory()); + const updateWorkItemMutationHandler = jest + .fn() + .mockResolvedValue(changeWorkItemParentMutationResponse); const findWorkItemLinkChildItems = () => wrapper.findAllComponents(WorkItemLinkChild); @@ -25,18 +37,33 @@ describe('WorkItemChildrenWrapper', () => { workItemType = 'Objective', confidential = false, children = childrenWorkItems, + mutationHandler = updateWorkItemMutationHandler, } = {}) => { + const mockApollo = createMockApollo([ + [workItemByIidQuery, getWorkItemQueryHandler], + [updateWorkItemMutation, mutationHandler], + ]); + + mockApollo.clients.defaultClient.cache.writeQuery({ + query: workItemByIidQuery, + variables: { fullPath: 'test/project', iid: '1' }, + data: workItemByIidResponseFactory().data, + }); + wrapper = shallowMountExtended(WorkItemChildrenWrapper, { - apolloProvider: createMockApollo([[workItemByIidQuery, getWorkItemQueryHandler]]), + apolloProvider: mockApollo, provide: { fullPath: 'test/project', }, propsData: { workItemType, workItemId: 'gid://gitlab/WorkItem/515', + workItemIid: '1', confidential, children, - fetchByIid: true, + }, + mocks: { + $toast, }, }); }; @@ -51,16 +78,6 @@ describe('WorkItemChildrenWrapper', () => { ); }); - it('remove event on child triggers `removeChild` event', () => { - createComponent(); - const workItem = { id: 'gid://gitlab/WorkItem/2' }; - const firstChild = findWorkItemLinkChildItems().at(0); - - firstChild.vm.$emit('removeChild', workItem); - - expect(wrapper.emitted('removeChild')).toEqual([[workItem]]); - }); - it('emits `show-modal` on `click` event', () => { createComponent(); const firstChild = findWorkItemLinkChildItems().at(0); @@ -95,4 +112,47 @@ describe('WorkItemChildrenWrapper', () => { } }, ); + + describe('when removing child work item', () => { + const workItem = { id: 'gid://gitlab/WorkItem/2' }; + + describe('when successful', () => { + beforeEach(async () => { + createComponent(); + findWorkItemLinkChildItems().at(0).vm.$emit('removeChild', workItem); + await waitForPromises(); + }); + + it('calls a mutation to update the work item', () => { + expect(updateWorkItemMutationHandler).toHaveBeenCalledWith({ + input: { + id: workItem.id, + hierarchyWidget: { + parentId: null, + }, + }, + }); + }); + + it('shows a toast', () => { + expect($toast.show).toHaveBeenCalledWith('Child removed', { + action: { onClick: expect.anything(), text: 'Undo' }, + }); + }); + }); + + describe('when not successful', () => { + beforeEach(async () => { + createComponent({ + mutationHandler: jest.fn().mockResolvedValue(updateWorkItemMutationErrorResponse), + }); + findWorkItemLinkChildItems().at(0).vm.$emit('removeChild', workItem); + await waitForPromises(); + }); + + it('emits an error message', () => { + expect(wrapper.emitted('error')).toEqual([['Something went wrong while removing child.']]); + }); + }); + }); }); diff --git a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js index 786f8604039..dd46505bd65 100644 --- a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js +++ b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js @@ -4,7 +4,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import setWindowLocation from 'helpers/set_window_location_helper'; -import { stubComponent } from 'helpers/stub_component'; +import { RENDER_ALL_SLOTS_TEMPLATE, stubComponent } from 'helpers/stub_component'; import issueDetailsQuery from 'ee_else_ce/work_items/graphql/get_issue_details.query.graphql'; import { resolvers } from '~/graphql_shared/issuable_client'; import WidgetWrapper from '~/work_items/components/widget_wrapper.vue'; @@ -13,19 +13,14 @@ import WorkItemChildrenWrapper from '~/work_items/components/work_item_links/wor import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue'; import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue'; import { FORM_TYPES } from '~/work_items/constants'; -import changeWorkItemParentMutation from '~/work_items/graphql/update_work_item.mutation.graphql'; -import workItemQuery from '~/work_items/graphql/work_item.query.graphql'; import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql'; import { getIssueDetailsResponse, workItemHierarchyResponse, workItemHierarchyEmptyResponse, workItemHierarchyNoUpdatePermissionResponse, - changeWorkItemParentMutationResponse, workItemByIidResponseFactory, - workItemQueryResponse, mockWorkItemCommentNote, - childrenWorkItems, } from '../../mock_data'; Vue.use(VueApollo); @@ -36,66 +31,48 @@ describe('WorkItemLinks', () => { let wrapper; let mockApollo; - const WORK_ITEM_ID = 'gid://gitlab/WorkItem/2'; - - const $toast = { - show: jest.fn(), - }; - - const mutationChangeParentHandler = jest - .fn() - .mockResolvedValue(changeWorkItemParentMutationResponse); - const childWorkItemByIidHandler = jest.fn().mockResolvedValue(workItemByIidResponseFactory()); const responseWithAddChildPermission = jest.fn().mockResolvedValue(workItemHierarchyResponse); const responseWithoutAddChildPermission = jest .fn() .mockResolvedValue(workItemByIidResponseFactory({ adminParentLink: false })); const createComponent = async ({ - data = {}, fetchHandler = responseWithAddChildPermission, - mutationHandler = mutationChangeParentHandler, issueDetailsQueryHandler = jest.fn().mockResolvedValue(getIssueDetailsResponse()), hasIterationsFeature = false, } = {}) => { mockApollo = createMockApollo( [ - [workItemQuery, fetchHandler], - [changeWorkItemParentMutation, mutationHandler], + [workItemByIidQuery, fetchHandler], [issueDetailsQuery, issueDetailsQueryHandler], - [workItemByIidQuery, childWorkItemByIidHandler], ], resolvers, { addTypename: true }, ); wrapper = shallowMountExtended(WorkItemLinks, { - data() { - return { - ...data, - }; - }, provide: { fullPath: 'project/path', hasIterationsFeature, reportAbusePath: '/report/abuse/path', }, - propsData: { issuableId: 1 }, - apolloProvider: mockApollo, - mocks: { - $toast, + propsData: { + issuableId: 1, + issuableIid: 1, }, + apolloProvider: mockApollo, stubs: { WorkItemDetailModal: stubComponent(WorkItemDetailModal, { methods: { show: showModal, }, }), + WidgetWrapper: stubComponent(WidgetWrapper, { + template: RENDER_ALL_SLOTS_TEMPLATE, + }), }, }); - wrapper.vm.$refs.wrapper.show = jest.fn(); - await waitForPromises(); }; @@ -122,8 +99,7 @@ describe('WorkItemLinks', () => { `( '$expectedAssertion "Add" button in hierarchy widget header when "userPermissions.adminParentLink" is $value', async ({ workItemFetchHandler, value }) => { - createComponent({ fetchHandler: workItemFetchHandler }); - await waitForPromises(); + await createComponent({ fetchHandler: workItemFetchHandler }); expect(findToggleFormDropdown().exists()).toBe(value); }, @@ -159,24 +135,6 @@ describe('WorkItemLinks', () => { expect(findAddLinksForm().exists()).toBe(false); }); - - it('adds work item child from the form', async () => { - const workItem = { - ...workItemQueryResponse.data.workItem, - id: 'gid://gitlab/WorkItem/11', - }; - await createComponent(); - findToggleFormDropdown().vm.$emit('click'); - findToggleCreateFormButton().vm.$emit('click'); - await nextTick(); - - expect(findWorkItemLinkChildrenWrapper().props().children).toHaveLength(4); - - findAddLinksForm().vm.$emit('addWorkItemChild', workItem); - await waitForPromises(); - - expect(findWorkItemLinkChildrenWrapper().props().children).toHaveLength(5); - }); }); describe('when no child links', () => { @@ -230,50 +188,6 @@ describe('WorkItemLinks', () => { }); }); - describe('remove child', () => { - let firstChild; - - beforeEach(async () => { - await createComponent({ mutationHandler: mutationChangeParentHandler }); - - [firstChild] = childrenWorkItems; - }); - - it('calls correct mutation with correct variables', async () => { - findWorkItemLinkChildrenWrapper().vm.$emit('removeChild', firstChild); - - await waitForPromises(); - - expect(mutationChangeParentHandler).toHaveBeenCalledWith({ - input: { - id: WORK_ITEM_ID, - hierarchyWidget: { - parentId: null, - }, - }, - }); - }); - - it('shows toast when mutation succeeds', async () => { - findWorkItemLinkChildrenWrapper().vm.$emit('removeChild', firstChild); - - await waitForPromises(); - - expect($toast.show).toHaveBeenCalledWith('Child removed', { - action: { onClick: expect.anything(), text: 'Undo' }, - }); - }); - - it('renders correct number of children after removal', async () => { - expect(findWorkItemLinkChildrenWrapper().props().children).toHaveLength(4); - - findWorkItemLinkChildrenWrapper().vm.$emit('removeChild', firstChild); - await waitForPromises(); - - expect(findWorkItemLinkChildrenWrapper().props().children).toHaveLength(3); - }); - }); - describe('when parent item is confidential', () => { it('passes correct confidentiality status to form', async () => { await createComponent({ @@ -289,16 +203,6 @@ describe('WorkItemLinks', () => { }); }); - it('starts prefetching work item by iid if URL contains work_item_iid query parameter', async () => { - setWindowLocation('?work_item_iid=5'); - await createComponent(); - - expect(childWorkItemByIidHandler).toHaveBeenCalledWith({ - iid: '5', - fullPath: 'project/path', - }); - }); - it('does not open the modal if work item iid URL parameter is not found in child items', async () => { setWindowLocation('?work_item_iid=555'); await createComponent(); diff --git a/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js index 06716584879..f3aa347f389 100644 --- a/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js +++ b/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js @@ -1,6 +1,7 @@ import { nextTick } from 'vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import WidgetWrapper from '~/work_items/components/widget_wrapper.vue'; import WorkItemTree from '~/work_items/components/work_item_links/work_item_tree.vue'; import WorkItemChildrenWrapper from '~/work_items/components/work_item_links/work_item_children_wrapper.vue'; import WorkItemLinksForm from '~/work_items/components/work_item_links/work_item_links_form.vue'; @@ -19,6 +20,7 @@ describe('WorkItemTree', () => { const findEmptyState = () => wrapper.findByTestId('tree-empty'); const findToggleFormSplitButton = () => wrapper.findComponent(OkrActionsSplitButton); const findForm = () => wrapper.findComponent(WorkItemLinksForm); + const findWidgetWrapper = () => wrapper.findComponent(WidgetWrapper); const findWorkItemLinkChildrenWrapper = () => wrapper.findComponent(WorkItemChildrenWrapper); const createComponent = ({ @@ -70,6 +72,16 @@ describe('WorkItemTree', () => { expect(findForm().exists()).toBe(false); }); + it('shows an error message on error', async () => { + const errorMessage = 'Some error'; + createComponent(); + + findWorkItemLinkChildrenWrapper().vm.$emit('error', errorMessage); + await nextTick(); + + expect(findWidgetWrapper().props('error')).toBe(errorMessage); + }); + it.each` option | event | formType | childType ${'New objective'} | ${'showCreateObjectiveForm'} | ${FORM_TYPES.create} | ${WORK_ITEM_TYPE_ENUM_OBJECTIVE} |