From 48a8fb609854db4dcac0031af33c460365ef2cfd Mon Sep 17 00:00:00 2001 From: Deepika Guliani <dguliani@gitlab.com> Date: Mon, 26 Jun 2023 10:00:43 +0530 Subject: [PATCH] Refactoring of work item attributes into a separate component Changelog: changed EE: true --- .../work_item_attributes_wrapper.vue | 180 ++++++++++++++++++ .../components/work_item_detail.vue | 123 +----------- ...s => work_item_attributes_wrapper_spec.js} | 108 +++++------ .../work_item_attributes_wrapper_spec.js | 107 +++++++++++ .../components/work_item_detail_spec.js | 111 +++-------- 5 files changed, 360 insertions(+), 269 deletions(-) create mode 100644 app/assets/javascripts/work_items/components/work_item_attributes_wrapper.vue rename ee/spec/frontend/work_items/components/{work_item_detail_spec.js => work_item_attributes_wrapper_spec.js} (64%) create mode 100644 spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js diff --git a/app/assets/javascripts/work_items/components/work_item_attributes_wrapper.vue b/app/assets/javascripts/work_items/components/work_item_attributes_wrapper.vue new file mode 100644 index 000000000000..3e82d603c1df --- /dev/null +++ b/app/assets/javascripts/work_items/components/work_item_attributes_wrapper.vue @@ -0,0 +1,180 @@ +<script> +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { + sprintfWorkItem, + WIDGET_TYPE_ASSIGNEES, + WIDGET_TYPE_HEALTH_STATUS, + WIDGET_TYPE_ITERATION, + WIDGET_TYPE_LABELS, + WIDGET_TYPE_MILESTONE, + WIDGET_TYPE_PROGRESS, + WIDGET_TYPE_START_AND_DUE_DATE, + WIDGET_TYPE_WEIGHT, +} from '../constants'; +import WorkItemState from './work_item_state.vue'; +import WorkItemDueDate from './work_item_due_date.vue'; +import WorkItemAssignees from './work_item_assignees.vue'; +import WorkItemLabels from './work_item_labels.vue'; +import WorkItemMilestone from './work_item_milestone.vue'; + +export default { + components: { + WorkItemLabels, + WorkItemMilestone, + WorkItemAssignees, + WorkItemDueDate, + WorkItemState, + WorkItemWeight: () => import('ee_component/work_items/components/work_item_weight.vue'), + WorkItemProgress: () => import('ee_component/work_items/components/work_item_progress.vue'), + WorkItemIteration: () => import('ee_component/work_items/components/work_item_iteration.vue'), + WorkItemHealthStatus: () => + import('ee_component/work_items/components/work_item_health_status.vue'), + }, + mixins: [glFeatureFlagMixin()], + inject: ['fullPath'], + props: { + workItem: { + type: Object, + required: true, + }, + workItemParentId: { + type: String, + required: false, + default: null, + }, + }, + computed: { + workItemType() { + return this.workItem.workItemType?.name; + }, + canUpdate() { + return this.workItem?.userPermissions?.updateWorkItem; + }, + canDelete() { + return this.workItem?.userPermissions?.deleteWorkItem; + }, + canSetWorkItemMetadata() { + return this.workItem?.userPermissions?.setWorkItemMetadata; + }, + canAssignUnassignUser() { + return this.workItemAssignees && this.canSetWorkItemMetadata; + }, + confidentialTooltip() { + return sprintfWorkItem(this.$options.i18n.confidentialTooltip, this.workItemType); + }, + workItemAssignees() { + return this.isWidgetPresent(WIDGET_TYPE_ASSIGNEES); + }, + workItemLabels() { + return this.isWidgetPresent(WIDGET_TYPE_LABELS); + }, + workItemDueDate() { + return this.isWidgetPresent(WIDGET_TYPE_START_AND_DUE_DATE); + }, + workItemWeight() { + return this.isWidgetPresent(WIDGET_TYPE_WEIGHT); + }, + workItemProgress() { + return this.isWidgetPresent(WIDGET_TYPE_PROGRESS); + }, + workItemIteration() { + return this.isWidgetPresent(WIDGET_TYPE_ITERATION); + }, + workItemHealthStatus() { + return this.isWidgetPresent(WIDGET_TYPE_HEALTH_STATUS); + }, + workItemMilestone() { + return this.isWidgetPresent(WIDGET_TYPE_MILESTONE); + }, + }, + methods: { + isWidgetPresent(type) { + return this.workItem?.widgets?.find((widget) => widget.type === type); + }, + }, +}; +</script> + +<template> + <section> + <work-item-state + :work-item="workItem" + :work-item-parent-id="workItemParentId" + :can-update="canUpdate" + @error="$emit('error', $event)" + /> + <work-item-assignees + v-if="workItemAssignees" + :can-update="canUpdate" + :work-item-id="workItem.id" + :assignees="workItemAssignees.assignees.nodes" + :allows-multiple-assignees="workItemAssignees.allowsMultipleAssignees" + :work-item-type="workItemType" + :can-invite-members="workItemAssignees.canInviteMembers" + @error="$emit('error', $event)" + /> + <work-item-labels + v-if="workItemLabels" + :can-update="canUpdate" + :work-item-id="workItem.id" + :work-item-iid="workItem.iid" + @error="$emit('error', $event)" + /> + <work-item-due-date + v-if="workItemDueDate" + :can-update="canUpdate" + :due-date="workItemDueDate.dueDate" + :start-date="workItemDueDate.startDate" + :work-item-id="workItem.id" + :work-item-type="workItemType" + @error="$emit('error', $event)" + /> + <work-item-milestone + v-if="workItemMilestone" + :work-item-id="workItem.id" + :work-item-milestone="workItemMilestone.milestone" + :work-item-type="workItemType" + :can-update="canUpdate" + @error="$emit('error', $event)" + /> + <work-item-weight + v-if="workItemWeight" + class="gl-mb-5" + :can-update="canUpdate" + :weight="workItemWeight.weight" + :work-item-id="workItem.id" + :work-item-iid="workItem.iid" + :work-item-type="workItemType" + @error="$emit('error', $event)" + /> + <work-item-progress + v-if="workItemProgress" + class="gl-mb-5" + :can-update="canUpdate" + :progress="workItemProgress.progress" + :work-item-id="workItem.id" + :work-item-type="workItemType" + @error="$emit('error', $event)" + /> + <work-item-iteration + v-if="workItemIteration" + class="gl-mb-5" + :iteration="workItemIteration.iteration" + :can-update="canUpdate" + :work-item-id="workItem.id" + :work-item-iid="workItem.iid" + :work-item-type="workItemType" + @error="$emit('error', $event)" + /> + <work-item-health-status + v-if="workItemHealthStatus" + class="gl-mb-5" + :health-status="workItemHealthStatus.healthStatus" + :can-update="canUpdate" + :work-item-id="workItem.id" + :work-item-iid="workItem.iid" + :work-item-type="workItemType" + @error="$emit('error', $event)" + /> + </section> +</template> diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index 5539226e84e1..3acdedf77aaf 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -22,18 +22,11 @@ import { sprintfWorkItem, i18n, WIDGET_TYPE_ASSIGNEES, - WIDGET_TYPE_LABELS, WIDGET_TYPE_NOTIFICATIONS, WIDGET_TYPE_CURRENT_USER_TODOS, WIDGET_TYPE_DESCRIPTION, WIDGET_TYPE_AWARD_EMOJI, - WIDGET_TYPE_START_AND_DUE_DATE, - WIDGET_TYPE_WEIGHT, - WIDGET_TYPE_PROGRESS, WIDGET_TYPE_HIERARCHY, - WIDGET_TYPE_MILESTONE, - WIDGET_TYPE_ITERATION, - WIDGET_TYPE_HEALTH_STATUS, WORK_ITEM_TYPE_VALUE_ISSUE, WORK_ITEM_TYPE_VALUE_OBJECTIVE, WIDGET_TYPE_NOTES, @@ -48,17 +41,13 @@ import { findHierarchyWidgetChildren } from '../utils'; import WorkItemTree from './work_item_links/work_item_tree.vue'; import WorkItemActions from './work_item_actions.vue'; import WorkItemTodos from './work_item_todos.vue'; -import WorkItemState from './work_item_state.vue'; import WorkItemTitle from './work_item_title.vue'; +import WorkItemAttributesWrapper from './work_item_attributes_wrapper.vue'; import WorkItemCreatedUpdated from './work_item_created_updated.vue'; import WorkItemDescription from './work_item_description.vue'; -import WorkItemAwardEmoji from './work_item_award_emoji.vue'; -import WorkItemDueDate from './work_item_due_date.vue'; -import WorkItemAssignees from './work_item_assignees.vue'; -import WorkItemLabels from './work_item_labels.vue'; -import WorkItemMilestone from './work_item_milestone.vue'; import WorkItemNotes from './work_item_notes.vue'; import WorkItemDetailModal from './work_item_detail_modal.vue'; +import WorkItemAwardEmoji from './work_item_award_emoji.vue'; export default { i18n, @@ -74,23 +63,14 @@ export default { GlSkeletonLoader, GlIcon, GlEmptyState, - WorkItemAssignees, WorkItemActions, WorkItemTodos, WorkItemCreatedUpdated, WorkItemDescription, WorkItemAwardEmoji, - WorkItemDueDate, - WorkItemLabels, WorkItemTitle, - WorkItemState, - WorkItemWeight: () => import('ee_component/work_items/components/work_item_weight.vue'), - WorkItemProgress: () => import('ee_component/work_items/components/work_item_progress.vue'), + WorkItemAttributesWrapper, WorkItemTypeIcon, - WorkItemIteration: () => import('ee_component/work_items/components/work_item_iteration.vue'), - WorkItemHealthStatus: () => - import('ee_component/work_items/components/work_item_health_status.vue'), - WorkItemMilestone, WorkItemTree, WorkItemNotes, WorkItemDetailModal, @@ -256,33 +236,12 @@ export default { workItemAssignees() { return this.isWidgetPresent(WIDGET_TYPE_ASSIGNEES); }, - workItemLabels() { - return this.isWidgetPresent(WIDGET_TYPE_LABELS); - }, - workItemDueDate() { - return this.isWidgetPresent(WIDGET_TYPE_START_AND_DUE_DATE); - }, - workItemWeight() { - return this.isWidgetPresent(WIDGET_TYPE_WEIGHT); - }, - workItemProgress() { - return this.isWidgetPresent(WIDGET_TYPE_PROGRESS); - }, workItemAwardEmoji() { return this.isWidgetPresent(WIDGET_TYPE_AWARD_EMOJI); }, workItemHierarchy() { return this.isWidgetPresent(WIDGET_TYPE_HIERARCHY); }, - workItemIteration() { - return this.isWidgetPresent(WIDGET_TYPE_ITERATION); - }, - workItemHealthStatus() { - return this.isWidgetPresent(WIDGET_TYPE_HEALTH_STATUS); - }, - workItemMilestone() { - return this.isWidgetPresent(WIDGET_TYPE_MILESTONE); - }, workItemNotes() { return this.isWidgetPresent(WIDGET_TYPE_NOTES); }, @@ -511,83 +470,9 @@ export default { @error="updateError = $event" /> <work-item-created-updated :work-item-iid="workItemIid" /> - <work-item-state + <work-item-attributes-wrapper :work-item="workItem" :work-item-parent-id="workItemParentId" - :can-update="canUpdate" - @error="updateError = $event" - /> - <work-item-assignees - v-if="workItemAssignees" - :can-update="canUpdate" - :work-item-id="workItem.id" - :assignees="workItemAssignees.assignees.nodes" - :allows-multiple-assignees="workItemAssignees.allowsMultipleAssignees" - :work-item-type="workItemType" - :can-invite-members="workItemAssignees.canInviteMembers" - @error="updateError = $event" - /> - <work-item-labels - v-if="workItemLabels" - :can-update="canUpdate" - :work-item-id="workItem.id" - :work-item-iid="workItem.iid" - @error="updateError = $event" - /> - <work-item-due-date - v-if="workItemDueDate" - :can-update="canUpdate" - :due-date="workItemDueDate.dueDate" - :start-date="workItemDueDate.startDate" - :work-item-id="workItem.id" - :work-item-type="workItemType" - @error="updateError = $event" - /> - <work-item-milestone - v-if="workItemMilestone" - :work-item-id="workItem.id" - :work-item-milestone="workItemMilestone.milestone" - :work-item-type="workItemType" - :can-update="canUpdate" - @error="updateError = $event" - /> - <work-item-weight - v-if="workItemWeight" - class="gl-mb-5" - :can-update="canUpdate" - :weight="workItemWeight.weight" - :work-item-id="workItem.id" - :work-item-iid="workItem.iid" - :work-item-type="workItemType" - @error="updateError = $event" - /> - <work-item-progress - v-if="workItemProgress" - class="gl-mb-5" - :can-update="canUpdate" - :progress="workItemProgress.progress" - :work-item-id="workItem.id" - :work-item-type="workItemType" - @error="updateError = $event" - /> - <work-item-iteration - v-if="workItemIteration" - class="gl-mb-5" - :iteration="workItemIteration.iteration" - :can-update="canUpdate" - :work-item-id="workItem.id" - :work-item-iid="workItem.iid" - :work-item-type="workItemType" - @error="updateError = $event" - /> - <work-item-health-status - v-if="workItemHealthStatus" - class="gl-mb-5" - :health-status="workItemHealthStatus.healthStatus" - :can-update="canUpdate" - :work-item-id="workItem.id" - :work-item-iid="workItem.iid" - :work-item-type="workItemType" @error="updateError = $event" /> <work-item-description diff --git a/ee/spec/frontend/work_items/components/work_item_detail_spec.js b/ee/spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js similarity index 64% rename from ee/spec/frontend/work_items/components/work_item_detail_spec.js rename to ee/spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js index 5ba69c1ea25a..1f9eb448249a 100644 --- a/ee/spec/frontend/work_items/components/work_item_detail_spec.js +++ b/ee/spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js @@ -1,61 +1,57 @@ -import { GlAlert } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; -import WorkItemWeight from 'ee/work_items/components/work_item_weight.vue'; +import { shallowMount } from '@vue/test-utils'; import WorkItemProgress from 'ee/work_items/components/work_item_progress.vue'; -import WorkItemIteration from 'ee/work_items/components/work_item_iteration.vue'; import WorkItemHealthStatus from 'ee/work_items/components/work_item_health_status.vue'; -import createMockApollo from 'helpers/mock_apollo_helper'; +import WorkItemWeight from 'ee/work_items/components/work_item_weight.vue'; +import WorkItemIteration from 'ee/work_items/components/work_item_iteration.vue'; import waitForPromises from 'helpers/wait_for_promises'; -import { workItemByIidResponseFactory } from 'jest/work_items/mock_data'; -import WorkItemDetail from '~/work_items/components/work_item_detail.vue'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { workItemResponseFactory } from 'jest/work_items/mock_data'; + +import WorkItemAttributesWrapper from '~/work_items/components/work_item_attributes_wrapper.vue'; import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql'; import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql'; import workItemUpdatedSubscription from '~/work_items/graphql/work_item_updated.subscription.graphql'; -describe('WorkItemDetail component', () => { +describe('EE WorkItemAttributesWrapper component', () => { let wrapper; Vue.use(VueApollo); - const workItemQueryResponse = workItemByIidResponseFactory({ canUpdate: true, canDelete: true }); + const workItemQueryResponse = workItemResponseFactory({ canUpdate: true, canDelete: true }); + const successHandler = jest.fn().mockResolvedValue(workItemQueryResponse); const workItemUpdatedSubscriptionHandler = jest .fn() .mockResolvedValue({ data: { workItemUpdated: null } }); - const findAlert = () => wrapper.findComponent(GlAlert); + const findWorkItemIteration = () => wrapper.findComponent(WorkItemIteration); const findWorkItemWeight = () => wrapper.findComponent(WorkItemWeight); const findWorkItemProgress = () => wrapper.findComponent(WorkItemProgress); - const findWorkItemIteration = () => wrapper.findComponent(WorkItemIteration); const findWorkItemHealthStatus = () => wrapper.findComponent(WorkItemHealthStatus); const createComponent = ({ + workItem = workItemQueryResponse.data.workItem, handler = successHandler, - workItemsMvcEnabled = false, confidentialityMock = [updateWorkItemMutation, jest.fn()], } = {}) => { - wrapper = shallowMount(WorkItemDetail, { + wrapper = shallowMount(WorkItemAttributesWrapper, { apolloProvider: createMockApollo([ [workItemByIidQuery, handler], [workItemUpdatedSubscription, workItemUpdatedSubscriptionHandler], confidentialityMock, ]), + propsData: { + workItem, + }, provide: { - glFeatures: { - workItemsMvc: workItemsMvcEnabled, - }, hasIssueWeightsFeature: true, hasIterationsFeature: true, - hasIssuableHealthStatusFeature: true, hasOkrsFeature: true, + hasIssuableHealthStatusFeature: true, projectNamespace: 'namespace', fullPath: 'group/project', - reportAbusePath: '/report/abuse/path', - }, - propsData: { - workItemIid: '1', }, }); }; @@ -69,24 +65,22 @@ describe('WorkItemDetail component', () => { it(`${ iterationWidgetPresent ? 'renders' : 'does not render' } iteration component`, async () => { - const response = workItemByIidResponseFactory({ iterationWidgetPresent }); - const handler = jest.fn().mockResolvedValue(response); - createComponent({ handler }); + const response = workItemResponseFactory({ iterationWidgetPresent }); + createComponent({ workItem: response.data.workItem }); await waitForPromises(); expect(findWorkItemIteration().exists()).toBe(exists); }); }); - it('shows an error message when it emits an `error` event', async () => { + it('emits an error event to the wrapper', async () => { createComponent(); - await waitForPromises(); const updateError = 'Failed to update'; findWorkItemIteration().vm.$emit('error', updateError); - await waitForPromises(); + await nextTick(); - expect(findAlert().text()).toBe(updateError); + expect(wrapper.emitted('error')).toEqual([[updateError]]); }); }); @@ -96,25 +90,23 @@ describe('WorkItemDetail component', () => { ${'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 = workItemByIidResponseFactory({ weightWidgetPresent }); - const handler = jest.fn().mockResolvedValue(response); - createComponent({ handler }); - await waitForPromises(); + it(`${weightWidgetPresent ? 'renders' : 'does not render'} weight component`, () => { + const response = workItemResponseFactory({ weightWidgetPresent }); + createComponent({ workItem: response.data.workItem }); expect(findWorkItemWeight().exists()).toBe(exists); }); }); - it('shows an error message when it emits an `error` event', async () => { - createComponent(); - await waitForPromises(); + it('emits an error event to the wrapper', async () => { + const response = workItemResponseFactory({ weightWidgetPresent: true }); + createComponent({ workItem: response.data.workItem }); const updateError = 'Failed to update'; findWorkItemWeight().vm.$emit('error', updateError); - await waitForPromises(); + await nextTick(); - expect(findAlert().text()).toBe(updateError); + expect(wrapper.emitted('error')).toEqual([[updateError]]); }); }); @@ -126,25 +118,23 @@ describe('WorkItemDetail component', () => { `('$description', ({ healthStatusWidgetPresent, exists }) => { it(`${ healthStatusWidgetPresent ? 'renders' : 'does not render' - } healthStatus component`, async () => { - const response = workItemByIidResponseFactory({ healthStatusWidgetPresent }); - const handler = jest.fn().mockResolvedValue(response); - createComponent({ handler }); - await waitForPromises(); + } healthStatus component`, () => { + const response = workItemResponseFactory({ healthStatusWidgetPresent }); + createComponent({ workItem: response.data.workItem }); expect(findWorkItemHealthStatus().exists()).toBe(exists); }); }); - it('shows an error message when it emits an `error` event', async () => { - createComponent(); - await waitForPromises(); + it('emits an error event to the wrapper', async () => { + const response = workItemResponseFactory({ healthStatusWidgetPresent: true }); + createComponent({ workItem: response.data.workItem }); const updateError = 'Failed to update'; findWorkItemHealthStatus().vm.$emit('error', updateError); - await waitForPromises(); + await nextTick(); - expect(findAlert().text()).toBe(updateError); + expect(wrapper.emitted('error')).toEqual([[updateError]]); }); }); @@ -154,27 +144,23 @@ describe('WorkItemDetail component', () => { ${'when widget is returned from API'} | ${true} | ${true} ${'when widget is not returned from API'} | ${false} | ${false} `('$description', ({ progressWidgetPresent, exists }) => { - it(`${ - progressWidgetPresent ? 'renders' : 'does not render' - } progress component`, async () => { - const response = workItemByIidResponseFactory({ progressWidgetPresent }); - const handler = jest.fn().mockResolvedValue(response); - createComponent({ handler }); - await waitForPromises(); + it(`${progressWidgetPresent ? 'renders' : 'does not render'} progress component`, () => { + const response = workItemResponseFactory({ progressWidgetPresent }); + createComponent({ workItem: response.data.workItem }); expect(findWorkItemProgress().exists()).toBe(exists); }); }); - it('shows an error message when it emits an `error` event', async () => { - createComponent(); - await waitForPromises(); + it('emits an error event to the wrapper', async () => { + const response = workItemResponseFactory({ progressWidgetPresent: true }); + createComponent({ workItem: response.data.workItem }); const updateError = 'Failed to update'; findWorkItemProgress().vm.$emit('error', updateError); - await waitForPromises(); + await nextTick(); - expect(findAlert().text()).toBe(updateError); + expect(wrapper.emitted('error')).toEqual([[updateError]]); }); }); }); diff --git a/spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js b/spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js new file mode 100644 index 000000000000..ba9af7b2b686 --- /dev/null +++ b/spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js @@ -0,0 +1,107 @@ +import { shallowMount } from '@vue/test-utils'; +import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue'; +import WorkItemDueDate from '~/work_items/components/work_item_due_date.vue'; +import WorkItemState from '~/work_items/components/work_item_state.vue'; +import WorkItemLabels from '~/work_items/components/work_item_labels.vue'; +import WorkItemMilestone from '~/work_items/components/work_item_milestone.vue'; + +import WorkItemAttributesWrapper from '~/work_items/components/work_item_attributes_wrapper.vue'; +import { workItemResponseFactory } from '../mock_data'; + +describe('WorkItemAttributesWrapper component', () => { + let wrapper; + + const workItemQueryResponse = workItemResponseFactory({ canUpdate: true, canDelete: true }); + + const findWorkItemState = () => wrapper.findComponent(WorkItemState); + const findWorkItemDueDate = () => wrapper.findComponent(WorkItemDueDate); + const findWorkItemAssignees = () => wrapper.findComponent(WorkItemAssignees); + const findWorkItemLabels = () => wrapper.findComponent(WorkItemLabels); + const findWorkItemMilestone = () => wrapper.findComponent(WorkItemMilestone); + + const createComponent = ({ workItem = workItemQueryResponse.data.workItem } = {}) => { + wrapper = shallowMount(WorkItemAttributesWrapper, { + propsData: { + workItem, + }, + provide: { + hasIssueWeightsFeature: true, + hasIterationsFeature: true, + hasOkrsFeature: true, + hasIssuableHealthStatusFeature: true, + projectNamespace: 'namespace', + fullPath: 'group/project', + }, + stubs: { + WorkItemWeight: true, + WorkItemIteration: true, + WorkItemHealthStatus: true, + }, + }); + }; + + describe('work item state', () => { + it('renders the work item state', () => { + createComponent(); + + expect(findWorkItemState().exists()).toBe(true); + }); + }); + + describe('assignees widget', () => { + it('renders assignees component when widget is returned from the API', () => { + createComponent(); + + expect(findWorkItemAssignees().exists()).toBe(true); + }); + + it('does not render assignees component when widget is not returned from the API', () => { + createComponent({ + workItem: workItemResponseFactory({ assigneesWidgetPresent: false }).data.workItem, + }); + + expect(findWorkItemAssignees().exists()).toBe(false); + }); + }); + + describe('labels widget', () => { + it.each` + description | labelsWidgetPresent | exists + ${'renders when widget is returned from API'} | ${true} | ${true} + ${'does not render when widget is not returned from API'} | ${false} | ${false} + `('$description', ({ labelsWidgetPresent, exists }) => { + const response = workItemResponseFactory({ labelsWidgetPresent }); + createComponent({ workItem: response.data.workItem }); + + expect(findWorkItemLabels().exists()).toBe(exists); + }); + }); + + describe('dates widget', () => { + describe.each` + description | datesWidgetPresent | exists + ${'when widget is returned from API'} | ${true} | ${true} + ${'when widget is not returned from API'} | ${false} | ${false} + `('$description', ({ datesWidgetPresent, exists }) => { + it(`${datesWidgetPresent ? 'renders' : 'does not render'} due date component`, () => { + const response = workItemResponseFactory({ datesWidgetPresent }); + createComponent({ workItem: response.data.workItem }); + + expect(findWorkItemDueDate().exists()).toBe(exists); + }); + }); + }); + + describe('milestone widget', () => { + it.each` + description | milestoneWidgetPresent | exists + ${'renders when widget is returned from API'} | ${true} | ${true} + ${'does not render when widget is not returned from API'} | ${false} | ${false} + `('$description', ({ milestoneWidgetPresent, exists }) => { + const response = workItemResponseFactory({ milestoneWidgetPresent }); + createComponent({ workItem: response.data.workItem }); + + expect(findWorkItemMilestone().exists()).toBe(exists); + }); + }); +}); diff --git a/spec/frontend/work_items/components/work_item_detail_spec.js b/spec/frontend/work_items/components/work_item_detail_spec.js index 0266533a11ca..14a6ada16bd2 100644 --- a/spec/frontend/work_items/components/work_item_detail_spec.js +++ b/spec/frontend/work_items/components/work_item_detail_spec.js @@ -18,12 +18,8 @@ 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 WorkItemCreatedUpdated from '~/work_items/components/work_item_created_updated.vue'; -import WorkItemDueDate from '~/work_items/components/work_item_due_date.vue'; -import WorkItemState from '~/work_items/components/work_item_state.vue'; +import WorkItemAttributesWrapper from '~/work_items/components/work_item_attributes_wrapper.vue'; import WorkItemTitle from '~/work_items/components/work_item_title.vue'; -import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue'; -import WorkItemLabels from '~/work_items/components/work_item_labels.vue'; -import WorkItemMilestone from '~/work_items/components/work_item_milestone.vue'; import WorkItemTree from '~/work_items/components/work_item_links/work_item_tree.vue'; import WorkItemNotes from '~/work_items/components/work_item_notes.vue'; import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue'; @@ -69,12 +65,8 @@ describe('WorkItemDetail component', () => { const findWorkItemActions = () => wrapper.findComponent(WorkItemActions); const findWorkItemTitle = () => wrapper.findComponent(WorkItemTitle); const findCreatedUpdated = () => wrapper.findComponent(WorkItemCreatedUpdated); - const findWorkItemState = () => wrapper.findComponent(WorkItemState); const findWorkItemDescription = () => wrapper.findComponent(WorkItemDescription); - const findWorkItemDueDate = () => wrapper.findComponent(WorkItemDueDate); - const findWorkItemAssignees = () => wrapper.findComponent(WorkItemAssignees); - const findWorkItemLabels = () => wrapper.findComponent(WorkItemLabels); - const findWorkItemMilestone = () => wrapper.findComponent(WorkItemMilestone); + const findWorkItemAttributesWrapper = () => wrapper.findComponent(WorkItemAttributesWrapper); const findParent = () => wrapper.find('[data-testid="work-item-parent"]'); const findParentButton = () => findParent().findComponent(GlButton); const findCloseButton = () => wrapper.find('[data-testid="work-item-close"]'); @@ -168,7 +160,6 @@ describe('WorkItemDetail component', () => { it('renders skeleton loader', () => { expect(findSkeleton().exists()).toBe(true); - expect(findWorkItemState().exists()).toBe(false); expect(findWorkItemTitle().exists()).toBe(false); }); }); @@ -181,7 +172,6 @@ describe('WorkItemDetail component', () => { it('does not render skeleton', () => { expect(findSkeleton().exists()).toBe(false); - expect(findWorkItemState().exists()).toBe(true); expect(findWorkItemTitle().exists()).toBe(true); }); @@ -480,83 +470,6 @@ describe('WorkItemDetail component', () => { expect(findAlert().text()).toBe(updateError); }); - describe('assignees widget', () => { - it('renders assignees component when widget is returned from the API', async () => { - createComponent(); - await waitForPromises(); - - expect(findWorkItemAssignees().exists()).toBe(true); - }); - - it('does not render assignees component when widget is not returned from the API', async () => { - createComponent({ - handler: jest - .fn() - .mockResolvedValue(workItemByIidResponseFactory({ assigneesWidgetPresent: false })), - }); - await waitForPromises(); - - expect(findWorkItemAssignees().exists()).toBe(false); - }); - }); - - describe('labels widget', () => { - it.each` - description | labelsWidgetPresent | exists - ${'renders when widget is returned from API'} | ${true} | ${true} - ${'does not render when widget is not returned from API'} | ${false} | ${false} - `('$description', async ({ labelsWidgetPresent, exists }) => { - const response = workItemByIidResponseFactory({ labelsWidgetPresent }); - const handler = jest.fn().mockResolvedValue(response); - createComponent({ handler }); - await waitForPromises(); - - expect(findWorkItemLabels().exists()).toBe(exists); - }); - }); - - describe('dates widget', () => { - describe.each` - description | datesWidgetPresent | exists - ${'when widget is returned from API'} | ${true} | ${true} - ${'when widget is not returned from API'} | ${false} | ${false} - `('$description', ({ datesWidgetPresent, exists }) => { - it(`${datesWidgetPresent ? 'renders' : 'does not render'} due date component`, async () => { - const response = workItemByIidResponseFactory({ datesWidgetPresent }); - const handler = jest.fn().mockResolvedValue(response); - createComponent({ handler }); - await waitForPromises(); - - expect(findWorkItemDueDate().exists()).toBe(exists); - }); - }); - - it('shows an error message when it emits an `error` event', async () => { - createComponent(); - await waitForPromises(); - const updateError = 'Failed to update'; - - findWorkItemDueDate().vm.$emit('error', updateError); - await waitForPromises(); - - expect(findAlert().text()).toBe(updateError); - }); - }); - - describe('milestone widget', () => { - it.each` - description | milestoneWidgetPresent | exists - ${'renders when widget is returned from API'} | ${true} | ${true} - ${'does not render when widget is not returned from API'} | ${false} | ${false} - `('$description', async ({ milestoneWidgetPresent, exists }) => { - const response = workItemByIidResponseFactory({ milestoneWidgetPresent }); - const handler = jest.fn().mockResolvedValue(response); - createComponent({ handler }); - await waitForPromises(); - - expect(findWorkItemMilestone().exists()).toBe(exists); - }); - }); it('calls the work item query', async () => { createComponent(); @@ -713,4 +626,24 @@ describe('WorkItemDetail component', () => { expect(findWorkItemTodos().exists()).toBe(false); }); }); + + describe('work item attributes wrapper', () => { + beforeEach(async () => { + createComponent(); + await waitForPromises(); + }); + + it('renders the work item attributes wrapper', () => { + expect(findWorkItemAttributesWrapper().exists()).toBe(true); + }); + + it('shows an error message when it emits an `error` event', async () => { + const updateError = 'Failed to update'; + + findWorkItemAttributesWrapper().vm.$emit('error', updateError); + await waitForPromises(); + + expect(findAlert().text()).toBe(updateError); + }); + }); }); -- GitLab