diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-18 11:17:02 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-18 11:17:02 +0300 |
commit | b39512ed755239198a9c294b6a45e65c05900235 (patch) | |
tree | d234a3efade1de67c46b9e5a38ce813627726aa7 /spec/frontend/work_items/pages | |
parent | d31474cf3b17ece37939d20082b07f6657cc79a9 (diff) |
Add latest changes from gitlab-org/gitlab@15-3-stable-eev15.3.0-rc42
Diffstat (limited to 'spec/frontend/work_items/pages')
-rw-r--r-- | spec/frontend/work_items/pages/work_item_detail_spec.js | 213 |
1 files changed, 185 insertions, 28 deletions
diff --git a/spec/frontend/work_items/pages/work_item_detail_spec.js b/spec/frontend/work_items/pages/work_item_detail_spec.js index 43869468ad0..823981df880 100644 --- a/spec/frontend/work_items/pages/work_item_detail_spec.js +++ b/spec/frontend/work_items/pages/work_item_detail_spec.js @@ -1,11 +1,12 @@ -import { GlAlert, GlSkeletonLoader, GlButton } from '@gitlab/ui'; +import { GlAlert, GlBadge, GlLoadingIcon, GlSkeletonLoader, GlButton } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import WorkItemDetail from '~/work_items/components/work_item_detail.vue'; +import WorkItemActions from '~/work_items/components/work_item_actions.vue'; import WorkItemDescription from '~/work_items/components/work_item_description.vue'; import WorkItemState from '~/work_items/components/work_item_state.vue'; import WorkItemTitle from '~/work_items/components/work_item_title.vue'; @@ -16,6 +17,8 @@ import WorkItemInformation from '~/work_items/components/work_item_information.v import { i18n } from '~/work_items/constants'; import workItemQuery from '~/work_items/graphql/work_item.query.graphql'; import workItemTitleSubscription from '~/work_items/graphql/work_item_title.subscription.graphql'; +import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql'; +import updateWorkItemTaskMutation from '~/work_items/graphql/update_work_item_task.mutation.graphql'; import { temporaryConfig } from '~/work_items/graphql/provider'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import { @@ -30,12 +33,19 @@ describe('WorkItemDetail component', () => { Vue.use(VueApollo); - const workItemQueryResponse = workItemResponseFactory(); + const workItemQueryResponse = workItemResponseFactory({ canUpdate: true, canDelete: true }); + const workItemQueryResponseWithoutParent = workItemResponseFactory({ + parent: null, + canUpdate: true, + canDelete: true, + }); const successHandler = jest.fn().mockResolvedValue(workItemQueryResponse); const initialSubscriptionHandler = jest.fn().mockResolvedValue(workItemTitleSubscriptionResponse); const findAlert = () => wrapper.findComponent(GlAlert); const findSkeleton = () => wrapper.findComponent(GlSkeletonLoader); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findWorkItemActions = () => wrapper.findComponent(WorkItemActions); const findWorkItemTitle = () => wrapper.findComponent(WorkItemTitle); const findWorkItemState = () => wrapper.findComponent(WorkItemState); const findWorkItemDescription = () => wrapper.findComponent(WorkItemDescription); @@ -51,17 +61,21 @@ describe('WorkItemDetail component', () => { const createComponent = ({ isModal = false, + updateInProgress = false, workItemId = workItemQueryResponse.data.workItem.id, handler = successHandler, subscriptionHandler = initialSubscriptionHandler, + confidentialityMock = [updateWorkItemMutation, jest.fn()], workItemsMvc2Enabled = false, includeWidgets = false, + error = undefined, } = {}) => { wrapper = shallowMount(WorkItemDetail, { apolloProvider: createMockApollo( [ [workItemQuery, handler], [workItemTitleSubscription, subscriptionHandler], + confidentialityMock, ], {}, { @@ -69,6 +83,12 @@ describe('WorkItemDetail component', () => { }, ), propsData: { isModal, workItemId }, + data() { + return { + updateInProgress, + error, + }; + }, provide: { glFeatures: { workItemsMvc2: workItemsMvc2Enabled, @@ -146,6 +166,148 @@ describe('WorkItemDetail component', () => { }); }); + describe('confidentiality', () => { + const errorMessage = 'Mutation failed'; + const confidentialWorkItem = workItemResponseFactory({ + confidential: true, + }); + + // Mocks for work item without parent + const withoutParentExpectedInputVars = { + id: workItemQueryResponse.data.workItem.id, + confidential: true, + }; + const toggleConfidentialityWithoutParentHandler = jest.fn().mockResolvedValue({ + data: { + workItemUpdate: { + workItem: confidentialWorkItem.data.workItem, + errors: [], + }, + }, + }); + const withoutParentHandlerMock = jest + .fn() + .mockResolvedValue(workItemQueryResponseWithoutParent); + const confidentialityWithoutParentMock = [ + updateWorkItemMutation, + toggleConfidentialityWithoutParentHandler, + ]; + const confidentialityWithoutParentFailureMock = [ + updateWorkItemMutation, + jest.fn().mockRejectedValue(new Error(errorMessage)), + ]; + + // Mocks for work item with parent + const withParentExpectedInputVars = { + id: mockParent.parent.id, + taskData: { id: workItemQueryResponse.data.workItem.id, confidential: true }, + }; + const toggleConfidentialityWithParentHandler = jest.fn().mockResolvedValue({ + data: { + workItemUpdate: { + workItem: { + id: confidentialWorkItem.data.workItem.id, + descriptionHtml: confidentialWorkItem.data.workItem.description, + }, + task: { + workItem: confidentialWorkItem.data.workItem, + confidential: true, + }, + errors: [], + }, + }, + }); + const confidentialityWithParentMock = [ + updateWorkItemTaskMutation, + toggleConfidentialityWithParentHandler, + ]; + const confidentialityWithParentFailureMock = [ + updateWorkItemTaskMutation, + jest.fn().mockRejectedValue(new Error(errorMessage)), + ]; + + describe.each` + context | handlerMock | confidentialityMock | confidentialityFailureMock | inputVariables + ${'no parent'} | ${withoutParentHandlerMock} | ${confidentialityWithoutParentMock} | ${confidentialityWithoutParentFailureMock} | ${withoutParentExpectedInputVars} + ${'parent'} | ${successHandler} | ${confidentialityWithParentMock} | ${confidentialityWithParentFailureMock} | ${withParentExpectedInputVars} + `( + 'when work item has $context', + ({ handlerMock, confidentialityMock, confidentialityFailureMock, inputVariables }) => { + it('renders confidential badge when work item is confidential', async () => { + createComponent({ + handler: jest.fn().mockResolvedValue(confidentialWorkItem), + confidentialityMock, + }); + + await waitForPromises(); + + const confidentialBadge = wrapper.findComponent(GlBadge); + expect(confidentialBadge.exists()).toBe(true); + expect(confidentialBadge.props()).toMatchObject({ + variant: 'warning', + icon: 'eye-slash', + }); + expect(confidentialBadge.attributes('title')).toBe( + 'Only project members with at least the Reporter role, the author, and assignees can view or be notified about this task.', + ); + expect(confidentialBadge.text()).toBe('Confidential'); + }); + + it('renders gl-loading-icon while update mutation is in progress', async () => { + createComponent({ + handler: handlerMock, + confidentialityMock, + }); + + await waitForPromises(); + + findWorkItemActions().vm.$emit('toggleWorkItemConfidentiality', true); + + await nextTick(); + + expect(findLoadingIcon().exists()).toBe(true); + }); + + it('emits workItemUpdated and shows confidentiality badge when mutation is successful', async () => { + createComponent({ + handler: handlerMock, + confidentialityMock, + }); + + await waitForPromises(); + + findWorkItemActions().vm.$emit('toggleWorkItemConfidentiality', true); + await waitForPromises(); + + expect(wrapper.emitted('workItemUpdated')).toEqual([[{ confidential: true }]]); + expect(confidentialityMock[1]).toHaveBeenCalledWith({ + input: inputVariables, + }); + expect(findLoadingIcon().exists()).toBe(false); + }); + + it('shows alert message when mutation fails', async () => { + createComponent({ + handler: handlerMock, + confidentialityMock: confidentialityFailureMock, + }); + + await waitForPromises(); + findWorkItemActions().vm.$emit('toggleWorkItemConfidentiality', true); + await waitForPromises(); + + expect(wrapper.emitted('workItemUpdated')).toBeFalsy(); + + await nextTick(); + + expect(findAlert().exists()).toBe(true); + expect(findAlert().text()).toBe(errorMessage); + expect(findLoadingIcon().exists()).toBe(false); + }); + }, + ); + }); + describe('description', () => { it('does not show description widget if loading description fails', () => { createComponent(); @@ -169,7 +331,7 @@ describe('WorkItemDetail component', () => { }); it('does not show secondary breadcrumbs if there is not a parent', async () => { - createComponent(); + createComponent({ handler: jest.fn().mockResolvedValue(workItemQueryResponseWithoutParent) }); await waitForPromises(); @@ -177,7 +339,7 @@ describe('WorkItemDetail component', () => { }); it('shows work item type if there is not a parent', async () => { - createComponent(); + createComponent({ handler: jest.fn().mockResolvedValue(workItemQueryResponseWithoutParent) }); await waitForPromises(); expect(findWorkItemType().exists()).toBe(true); @@ -276,34 +438,29 @@ describe('WorkItemDetail component', () => { }); describe('weight widget', () => { - describe('when work_items_mvc_2 feature flag is enabled', () => { - describe.each` - description | includeWidgets | exists - ${'when widget is returned from API'} | ${true} | ${true} - ${'when widget is not returned from API'} | ${false} | ${false} - `('$description', ({ includeWidgets, exists }) => { - it(`${includeWidgets ? 'renders' : 'does not render'} weight component`, async () => { - createComponent({ includeWidgets, workItemsMvc2Enabled: true }); - await waitForPromises(); + describe.each` + description | weightWidgetPresent | exists + ${'when widget is returned from API'} | ${true} | ${true} + ${'when widget is not returned from API'} | ${false} | ${false} + `('$description', ({ weightWidgetPresent, exists }) => { + it(`${weightWidgetPresent ? 'renders' : 'does not render'} weight component`, async () => { + const response = workItemResponseFactory({ weightWidgetPresent }); + const handler = jest.fn().mockResolvedValue(response); + createComponent({ handler }); + await waitForPromises(); - expect(findWorkItemWeight().exists()).toBe(exists); - }); + expect(findWorkItemWeight().exists()).toBe(exists); }); }); - describe('when work_items_mvc_2 feature flag is disabled', () => { - describe.each` - description | includeWidgets | exists - ${'when widget is returned from API'} | ${true} | ${false} - ${'when widget is not returned from API'} | ${false} | ${false} - `('$description', ({ includeWidgets, exists }) => { - it(`${includeWidgets ? 'renders' : 'does not render'} weight component`, async () => { - createComponent({ includeWidgets, workItemsMvc2Enabled: false }); - await waitForPromises(); + it('shows an error message when it emits an `error` event', async () => { + createComponent({ workItemsMvc2Enabled: true }); + await waitForPromises(); - expect(findWorkItemWeight().exists()).toBe(exists); - }); - }); + findWorkItemWeight().vm.$emit('error', i18n.updateError); + await waitForPromises(); + + expect(findAlert().text()).toBe(i18n.updateError); }); }); |