Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/work_items')
-rw-r--r--spec/frontend/work_items/components/notes/work_item_add_note_spec.js60
-rw-r--r--spec/frontend/work_items/components/notes/work_item_comment_form_spec.js4
-rw-r--r--spec/frontend/work_items/components/notes/work_item_discussion_spec.js4
-rw-r--r--spec/frontend/work_items/components/notes/work_item_note_actions_spec.js2
-rw-r--r--spec/frontend/work_items/components/notes/work_item_note_awards_list_spec.js4
-rw-r--r--spec/frontend/work_items/components/notes/work_item_note_spec.js38
-rw-r--r--spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js5
-rw-r--r--spec/frontend/work_items/components/work_item_actions_spec.js140
-rw-r--r--spec/frontend/work_items/components/work_item_assignees_spec.js45
-rw-r--r--spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js2
-rw-r--r--spec/frontend/work_items/components/work_item_created_updated_spec.js64
-rw-r--r--spec/frontend/work_items/components/work_item_description_spec.js39
-rw-r--r--spec/frontend/work_items/components/work_item_detail_spec.js87
-rw-r--r--spec/frontend/work_items/components/work_item_labels_spec.js58
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_children_wrapper_spec.js3
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js21
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js3
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_links_spec.js37
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js4
-rw-r--r--spec/frontend/work_items/components/work_item_milestone_spec.js4
-rw-r--r--spec/frontend/work_items/components/work_item_notes_spec.js4
-rw-r--r--spec/frontend/work_items/components/work_item_parent_spec.js236
-rw-r--r--spec/frontend/work_items/components/work_item_relationships/__snapshots__/work_item_relationship_list_spec.js.snap6
-rw-r--r--spec/frontend/work_items/components/work_item_relationships/work_item_add_relationship_form_spec.js156
-rw-r--r--spec/frontend/work_items/components/work_item_relationships/work_item_relationship_list_spec.js3
-rw-r--r--spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js157
-rw-r--r--spec/frontend/work_items/components/work_item_todos_spec.js3
-rw-r--r--spec/frontend/work_items/graphql/cache_utils_spec.js8
-rw-r--r--spec/frontend/work_items/mock_data.js173
-rw-r--r--spec/frontend/work_items/pages/create_work_item_spec.js5
-rw-r--r--spec/frontend/work_items/router_spec.js1
-rw-r--r--spec/frontend/work_items/utils_spec.js13
32 files changed, 1151 insertions, 238 deletions
diff --git a/spec/frontend/work_items/components/notes/work_item_add_note_spec.js b/spec/frontend/work_items/components/notes/work_item_add_note_spec.js
index 826fc2b2230..b2b372d9d0d 100644
--- a/spec/frontend/work_items/components/notes/work_item_add_note_spec.js
+++ b/spec/frontend/work_items/components/notes/work_item_add_note_spec.js
@@ -10,9 +10,11 @@ import WorkItemCommentLocked from '~/work_items/components/notes/work_item_comme
import WorkItemCommentForm from '~/work_items/components/notes/work_item_comment_form.vue';
import createNoteMutation from '~/work_items/graphql/notes/create_work_item_note.mutation.graphql';
import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
+import groupWorkItemByIidQuery from '~/work_items/graphql/group_work_item_by_iid.query.graphql';
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
import {
createWorkItemNoteResponse,
+ groupWorkItemByIidResponseFactory,
workItemByIidResponseFactory,
workItemQueryResponse,
} from '../../mock_data';
@@ -29,6 +31,7 @@ describe('Work item add note', () => {
const mutationSuccessHandler = jest.fn().mockResolvedValue(createWorkItemNoteResponse);
let workItemResponseHandler;
+ let groupWorkItemResponseHandler;
const findCommentForm = () => wrapper.findComponent(WorkItemCommentForm);
const findTextarea = () => wrapper.findByTestId('note-reply-textarea');
@@ -40,29 +43,32 @@ describe('Work item add note', () => {
canCreateNote = true,
workItemIid = '1',
workItemResponse = workItemByIidResponseFactory({ canUpdate, canCreateNote }),
+ groupWorkItemResponse = groupWorkItemByIidResponseFactory({ canUpdate, canCreateNote }),
signedIn = true,
isEditing = true,
+ isGroup = false,
workItemType = 'Task',
isInternalThread = false,
} = {}) => {
workItemResponseHandler = jest.fn().mockResolvedValue(workItemResponse);
+ groupWorkItemResponseHandler = jest.fn().mockResolvedValue(groupWorkItemResponse);
if (signedIn) {
window.gon.current_user_id = '1';
window.gon.current_user_avatar_url = 'avatar.png';
}
- const apolloProvider = createMockApollo([
- [workItemByIidQuery, workItemResponseHandler],
- [createNoteMutation, mutationHandler],
- ]);
-
const { id } = workItemQueryResponse.data.workItem;
wrapper = shallowMountExtended(WorkItemAddNote, {
- apolloProvider,
+ apolloProvider: createMockApollo([
+ [workItemByIidQuery, workItemResponseHandler],
+ [groupWorkItemByIidQuery, groupWorkItemResponseHandler],
+ [createNoteMutation, mutationHandler],
+ ]),
provide: {
- fullPath: 'test-project-path',
+ isGroup,
},
propsData: {
+ fullPath: 'test-project-path',
workItemId: id,
workItemIid,
workItemType,
@@ -272,16 +278,44 @@ describe('Work item add note', () => {
});
});
- it('calls the work item query', async () => {
- await createComponent();
+ describe('when project context', () => {
+ it('calls the project work item query', async () => {
+ await createComponent();
+
+ expect(workItemResponseHandler).toHaveBeenCalled();
+ });
+
+ it('skips calling the group work item query', async () => {
+ await createComponent();
+
+ expect(groupWorkItemResponseHandler).not.toHaveBeenCalled();
+ });
+
+ it('skips calling the project work item query when missing workItemIid', async () => {
+ await createComponent({ workItemIid: '', isEditing: false });
- expect(workItemResponseHandler).toHaveBeenCalled();
+ expect(workItemResponseHandler).not.toHaveBeenCalled();
+ });
});
- it('skips calling the work item query when missing workItemIid', async () => {
- await createComponent({ workItemIid: '', isEditing: false });
+ describe('when group context', () => {
+ it('skips calling the project work item query', async () => {
+ await createComponent({ isGroup: true });
+
+ expect(workItemResponseHandler).not.toHaveBeenCalled();
+ });
+
+ it('calls the group work item query', async () => {
+ await createComponent({ isGroup: true });
- expect(workItemResponseHandler).not.toHaveBeenCalled();
+ expect(groupWorkItemResponseHandler).toHaveBeenCalled();
+ });
+
+ it('skips calling the group work item query when missing workItemIid', async () => {
+ await createComponent({ isGroup: true, workItemIid: '', isEditing: false });
+
+ expect(groupWorkItemResponseHandler).not.toHaveBeenCalled();
+ });
});
it('wrapper adds `internal-note` class when internal thread', async () => {
diff --git a/spec/frontend/work_items/components/notes/work_item_comment_form_spec.js b/spec/frontend/work_items/components/notes/work_item_comment_form_spec.js
index dd88f34ae4f..ee2b434bd75 100644
--- a/spec/frontend/work_items/components/notes/work_item_comment_form_spec.js
+++ b/spec/frontend/work_items/components/notes/work_item_comment_form_spec.js
@@ -48,6 +48,7 @@ describe('Work item comment form component', () => {
} = {}) => {
wrapper = shallowMount(WorkItemCommentForm, {
propsData: {
+ fullPath: 'test-project-path',
workItemState,
workItemId,
workItemType,
@@ -59,9 +60,6 @@ describe('Work item comment form component', () => {
autocompleteDataSources: {},
isNewDiscussion,
},
- provide: {
- fullPath: 'test-project-path',
- },
directives: {
GlTooltip: createMockDirective('gl-tooltip'),
},
diff --git a/spec/frontend/work_items/components/notes/work_item_discussion_spec.js b/spec/frontend/work_items/components/notes/work_item_discussion_spec.js
index 9d22a64f2cb..fa53ba54faa 100644
--- a/spec/frontend/work_items/components/notes/work_item_discussion_spec.js
+++ b/spec/frontend/work_items/components/notes/work_item_discussion_spec.js
@@ -31,10 +31,8 @@ describe('Work Item Discussion', () => {
workItemType = 'Task',
} = {}) => {
wrapper = shallowMount(WorkItemDiscussion, {
- provide: {
- fullPath: 'gitlab-org',
- },
propsData: {
+ fullPath: 'gitlab-org',
discussion,
workItemId,
workItemIid: '1',
diff --git a/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js b/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js
index e4180b2d178..6a24987b737 100644
--- a/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js
+++ b/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js
@@ -48,6 +48,7 @@ describe('Work Item Note Actions', () => {
} = {}) => {
wrapper = shallowMountExtended(WorkItemNoteActions, {
propsData: {
+ fullPath: 'gitlab-org',
showReply,
showEdit,
workItemIid: '1',
@@ -63,7 +64,6 @@ describe('Work Item Note Actions', () => {
projectName,
},
provide: {
- fullPath: 'gitlab-org',
glFeatures: {
workItemsMvc2: true,
},
diff --git a/spec/frontend/work_items/components/notes/work_item_note_awards_list_spec.js b/spec/frontend/work_items/components/notes/work_item_note_awards_list_spec.js
index d425f1e50dc..ce915635946 100644
--- a/spec/frontend/work_items/components/notes/work_item_note_awards_list_spec.js
+++ b/spec/frontend/work_items/components/notes/work_item_note_awards_list_spec.js
@@ -61,10 +61,8 @@ describe('Work Item Note Awards List', () => {
});
wrapper = shallowMount(WorkItemNoteAwardsList, {
- provide: {
- fullPath,
- },
propsData: {
+ fullPath,
workItemIid,
note,
isModal: false,
diff --git a/spec/frontend/work_items/components/notes/work_item_note_spec.js b/spec/frontend/work_items/components/notes/work_item_note_spec.js
index 9049a69656a..2b4c9604382 100644
--- a/spec/frontend/work_items/components/notes/work_item_note_spec.js
+++ b/spec/frontend/work_items/components/notes/work_item_note_spec.js
@@ -15,8 +15,10 @@ import NoteActions from '~/work_items/components/notes/work_item_note_actions.vu
import WorkItemCommentForm from '~/work_items/components/notes/work_item_comment_form.vue';
import updateWorkItemNoteMutation from '~/work_items/graphql/notes/update_work_item_note.mutation.graphql';
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
+import groupWorkItemByIidQuery from '~/work_items/graphql/group_work_item_by_iid.query.graphql';
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
import {
+ groupWorkItemByIidResponseFactory,
mockAssignees,
mockWorkItemCommentNote,
updateWorkItemMutationResponse,
@@ -68,6 +70,9 @@ describe('Work Item Note', () => {
});
const workItemResponseHandler = jest.fn().mockResolvedValue(workItemByIidResponseFactory());
+ const groupWorkItemResponseHandler = jest
+ .fn()
+ .mockResolvedValue(groupWorkItemByIidResponseFactory());
const workItemByAuthoredByDifferentUser = jest
.fn()
.mockResolvedValue(mockWorkItemByDifferentUser);
@@ -90,6 +95,7 @@ describe('Work Item Note', () => {
const createComponent = ({
note = mockWorkItemCommentNote,
isFirstNote = false,
+ isGroup = false,
updateNoteMutationHandler = successHandler,
workItemId = mockWorkItemId,
updateWorkItemMutationHandler = updateWorkItemMutationSuccessHandler,
@@ -98,9 +104,10 @@ describe('Work Item Note', () => {
} = {}) => {
wrapper = shallowMount(WorkItemNote, {
provide: {
- fullPath: 'test-project-path',
+ isGroup,
},
propsData: {
+ fullPath: 'test-project-path',
workItemId,
workItemIid: '1',
note,
@@ -112,6 +119,7 @@ describe('Work Item Note', () => {
},
apolloProvider: mockApollo([
[workItemByIidQuery, workItemByIidResponseHandler],
+ [groupWorkItemByIidQuery, groupWorkItemResponseHandler],
[updateWorkItemNoteMutation, updateNoteMutationHandler],
[updateWorkItemMutation, updateWorkItemMutationHandler],
]),
@@ -442,4 +450,32 @@ describe('Work Item Note', () => {
expect(findAwardsList().props('workItemIid')).toBe('1');
});
});
+
+ describe('when project context', () => {
+ it('calls the project work item query', () => {
+ createComponent();
+
+ expect(workItemResponseHandler).toHaveBeenCalled();
+ });
+
+ it('skips calling the group work item query', () => {
+ createComponent();
+
+ expect(groupWorkItemResponseHandler).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when group context', () => {
+ it('skips calling the project work item query', () => {
+ createComponent({ isGroup: true });
+
+ expect(workItemResponseHandler).not.toHaveBeenCalled();
+ });
+
+ it('calls the group work item query', () => {
+ createComponent({ isGroup: true });
+
+ expect(groupWorkItemResponseHandler).toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js b/spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js
index b86f9ff34ae..2e1a7983dec 100644
--- a/spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js
+++ b/spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js
@@ -1,4 +1,4 @@
-import { GlLabel, GlIcon } from '@gitlab/ui';
+import { GlLabel, GlIcon, GlLink } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -33,7 +33,7 @@ describe('WorkItemLinkChildContents', () => {
const findStatusIconComponent = () =>
wrapper.findByTestId('item-status-icon').findComponent(GlIcon);
const findConfidentialIconComponent = () => wrapper.findByTestId('confidential-icon');
- const findTitleEl = () => wrapper.findByTestId('item-title');
+ const findTitleEl = () => wrapper.findComponent(GlLink);
const findStatusTooltipComponent = () => wrapper.findComponent(RichTimestampTooltip);
const findMetadataComponent = () => wrapper.findComponent(WorkItemLinkChildMetadata);
const findAllLabels = () => wrapper.findAllComponents(GlLabel);
@@ -46,7 +46,6 @@ describe('WorkItemLinkChildContents', () => {
propsData: {
canUpdate,
childItem,
- childPath: '/gitlab-org/gitlab-test/-/work_items/4',
},
});
};
diff --git a/spec/frontend/work_items/components/work_item_actions_spec.js b/spec/frontend/work_items/components/work_item_actions_spec.js
index 0098a2e0864..15c33bf5b1e 100644
--- a/spec/frontend/work_items/components/work_item_actions_spec.js
+++ b/spec/frontend/work_items/components/work_item_actions_spec.js
@@ -22,13 +22,12 @@ import {
import updateWorkItemNotificationsMutation from '~/work_items/graphql/update_work_item_notifications.mutation.graphql';
import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
import convertWorkItemMutation from '~/work_items/graphql/work_item_convert.mutation.graphql';
-import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
import {
convertWorkItemMutationResponse,
projectWorkItemTypesQueryResponse,
convertWorkItemMutationErrorResponse,
- workItemByIidResponseFactory,
+ updateWorkItemNotificationsMutationResponse,
} from '../mock_data';
jest.mock('~/lib/utils/common_utils');
@@ -38,10 +37,7 @@ describe('WorkItemActions component', () => {
Vue.use(VueApollo);
let wrapper;
- let mockApollo;
const mockWorkItemReference = 'gitlab-org/gitlab-test#1';
- const mockWorkItemIid = '1';
- const mockFullPath = 'gitlab-org/gitlab-test';
const mockWorkItemCreateNoteEmail =
'gitlab-incoming+gitlab-org-gitlab-test-2-ddpzuq0zd2wefzofcpcdr3dg7-issue-1@gmail.com';
@@ -75,14 +71,22 @@ describe('WorkItemActions component', () => {
hide: jest.fn(),
};
+ const typesQuerySuccessHandler = jest.fn().mockResolvedValue(projectWorkItemTypesQueryResponse);
const convertWorkItemMutationSuccessHandler = jest
.fn()
.mockResolvedValue(convertWorkItemMutationResponse);
-
const convertWorkItemMutationErrorHandler = jest
.fn()
.mockResolvedValue(convertWorkItemMutationErrorResponse);
- const typesQuerySuccessHandler = jest.fn().mockResolvedValue(projectWorkItemTypesQueryResponse);
+ const toggleNotificationsOffHandler = jest
+ .fn()
+ .mockResolvedValue(updateWorkItemNotificationsMutationResponse(false));
+ const toggleNotificationsOnHandler = jest
+ .fn()
+ .mockResolvedValue(updateWorkItemNotificationsMutationResponse(true));
+ const toggleNotificationsFailureHandler = jest
+ .fn()
+ .mockRejectedValue(new Error('Failed to subscribe'));
const createComponent = ({
canUpdate = true,
@@ -90,35 +94,21 @@ describe('WorkItemActions component', () => {
isConfidential = false,
subscribed = false,
isParentConfidential = false,
- notificationsMock = [updateWorkItemNotificationsMutation, jest.fn()],
convertWorkItemMutationHandler = convertWorkItemMutationSuccessHandler,
+ notificationsMutationHandler,
workItemType = 'Task',
workItemReference = mockWorkItemReference,
workItemCreateNoteEmail = mockWorkItemCreateNoteEmail,
- writeQueryCache = false,
} = {}) => {
- const handlers = [notificationsMock];
- mockApollo = createMockApollo([
- ...handlers,
- [convertWorkItemMutation, convertWorkItemMutationHandler],
- [projectWorkItemTypesQuery, typesQuerySuccessHandler],
- ]);
-
- // Write the query cache only when required e.g., notification widget mutation is called
- if (writeQueryCache) {
- const workItemQueryResponse = workItemByIidResponseFactory({ canUpdate: true });
-
- mockApollo.clients.defaultClient.cache.writeQuery({
- query: workItemByIidQuery,
- variables: { fullPath: mockFullPath, iid: mockWorkItemIid },
- data: workItemQueryResponse.data,
- });
- }
-
wrapper = shallowMountExtended(WorkItemActions, {
isLoggedIn: isLoggedIn(),
- apolloProvider: mockApollo,
+ apolloProvider: createMockApollo([
+ [projectWorkItemTypesQuery, typesQuerySuccessHandler],
+ [convertWorkItemMutation, convertWorkItemMutationHandler],
+ [updateWorkItemNotificationsMutation, notificationsMutationHandler],
+ ]),
propsData: {
+ fullPath: 'gitlab-org/gitlab-test',
workItemId: 'gid://gitlab/WorkItem/1',
canUpdate,
canDelete,
@@ -128,10 +118,9 @@ describe('WorkItemActions component', () => {
workItemType,
workItemReference,
workItemCreateNoteEmail,
- workItemIid: '1',
},
provide: {
- fullPath: mockFullPath,
+ isGroup: false,
glFeatures: { workItemsMvc2: true },
},
mocks: {
@@ -159,7 +148,6 @@ describe('WorkItemActions component', () => {
it('renders modal', () => {
createComponent();
- expect(findModal().exists()).toBe(true);
expect(findModal().props('visible')).toBe(false);
});
@@ -247,59 +235,15 @@ describe('WorkItemActions component', () => {
});
it('does not render when canDelete is false', () => {
- createComponent({
- canDelete: false,
- });
+ createComponent({ canDelete: false });
expect(findDeleteButton().exists()).toBe(false);
});
});
describe('notifications action', () => {
- const errorMessage = 'Failed to subscribe';
- const notificationToggledOffMessage = 'Notifications turned off.';
- const notificationToggledOnMessage = 'Notifications turned on.';
-
- const toggleNotificationsOffHandler = jest.fn().mockResolvedValue({
- data: {
- updateWorkItemNotificationsSubscription: {
- issue: {
- id: 'gid://gitlab/WorkItem/1',
- subscribed: false,
- },
- errors: [],
- },
- },
- });
-
- const toggleNotificationsOnHandler = jest.fn().mockResolvedValue({
- data: {
- updateWorkItemNotificationsSubscription: {
- issue: {
- id: 'gid://gitlab/WorkItem/1',
- subscribed: true,
- },
- errors: [],
- },
- },
- });
-
- const toggleNotificationsFailureHandler = jest.fn().mockRejectedValue(new Error(errorMessage));
-
- const notificationsOffMock = [
- updateWorkItemNotificationsMutation,
- toggleNotificationsOffHandler,
- ];
-
- const notificationsOnMock = [updateWorkItemNotificationsMutation, toggleNotificationsOnHandler];
-
- const notificationsFailureMock = [
- updateWorkItemNotificationsMutation,
- toggleNotificationsFailureHandler,
- ];
-
beforeEach(() => {
- createComponent({ writeQueryCache: true });
+ createComponent();
isLoggedIn.mockReturnValue(true);
});
@@ -308,25 +252,26 @@ describe('WorkItemActions component', () => {
});
it.each`
- scenario | subscribedToNotifications | notificationsMock | subscribedState | toastMessage
- ${'turned off'} | ${false} | ${notificationsOffMock} | ${false} | ${notificationToggledOffMessage}
- ${'turned on'} | ${true} | ${notificationsOnMock} | ${true} | ${notificationToggledOnMessage}
+ scenario | subscribedToNotifications | notificationsMutationHandler | subscribed | toastMessage
+ ${'turned off'} | ${false} | ${toggleNotificationsOffHandler} | ${false} | ${'Notifications turned off.'}
+ ${'turned on'} | ${true} | ${toggleNotificationsOnHandler} | ${true} | ${'Notifications turned on.'}
`(
'calls mutation and displays toast when notification toggle is $scenario',
- async ({ subscribedToNotifications, notificationsMock, subscribedState, toastMessage }) => {
- createComponent({ notificationsMock, writeQueryCache: true });
-
- await waitForPromises();
+ async ({
+ subscribedToNotifications,
+ notificationsMutationHandler,
+ subscribed,
+ toastMessage,
+ }) => {
+ createComponent({ notificationsMutationHandler });
findNotificationsToggle().vm.$emit('change', subscribedToNotifications);
-
await waitForPromises();
- expect(notificationsMock[1]).toHaveBeenCalledWith({
+ expect(notificationsMutationHandler).toHaveBeenCalledWith({
input: {
- projectPath: mockFullPath,
- iid: mockWorkItemIid,
- subscribedState,
+ id: 'gid://gitlab/WorkItem/1',
+ subscribed,
},
});
expect(toast).toHaveBeenCalledWith(toastMessage);
@@ -334,15 +279,12 @@ describe('WorkItemActions component', () => {
);
it('emits error when the update notification mutation fails', async () => {
- createComponent({ notificationsMock: notificationsFailureMock, writeQueryCache: true });
-
- await waitForPromises();
+ createComponent({ notificationsMutationHandler: toggleNotificationsFailureHandler });
findNotificationsToggle().vm.$emit('change', false);
-
await waitForPromises();
- expect(wrapper.emitted('error')).toEqual([[errorMessage]]);
+ expect(wrapper.emitted('error')).toEqual([['Failed to subscribe']]);
});
});
@@ -359,13 +301,11 @@ describe('WorkItemActions component', () => {
it('promote key result to objective', async () => {
createComponent({ workItemType: 'Key Result' });
-
- // wait for work item types
await waitForPromises();
expect(findPromoteButton().exists()).toBe(true);
- findPromoteButton().vm.$emit('action');
+ findPromoteButton().vm.$emit('action');
await waitForPromises();
expect(convertWorkItemMutationSuccessHandler).toHaveBeenCalled();
@@ -378,13 +318,11 @@ describe('WorkItemActions component', () => {
workItemType: 'Key Result',
convertWorkItemMutationHandler: convertWorkItemMutationErrorHandler,
});
-
- // wait for work item types
await waitForPromises();
expect(findPromoteButton().exists()).toBe(true);
- findPromoteButton().vm.$emit('action');
+ findPromoteButton().vm.$emit('action');
await waitForPromises();
expect(convertWorkItemMutationErrorHandler).toHaveBeenCalled();
@@ -399,6 +337,7 @@ describe('WorkItemActions component', () => {
createComponent();
expect(findCopyReferenceButton().exists()).toBe(true);
+
findCopyReferenceButton().vm.$emit('action');
expect(toast).toHaveBeenCalledWith('Reference copied');
@@ -421,6 +360,7 @@ describe('WorkItemActions component', () => {
createComponent();
expect(findCopyCreateNoteEmailButton().exists()).toBe(true);
+
findCopyCreateNoteEmailButton().vm.$emit('action');
expect(toast).toHaveBeenCalledWith('Email address copied');
diff --git a/spec/frontend/work_items/components/work_item_assignees_spec.js b/spec/frontend/work_items/components/work_item_assignees_spec.js
index 50a8847032e..196e19791df 100644
--- a/spec/frontend/work_items/components/work_item_assignees_spec.js
+++ b/spec/frontend/work_items/components/work_item_assignees_spec.js
@@ -6,7 +6,8 @@ import waitForPromises from 'helpers/wait_for_promises';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { mockTracking } from 'helpers/tracking_helper';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
-import userSearchQuery from '~/graphql_shared/queries/users_search.query.graphql';
+import groupUsersSearchQuery from '~/graphql_shared/queries/group_users_search.query.graphql';
+import usersSearchQuery from '~/graphql_shared/queries/users_search.query.graphql';
import currentUserQuery from '~/graphql_shared/queries/current_user.query.graphql';
import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue';
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
@@ -53,6 +54,9 @@ describe('WorkItemAssignees component', () => {
const successSearchQueryHandler = jest
.fn()
.mockResolvedValue(projectMembersResponseWithCurrentUser);
+ const successGroupSearchQueryHandler = jest
+ .fn()
+ .mockResolvedValue(projectMembersResponseWithCurrentUser);
const successSearchQueryHandlerWithMoreAssignees = jest
.fn()
.mockResolvedValue(projectMembersResponseWithCurrentUserWithNextPage);
@@ -75,19 +79,22 @@ describe('WorkItemAssignees component', () => {
allowsMultipleAssignees = true,
canInviteMembers = false,
canUpdate = true,
+ isGroup = false,
} = {}) => {
const apolloProvider = createMockApollo([
- [userSearchQuery, searchQueryHandler],
+ [usersSearchQuery, searchQueryHandler],
+ [groupUsersSearchQuery, successGroupSearchQueryHandler],
[currentUserQuery, currentUserQueryHandler],
[updateWorkItemMutation, updateWorkItemMutationHandler],
]);
wrapper = mountExtended(WorkItemAssignees, {
provide: {
- fullPath: 'test-project-path',
+ isGroup,
},
propsData: {
assignees,
+ fullPath: 'test-project-path',
workItemId,
allowsMultipleAssignees,
workItemType: TASK_TYPE_NAME,
@@ -540,4 +547,36 @@ describe('WorkItemAssignees component', () => {
expect(findTokenSelector().props('dropdownItems')).toHaveLength(2);
});
+
+ describe('when project context', () => {
+ beforeEach(() => {
+ createComponent();
+ findTokenSelector().vm.$emit('focus');
+ findTokenSelector().vm.$emit('text-input', 'jane');
+ });
+
+ it('calls the project users search query', () => {
+ expect(successSearchQueryHandler).toHaveBeenCalled();
+ });
+
+ it('does not call the group users search query', () => {
+ expect(successGroupSearchQueryHandler).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when group context', () => {
+ beforeEach(() => {
+ createComponent({ isGroup: true });
+ findTokenSelector().vm.$emit('focus');
+ findTokenSelector().vm.$emit('text-input', 'jane');
+ });
+
+ it('does not call the project users search query', () => {
+ expect(successSearchQueryHandler).not.toHaveBeenCalled();
+ });
+
+ it('calls the group users search query', () => {
+ expect(successGroupSearchQueryHandler).toHaveBeenCalled();
+ });
+ });
});
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
index 8b7e04854af..123cf647674 100644
--- a/spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js
+++ b/spec/frontend/work_items/components/work_item_attributes_wrapper_spec.js
@@ -20,6 +20,7 @@ describe('WorkItemAttributesWrapper component', () => {
const createComponent = ({ workItem = workItemQueryResponse.data.workItem } = {}) => {
wrapper = shallowMount(WorkItemAttributesWrapper, {
propsData: {
+ fullPath: 'group/project',
workItem,
},
provide: {
@@ -28,7 +29,6 @@ describe('WorkItemAttributesWrapper component', () => {
hasOkrsFeature: true,
hasIssuableHealthStatusFeature: true,
projectNamespace: 'namespace',
- fullPath: 'group/project',
},
stubs: {
WorkItemWeight: true,
diff --git a/spec/frontend/work_items/components/work_item_created_updated_spec.js b/spec/frontend/work_items/components/work_item_created_updated_spec.js
index f77c5481906..3f14615e173 100644
--- a/spec/frontend/work_items/components/work_item_created_updated_spec.js
+++ b/spec/frontend/work_items/components/work_item_created_updated_spec.js
@@ -7,12 +7,18 @@ import waitForPromises from 'helpers/wait_for_promises';
import WorkItemCreatedUpdated from '~/work_items/components/work_item_created_updated.vue';
import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
+import groupWorkItemByIidQuery from '~/work_items/graphql/group_work_item_by_iid.query.graphql';
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
-import { workItemByIidResponseFactory, mockAssignees } from '../mock_data';
+import {
+ groupWorkItemByIidResponseFactory,
+ mockAssignees,
+ workItemByIidResponseFactory,
+} from '../mock_data';
describe('WorkItemCreatedUpdated component', () => {
let wrapper;
let successHandler;
+ let groupSuccessHandler;
Vue.use(VueApollo);
@@ -30,21 +36,31 @@ describe('WorkItemCreatedUpdated component', () => {
updatedAt,
confidential = false,
updateInProgress = false,
+ isGroup = false,
} = {}) => {
- const workItemQueryResponse = workItemByIidResponseFactory({
+ const workItemQueryResponse = workItemByIidResponseFactory({ author, updatedAt, confidential });
+ const groupWorkItemQueryResponse = groupWorkItemByIidResponseFactory({
author,
updatedAt,
confidential,
});
successHandler = jest.fn().mockResolvedValue(workItemQueryResponse);
+ groupSuccessHandler = jest.fn().mockResolvedValue(groupWorkItemQueryResponse);
wrapper = shallowMount(WorkItemCreatedUpdated, {
- apolloProvider: createMockApollo([[workItemByIidQuery, successHandler]]),
+ apolloProvider: createMockApollo([
+ [workItemByIidQuery, successHandler],
+ [groupWorkItemByIidQuery, groupSuccessHandler],
+ ]),
provide: {
+ isGroup,
+ },
+ propsData: {
fullPath: '/some/project',
+ workItemIid,
+ updateInProgress,
},
- propsData: { workItemIid, updateInProgress },
stubs: {
GlAvatarLink,
GlSprintf,
@@ -54,10 +70,44 @@ describe('WorkItemCreatedUpdated component', () => {
await waitForPromises();
};
- it('skips the work item query when workItemIid is not defined', async () => {
- await createComponent({ workItemIid: null });
+ describe('when project context', () => {
+ it('calls the project work item query', async () => {
+ await createComponent();
+
+ expect(successHandler).toHaveBeenCalled();
+ });
+
+ it('skips calling the group work item query', async () => {
+ await createComponent();
+
+ expect(groupSuccessHandler).not.toHaveBeenCalled();
+ });
+
+ it('skips calling the project work item query when workItemIid is not defined', async () => {
+ await createComponent({ workItemIid: null });
+
+ expect(successHandler).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when group context', () => {
+ it('skips calling the project work item query', async () => {
+ await createComponent({ isGroup: true });
+
+ expect(successHandler).not.toHaveBeenCalled();
+ });
+
+ it('calls the group work item query', async () => {
+ await createComponent({ isGroup: true });
- expect(successHandler).not.toHaveBeenCalled();
+ expect(groupSuccessHandler).toHaveBeenCalled();
+ });
+
+ it('skips calling the group work item query when workItemIid is not defined', async () => {
+ await createComponent({ isGroup: true, workItemIid: null });
+
+ expect(groupSuccessHandler).not.toHaveBeenCalled();
+ });
});
it('shows work item type metadata with type and icon', async () => {
diff --git a/spec/frontend/work_items/components/work_item_description_spec.js b/spec/frontend/work_items/components/work_item_description_spec.js
index 8b9963b2476..de2895591dd 100644
--- a/spec/frontend/work_items/components/work_item_description_spec.js
+++ b/spec/frontend/work_items/components/work_item_description_spec.js
@@ -13,9 +13,11 @@ import WorkItemDescription from '~/work_items/components/work_item_description.v
import WorkItemDescriptionRendered from '~/work_items/components/work_item_description_rendered.vue';
import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
+import groupWorkItemByIidQuery from '~/work_items/graphql/group_work_item_by_iid.query.graphql';
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
import { autocompleteDataSources, markdownPreviewPath } from '~/work_items/utils';
import {
+ groupWorkItemByIidResponseFactory,
updateWorkItemMutationResponse,
workItemByIidResponseFactory,
workItemQueryResponse,
@@ -33,6 +35,7 @@ describe('WorkItemDescription', () => {
const mutationSuccessHandler = jest.fn().mockResolvedValue(updateWorkItemMutationResponse);
let workItemResponseHandler;
+ let groupWorkItemResponseHandler;
const findForm = () => wrapper.findComponent(GlForm);
const findMarkdownEditor = () => wrapper.findComponent(MarkdownEditor);
@@ -51,22 +54,28 @@ describe('WorkItemDescription', () => {
canUpdate = true,
workItemResponse = workItemByIidResponseFactory({ canUpdate }),
isEditing = false,
+ isGroup = false,
workItemIid = '1',
} = {}) => {
workItemResponseHandler = jest.fn().mockResolvedValue(workItemResponse);
+ groupWorkItemResponseHandler = jest
+ .fn()
+ .mockResolvedValue(groupWorkItemByIidResponseFactory({ canUpdate }));
const { id } = workItemQueryResponse.data.workItem;
wrapper = shallowMount(WorkItemDescription, {
apolloProvider: createMockApollo([
[workItemByIidQuery, workItemResponseHandler],
+ [groupWorkItemByIidQuery, groupWorkItemResponseHandler],
[updateWorkItemMutation, mutationHandler],
]),
propsData: {
+ fullPath: 'test-project-path',
workItemId: id,
workItemIid,
},
provide: {
- fullPath: 'test-project-path',
+ isGroup,
},
});
@@ -247,9 +256,31 @@ describe('WorkItemDescription', () => {
});
});
- it('calls the work item query', async () => {
- await createComponent();
+ describe('when project context', () => {
+ it('calls the project work item query', () => {
+ createComponent();
+
+ expect(workItemResponseHandler).toHaveBeenCalled();
+ });
- expect(workItemResponseHandler).toHaveBeenCalled();
+ it('skips calling the group work item query', () => {
+ createComponent();
+
+ expect(groupWorkItemResponseHandler).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when group context', () => {
+ it('skips calling the project work item query', () => {
+ createComponent({ isGroup: true });
+
+ expect(workItemResponseHandler).not.toHaveBeenCalled();
+ });
+
+ it('calls the group work item query', () => {
+ createComponent({ isGroup: true });
+
+ expect(groupWorkItemResponseHandler).toHaveBeenCalled();
+ });
});
});
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 fec6d0673c6..28826748cb0 100644
--- a/spec/frontend/work_items/components/work_item_detail_spec.js
+++ b/spec/frontend/work_items/components/work_item_detail_spec.js
@@ -28,12 +28,14 @@ import WorkItemStateToggleButton from '~/work_items/components/work_item_state_t
import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
import WorkItemTodos from '~/work_items/components/work_item_todos.vue';
import { i18n } from '~/work_items/constants';
+import groupWorkItemByIidQuery from '~/work_items/graphql/group_work_item_by_iid.query.graphql';
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.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 workItemUpdatedSubscription from '~/work_items/graphql/work_item_updated.subscription.graphql';
import {
+ groupWorkItemByIidResponseFactory,
mockParent,
workItemByIidResponseFactory,
objectiveType,
@@ -49,6 +51,10 @@ describe('WorkItemDetail component', () => {
Vue.use(VueApollo);
const workItemQueryResponse = workItemByIidResponseFactory({ canUpdate: true, canDelete: true });
+ const groupWorkItemQueryResponse = groupWorkItemByIidResponseFactory({
+ canUpdate: true,
+ canDelete: true,
+ });
const workItemQueryResponseWithCannotUpdate = workItemByIidResponseFactory({
canUpdate: false,
canDelete: false,
@@ -59,6 +65,7 @@ describe('WorkItemDetail component', () => {
canDelete: true,
});
const successHandler = jest.fn().mockResolvedValue(workItemQueryResponse);
+ const groupSuccessHandler = jest.fn().mockResolvedValue(groupWorkItemQueryResponse);
const showModalHandler = jest.fn();
const { id } = workItemQueryResponse.data.workspace.workItems.nodes[0];
const workItemUpdatedSubscriptionHandler = jest
@@ -92,6 +99,7 @@ describe('WorkItemDetail component', () => {
const findWorkItemTypeIcon = () => wrapper.findComponent(WorkItemTypeIcon);
const createComponent = ({
+ isGroup = false,
isModal = false,
updateInProgress = false,
workItemIid = '1',
@@ -101,14 +109,13 @@ describe('WorkItemDetail component', () => {
workItemsMvc2Enabled = false,
linkedWorkItemsEnabled = false,
} = {}) => {
- const handlers = [
- [workItemByIidQuery, handler],
- [workItemUpdatedSubscription, workItemUpdatedSubscriptionHandler],
- confidentialityMock,
- ];
-
wrapper = shallowMountExtended(WorkItemDetail, {
- apolloProvider: createMockApollo(handlers),
+ apolloProvider: createMockApollo([
+ [workItemByIidQuery, handler],
+ [groupWorkItemByIidQuery, groupSuccessHandler],
+ [workItemUpdatedSubscription, workItemUpdatedSubscriptionHandler],
+ confidentialityMock,
+ ]),
isLoggedIn: isLoggedIn(),
propsData: {
isModal,
@@ -131,6 +138,7 @@ describe('WorkItemDetail component', () => {
hasIssuableHealthStatusFeature: true,
projectNamespace: 'namespace',
fullPath: 'group/project',
+ isGroup,
reportAbusePath: '/report/abuse/path',
},
stubs: {
@@ -484,25 +492,64 @@ describe('WorkItemDetail component', () => {
expect(findAlert().text()).toBe(updateError);
});
- it('calls the work item query', async () => {
- createComponent();
- await waitForPromises();
+ describe('when project context', () => {
+ it('calls the project work item query', async () => {
+ createComponent();
+ await waitForPromises();
- expect(successHandler).toHaveBeenCalledWith({ fullPath: 'group/project', iid: '1' });
- });
+ expect(successHandler).toHaveBeenCalledWith({ fullPath: 'group/project', iid: '1' });
+ });
- it('skips the work item query when there is no workItemIid', async () => {
- createComponent({ workItemIid: null });
- await waitForPromises();
+ it('skips calling the group work item query', async () => {
+ createComponent();
+ await waitForPromises();
+
+ expect(groupSuccessHandler).not.toHaveBeenCalled();
+ });
- expect(successHandler).not.toHaveBeenCalled();
+ it('skips calling the project work item query when there is no workItemIid', async () => {
+ createComponent({ workItemIid: null });
+ await waitForPromises();
+
+ expect(successHandler).not.toHaveBeenCalled();
+ });
+
+ it('calls the project work item query when isModal=true', async () => {
+ createComponent({ isModal: true });
+ await waitForPromises();
+
+ expect(successHandler).toHaveBeenCalledWith({ fullPath: 'group/project', iid: '1' });
+ });
});
- it('calls the work item query when isModal=true', async () => {
- createComponent({ isModal: true });
- await waitForPromises();
+ describe('when group context', () => {
+ it('skips calling the project work item query', async () => {
+ createComponent({ isGroup: true });
+ await waitForPromises();
+
+ expect(successHandler).not.toHaveBeenCalled();
+ });
+
+ it('calls the group work item query', async () => {
+ createComponent({ isGroup: true });
+ await waitForPromises();
+
+ expect(groupSuccessHandler).toHaveBeenCalledWith({ fullPath: 'group/project', iid: '1' });
+ });
+
+ it('skips calling the group work item query when there is no workItemIid', async () => {
+ createComponent({ isGroup: true, workItemIid: null });
+ await waitForPromises();
- expect(successHandler).toHaveBeenCalledWith({ fullPath: 'group/project', iid: '1' });
+ expect(groupSuccessHandler).not.toHaveBeenCalled();
+ });
+
+ it('calls the group work item query when isModal=true', async () => {
+ createComponent({ isGroup: true, isModal: true });
+ await waitForPromises();
+
+ expect(groupSuccessHandler).toHaveBeenCalledWith({ fullPath: 'group/project', iid: '1' });
+ });
});
describe('hierarchy widget', () => {
diff --git a/spec/frontend/work_items/components/work_item_labels_spec.js b/spec/frontend/work_items/components/work_item_labels_spec.js
index 4a20e654060..28aa7ffa1be 100644
--- a/spec/frontend/work_items/components/work_item_labels_spec.js
+++ b/spec/frontend/work_items/components/work_item_labels_spec.js
@@ -7,10 +7,12 @@ import { mountExtended } from 'helpers/vue_test_utils_helper';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import labelSearchQuery from '~/sidebar/components/labels/labels_select_widget/graphql/project_labels.query.graphql';
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
+import groupWorkItemByIidQuery from '~/work_items/graphql/group_work_item_by_iid.query.graphql';
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
import WorkItemLabels from '~/work_items/components/work_item_labels.vue';
import { i18n, I18N_WORK_ITEM_ERROR_FETCHING_LABELS } from '~/work_items/constants';
import {
+ groupWorkItemByIidResponseFactory,
projectLabelsResponse,
mockLabels,
workItemByIidResponseFactory,
@@ -32,6 +34,9 @@ describe('WorkItemLabels component', () => {
const workItemQuerySuccess = jest
.fn()
.mockResolvedValue(workItemByIidResponseFactory({ labels: null }));
+ const groupWorkItemQuerySuccess = jest
+ .fn()
+ .mockResolvedValue(groupWorkItemByIidResponseFactory({ labels: null }));
const successSearchQueryHandler = jest.fn().mockResolvedValue(projectLabelsResponse);
const successUpdateWorkItemMutationHandler = jest
.fn()
@@ -40,6 +45,7 @@ describe('WorkItemLabels component', () => {
const createComponent = ({
canUpdate = true,
+ isGroup = false,
workItemQueryHandler = workItemQuerySuccess,
searchQueryHandler = successSearchQueryHandler,
updateWorkItemMutationHandler = successUpdateWorkItemMutationHandler,
@@ -48,13 +54,15 @@ describe('WorkItemLabels component', () => {
wrapper = mountExtended(WorkItemLabels, {
apolloProvider: createMockApollo([
[workItemByIidQuery, workItemQueryHandler],
+ [groupWorkItemByIidQuery, groupWorkItemQuerySuccess],
[labelSearchQuery, searchQueryHandler],
[updateWorkItemMutation, updateWorkItemMutationHandler],
]),
provide: {
- fullPath: 'test-project-path',
+ isGroup,
},
propsData: {
+ fullPath: 'test-project-path',
workItemId,
workItemIid,
canUpdate,
@@ -244,17 +252,49 @@ describe('WorkItemLabels component', () => {
});
});
- it('calls the work item query', async () => {
- createComponent();
- await waitForPromises();
+ describe('when project context', () => {
+ it('calls the project work item query', async () => {
+ createComponent();
+ await waitForPromises();
+
+ expect(workItemQuerySuccess).toHaveBeenCalled();
+ });
+
+ it('skips calling the group work item query', async () => {
+ createComponent();
+ await waitForPromises();
+
+ expect(groupWorkItemQuerySuccess).not.toHaveBeenCalled();
+ });
- expect(workItemQuerySuccess).toHaveBeenCalled();
+ it('skips calling the project work item query when missing workItemIid', async () => {
+ createComponent({ workItemIid: '' });
+ await waitForPromises();
+
+ expect(workItemQuerySuccess).not.toHaveBeenCalled();
+ });
});
- it('skips calling the work item query when missing workItemIid', async () => {
- createComponent({ workItemIid: '' });
- await waitForPromises();
+ describe('when group context', () => {
+ it('skips calling the project work item query', async () => {
+ createComponent({ isGroup: true });
+ await waitForPromises();
+
+ expect(workItemQuerySuccess).not.toHaveBeenCalled();
+ });
- expect(workItemQuerySuccess).not.toHaveBeenCalled();
+ it('calls the group work item query', async () => {
+ createComponent({ isGroup: true });
+ await waitForPromises();
+
+ expect(groupWorkItemQuerySuccess).toHaveBeenCalled();
+ });
+
+ it('skips calling the group work item query when missing workItemIid', async () => {
+ createComponent({ isGroup: true, workItemIid: '' });
+ await waitForPromises();
+
+ expect(groupWorkItemQuerySuccess).not.toHaveBeenCalled();
+ });
});
});
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 cd077fbf705..0147b199040 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
@@ -53,9 +53,10 @@ describe('WorkItemChildrenWrapper', () => {
wrapper = shallowMountExtended(WorkItemChildrenWrapper, {
apolloProvider: mockApollo,
provide: {
- fullPath: 'test/project',
+ isGroup: false,
},
propsData: {
+ fullPath: 'test/project',
workItemType,
workItemId: 'gid://gitlab/WorkItem/515',
workItemIid: '1',
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js
index a624bbe8567..9addf6c3450 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js
@@ -62,9 +62,6 @@ describe('WorkItemLinkChild', () => {
[getWorkItemTreeQuery, getWorkItemTreeQueryHandler],
[updateWorkItemMutation, mutationChangeParentHandler],
]),
- provide: {
- fullPath: 'gitlab-org/gitlab-test',
- },
propsData: {
canUpdate,
issuableGid,
@@ -93,23 +90,7 @@ describe('WorkItemLinkChild', () => {
expect(findWorkItemLinkChildContents().props()).toEqual({
childItem: workItemObjectiveWithChild,
canUpdate: true,
- childPath: '/gitlab-org/gitlab-test/-/work_items/12',
- });
- });
-
- describe('with relative instance', () => {
- beforeEach(() => {
- window.gon = { relative_url_root: '/test' };
- createComponent({
- childItem: workItemObjectiveWithChild,
- workItemType: WORK_ITEM_TYPE_VALUE_OBJECTIVE,
- });
- });
-
- it('adds the relative url to child path value', () => {
- expect(findWorkItemLinkChildContents().props('childPath')).toBe(
- '/test/gitlab-org/gitlab-test/-/work_items/12',
- );
+ showTaskIcon: false,
});
});
});
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js
index aaab22fd18d..0a9da17d284 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js
@@ -54,6 +54,7 @@ describe('WorkItemLinksForm', () => {
[createWorkItemMutation, createMutationResolver],
]),
propsData: {
+ fullPath: 'project/path',
issuableGid: 'gid://gitlab/WorkItem/1',
parentConfidential,
parentIteration,
@@ -62,8 +63,8 @@ describe('WorkItemLinksForm', () => {
formType,
},
provide: {
- fullPath: 'project/path',
hasIterationsFeature,
+ isGroup: false,
},
});
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 e24cfe27616..0b88b3ff5b4 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
@@ -13,9 +13,11 @@ 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 groupWorkItemByIidQuery from '~/work_items/graphql/group_work_item_by_iid.query.graphql';
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
import {
getIssueDetailsResponse,
+ groupWorkItemByIidResponseFactory,
workItemHierarchyResponse,
workItemHierarchyEmptyResponse,
workItemHierarchyNoUpdatePermissionResponse,
@@ -32,6 +34,9 @@ describe('WorkItemLinks', () => {
let mockApollo;
const responseWithAddChildPermission = jest.fn().mockResolvedValue(workItemHierarchyResponse);
+ const groupResponseWithAddChildPermission = jest
+ .fn()
+ .mockResolvedValue(groupWorkItemByIidResponseFactory());
const responseWithoutAddChildPermission = jest
.fn()
.mockResolvedValue(workItemByIidResponseFactory({ adminParentLink: false }));
@@ -40,20 +45,22 @@ describe('WorkItemLinks', () => {
fetchHandler = responseWithAddChildPermission,
issueDetailsQueryHandler = jest.fn().mockResolvedValue(getIssueDetailsResponse()),
hasIterationsFeature = false,
+ isGroup = false,
} = {}) => {
mockApollo = createMockApollo(
[
[workItemByIidQuery, fetchHandler],
+ [groupWorkItemByIidQuery, groupResponseWithAddChildPermission],
[issueDetailsQuery, issueDetailsQueryHandler],
],
resolvers,
- { addTypename: true },
);
wrapper = shallowMountExtended(WorkItemLinks, {
provide: {
fullPath: 'project/path',
hasIterationsFeature,
+ isGroup,
reportAbusePath: '/report/abuse/path',
},
propsData: {
@@ -243,4 +250,32 @@ describe('WorkItemLinks', () => {
expect(findAbuseCategorySelector().exists()).toBe(false);
});
});
+
+ describe('when project context', () => {
+ it('calls the project work item query', () => {
+ createComponent();
+
+ expect(responseWithAddChildPermission).toHaveBeenCalled();
+ });
+
+ it('skips calling the group work item query', () => {
+ createComponent();
+
+ expect(groupResponseWithAddChildPermission).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when group context', () => {
+ it('skips calling the project work item query', () => {
+ createComponent({ isGroup: true });
+
+ expect(responseWithAddChildPermission).not.toHaveBeenCalled();
+ });
+
+ it('calls the group work item query', () => {
+ createComponent({ isGroup: true });
+
+ expect(groupResponseWithAddChildPermission).toHaveBeenCalled();
+ });
+ });
});
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 01fa4591cde..f30fded0b45 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
@@ -29,10 +29,8 @@ describe('WorkItemTree', () => {
canUpdate = true,
} = {}) => {
wrapper = shallowMountExtended(WorkItemTree, {
- provide: {
- fullPath: 'test/project',
- },
propsData: {
+ fullPath: 'test/project',
workItemType,
parentWorkItemType,
workItemId: 'gid://gitlab/WorkItem/515',
diff --git a/spec/frontend/work_items/components/work_item_milestone_spec.js b/spec/frontend/work_items/components/work_item_milestone_spec.js
index c42c9a573e5..e303ad4b481 100644
--- a/spec/frontend/work_items/components/work_item_milestone_spec.js
+++ b/spec/frontend/work_items/components/work_item_milestone_spec.js
@@ -66,10 +66,8 @@ describe('WorkItemMilestone component', () => {
[projectMilestonesQuery, searchQueryHandler],
[updateWorkItemMutation, mutationHandler],
]),
- provide: {
- fullPath: 'full-path',
- },
propsData: {
+ fullPath: 'full-path',
canUpdate,
workItemMilestone: milestone,
workItemId,
diff --git a/spec/frontend/work_items/components/work_item_notes_spec.js b/spec/frontend/work_items/components/work_item_notes_spec.js
index 35f01c85ec8..9e02e0708d4 100644
--- a/spec/frontend/work_items/components/work_item_notes_spec.js
+++ b/spec/frontend/work_items/components/work_item_notes_spec.js
@@ -98,10 +98,8 @@ describe('WorkItemNotes component', () => {
[workItemNoteUpdatedSubscription, notesUpdateSubscriptionHandler],
[workItemNoteDeletedSubscription, notesDeleteSubscriptionHandler],
]),
- provide: {
- fullPath: 'test-path',
- },
propsData: {
+ fullPath: 'test-path',
workItemId,
workItemIid,
workItemType: 'task',
diff --git a/spec/frontend/work_items/components/work_item_parent_spec.js b/spec/frontend/work_items/components/work_item_parent_spec.js
new file mode 100644
index 00000000000..a72eeabc43c
--- /dev/null
+++ b/spec/frontend/work_items/components/work_item_parent_spec.js
@@ -0,0 +1,236 @@
+import * as Sentry from '@sentry/browser';
+import { GlCollapsibleListbox, GlFormGroup } from '@gitlab/ui';
+
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import waitForPromises from 'helpers/wait_for_promises';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+
+import WorkItemParent from '~/work_items/components/work_item_parent.vue';
+import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
+import projectWorkItemsQuery from '~/work_items/graphql/project_work_items.query.graphql';
+import { WORK_ITEM_TYPE_ENUM_OBJECTIVE } from '~/work_items/constants';
+
+import {
+ availableObjectivesResponse,
+ mockParentWidgetResponse,
+ updateWorkItemMutationResponseFactory,
+ searchedObjectiveResponse,
+ updateWorkItemMutationErrorResponse,
+} from '../mock_data';
+
+jest.mock('@sentry/browser');
+
+describe('WorkItemParent component', () => {
+ Vue.use(VueApollo);
+
+ let wrapper;
+
+ const workItemId = 'gid://gitlab/WorkItem/1';
+ const workItemType = 'Objective';
+
+ const availableWorkItemsSuccessHandler = jest.fn().mockResolvedValue(availableObjectivesResponse);
+ const availableWorkItemsFailureHandler = jest.fn().mockRejectedValue(new Error());
+
+ const successUpdateWorkItemMutationHandler = jest
+ .fn()
+ .mockResolvedValue(updateWorkItemMutationResponseFactory({ parent: mockParentWidgetResponse }));
+
+ const createComponent = ({
+ canUpdate = true,
+ parent = null,
+ searchQueryHandler = availableWorkItemsSuccessHandler,
+ mutationHandler = successUpdateWorkItemMutationHandler,
+ } = {}) => {
+ wrapper = shallowMountExtended(WorkItemParent, {
+ apolloProvider: createMockApollo([
+ [projectWorkItemsQuery, searchQueryHandler],
+ [updateWorkItemMutation, mutationHandler],
+ ]),
+ provide: {
+ fullPath: 'full-path',
+ },
+ propsData: {
+ canUpdate,
+ parent,
+ workItemId,
+ workItemType,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ const findInputGroup = () => wrapper.findComponent(GlFormGroup);
+ const findParentText = () => wrapper.findByTestId('disabled-text');
+ const findCollapsibleListbox = () => wrapper.findComponent(GlCollapsibleListbox);
+
+ describe('template', () => {
+ it('shows field label as Parent', () => {
+ expect(findInputGroup().exists()).toBe(true);
+ expect(findInputGroup().attributes('label')).toBe('Parent');
+ });
+
+ it('renders the collapsible listbox with required props', () => {
+ expect(findCollapsibleListbox().exists()).toBe(true);
+ expect(findCollapsibleListbox().props()).toMatchObject({
+ items: [],
+ headerText: 'Assign parent',
+ category: 'tertiary',
+ loading: false,
+ noCaret: true,
+ isCheckCentered: true,
+ searchable: true,
+ searching: false,
+ infiniteScroll: false,
+ noResultsText: 'No matching results',
+ toggleText: 'None',
+ searchPlaceholder: 'Search',
+ resetButtonLabel: 'Unassign',
+ block: true,
+ });
+ });
+
+ it('displays parent text instead of listbox if canUpdate is false', () => {
+ createComponent({ canUpdate: false, parent: mockParentWidgetResponse });
+
+ expect(findCollapsibleListbox().exists()).toBe(false);
+ expect(findParentText().exists()).toBe(true);
+ expect(findParentText().text()).toBe('Objective 101');
+ });
+
+ it('shows loading while searching', async () => {
+ await findCollapsibleListbox().vm.$emit('shown');
+ expect(findCollapsibleListbox().props('searching')).toBe(true);
+ expect(findCollapsibleListbox().props('no-caret')).toBeUndefined();
+ });
+ });
+
+ describe('work items query', () => {
+ it('loads work items in the listbox', async () => {
+ await findCollapsibleListbox().vm.$emit('shown');
+
+ await waitForPromises();
+
+ expect(findCollapsibleListbox().props('searching')).toBe(false);
+ expect(findCollapsibleListbox().props('items')).toStrictEqual([
+ { text: 'Objective 101', value: 'gid://gitlab/WorkItem/716' },
+ { text: 'Objective 103', value: 'gid://gitlab/WorkItem/712' },
+ { text: 'Objective 102', value: 'gid://gitlab/WorkItem/711' },
+ ]);
+ expect(availableWorkItemsSuccessHandler).toHaveBeenCalled();
+ });
+
+ it('emits error when the query fails', async () => {
+ createComponent({ searchQueryHandler: availableWorkItemsFailureHandler });
+
+ await findCollapsibleListbox().vm.$emit('shown');
+
+ await waitForPromises();
+
+ expect(wrapper.emitted('error')).toEqual([
+ ['Something went wrong while fetching items. Please try again.'],
+ ]);
+ });
+
+ it('searches item when input data is entered', async () => {
+ const searchedItemQueryHandler = jest.fn().mockResolvedValue(searchedObjectiveResponse);
+ createComponent({
+ searchQueryHandler: searchedItemQueryHandler,
+ });
+
+ await findCollapsibleListbox().vm.$emit('shown');
+ await findCollapsibleListbox().vm.$emit('search', 'Objective 101');
+
+ await waitForPromises();
+
+ expect(searchedItemQueryHandler).toHaveBeenCalledWith({
+ fullPath: 'full-path',
+ searchTerm: 'Objective 101',
+ types: [WORK_ITEM_TYPE_ENUM_OBJECTIVE],
+ in: 'TITLE',
+ });
+
+ await nextTick();
+
+ expect(findCollapsibleListbox().props('items')).toStrictEqual([
+ { text: 'Objective 101', value: 'gid://gitlab/WorkItem/716' },
+ ]);
+ });
+ });
+
+ describe('listbox', () => {
+ const selectWorkItem = async (workItem) => {
+ await findCollapsibleListbox().vm.$emit('shown');
+ await findCollapsibleListbox().vm.$emit('select', workItem);
+ };
+
+ it('calls mutation when item is selected', async () => {
+ selectWorkItem('gid://gitlab/WorkItem/716');
+
+ await waitForPromises();
+
+ expect(successUpdateWorkItemMutationHandler).toHaveBeenCalledWith({
+ input: {
+ id: 'gid://gitlab/WorkItem/1',
+ hierarchyWidget: {
+ parentId: 'gid://gitlab/WorkItem/716',
+ },
+ },
+ });
+ });
+
+ it('calls mutation when item is unassigned', async () => {
+ const unAssignParentWorkItemMutationHandler = jest
+ .fn()
+ .mockResolvedValue(updateWorkItemMutationResponseFactory({ parent: null }));
+ createComponent({
+ mutationHandler: unAssignParentWorkItemMutationHandler,
+ });
+
+ await findCollapsibleListbox().vm.$emit('reset');
+
+ await waitForPromises();
+
+ expect(unAssignParentWorkItemMutationHandler).toHaveBeenCalledWith({
+ input: {
+ id: 'gid://gitlab/WorkItem/1',
+ hierarchyWidget: {
+ parentId: null,
+ },
+ },
+ });
+ });
+
+ it('emits error when mutation fails', async () => {
+ createComponent({
+ mutationHandler: jest.fn().mockResolvedValue(updateWorkItemMutationErrorResponse),
+ });
+
+ selectWorkItem('gid://gitlab/WorkItem/716');
+
+ await waitForPromises();
+
+ expect(wrapper.emitted('error')).toEqual([['Error!']]);
+ });
+
+ it('emits error and captures exception in sentry when network request fails', async () => {
+ const error = new Error('error');
+ createComponent({
+ mutationHandler: jest.fn().mockRejectedValue(error),
+ });
+
+ selectWorkItem('gid://gitlab/WorkItem/716');
+
+ await waitForPromises();
+
+ expect(wrapper.emitted('error')).toEqual([
+ ['Something went wrong while updating the objective. Please try again.'],
+ ]);
+ expect(Sentry.captureException).toHaveBeenCalledWith(error);
+ });
+ });
+});
diff --git a/spec/frontend/work_items/components/work_item_relationships/__snapshots__/work_item_relationship_list_spec.js.snap b/spec/frontend/work_items/components/work_item_relationships/__snapshots__/work_item_relationship_list_spec.js.snap
index 9105e4de5e0..bbc19a011a5 100644
--- a/spec/frontend/work_items/components/work_item_relationships/__snapshots__/work_item_relationship_list_spec.js.snap
+++ b/spec/frontend/work_items/components/work_item_relationships/__snapshots__/work_item_relationship_list_spec.js.snap
@@ -1,7 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`WorkItemRelationshipList renders linked item list 1`] = `
-<div>
+<div
+ data-testid="work-item-linked-items-list"
+>
<h4
class="gl-font-sm gl-font-weight-semibold gl-mb-2 gl-mt-3 gl-mx-2 gl-text-gray-700"
data-testid="work-items-list-heading"
@@ -20,7 +22,7 @@ exports[`WorkItemRelationshipList renders linked item list 1`] = `
<work-item-link-child-contents-stub
canupdate="true"
childitem="[object Object]"
- childpath="/test-project-path/-/work_items/83"
+ showtaskicon="true"
/>
</li>
</ul>
diff --git a/spec/frontend/work_items/components/work_item_relationships/work_item_add_relationship_form_spec.js b/spec/frontend/work_items/components/work_item_relationships/work_item_add_relationship_form_spec.js
new file mode 100644
index 00000000000..d7b3ced2ff9
--- /dev/null
+++ b/spec/frontend/work_items/components/work_item_relationships/work_item_add_relationship_form_spec.js
@@ -0,0 +1,156 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { GlForm, GlFormRadioGroup, GlAlert } from '@gitlab/ui';
+
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+
+import WorkItemAddRelationshipForm from '~/work_items/components/work_item_relationships/work_item_add_relationship_form.vue';
+import WorkItemTokenInput from '~/work_items/components/shared/work_item_token_input.vue';
+import addLinkedItemsMutation from '~/work_items/graphql/add_linked_items.mutation.graphql';
+import { LINKED_ITEM_TYPE_VALUE, MAX_WORK_ITEMS } from '~/work_items/constants';
+
+import { linkedWorkItemResponse, generateWorkItemsListWithId } from '../../mock_data';
+
+describe('WorkItemAddRelationshipForm', () => {
+ Vue.use(VueApollo);
+
+ let wrapper;
+ const linkedWorkItemsSuccessMutationHandler = jest
+ .fn()
+ .mockResolvedValue(linkedWorkItemResponse());
+
+ const createComponent = async ({
+ workItemId = 'gid://gitlab/WorkItem/1',
+ workItemIid = '1',
+ workItemType = 'Objective',
+ childrenIds = [],
+ linkedWorkItemsMutationHandler = linkedWorkItemsSuccessMutationHandler,
+ } = {}) => {
+ const mockApolloProvider = createMockApollo([
+ [addLinkedItemsMutation, linkedWorkItemsMutationHandler],
+ ]);
+
+ wrapper = shallowMountExtended(WorkItemAddRelationshipForm, {
+ apolloProvider: mockApolloProvider,
+ propsData: {
+ workItemId,
+ workItemIid,
+ workItemFullPath: 'test-project-path',
+ workItemType,
+ childrenIds,
+ },
+ });
+
+ await waitForPromises();
+ };
+
+ const findLinkWorkItemForm = () => wrapper.findComponent(GlForm);
+ const findLinkWorkItemButton = () => wrapper.findByTestId('link-work-item-button');
+ const findMaxWorkItemNote = () => wrapper.findByTestId('max-work-item-note');
+ const findRadioGroup = () => wrapper.findComponent(GlFormRadioGroup);
+ const findWorkItemTokenInput = () => wrapper.findComponent(WorkItemTokenInput);
+ const findGlAlert = () => wrapper.findComponent(GlAlert);
+
+ beforeEach(async () => {
+ await createComponent();
+ });
+
+ it('renders link work item form with default values', () => {
+ expect(findLinkWorkItemForm().exists()).toBe(true);
+ expect(findRadioGroup().props('options')).toEqual([
+ { text: 'relates to', value: LINKED_ITEM_TYPE_VALUE.RELATED },
+ { text: 'blocks', value: LINKED_ITEM_TYPE_VALUE.BLOCKS },
+ { text: 'is blocked by', value: LINKED_ITEM_TYPE_VALUE.BLOCKED_BY },
+ ]);
+ expect(findLinkWorkItemButton().attributes('disabled')).toBe('true');
+ expect(findMaxWorkItemNote().text()).toBe('Add a maximum of 10 items at a time.');
+ });
+
+ it('renders work item token input with default props', () => {
+ expect(findWorkItemTokenInput().props()).toMatchObject({
+ value: [],
+ fullPath: 'test-project-path',
+ childrenIds: [],
+ parentWorkItemId: 'gid://gitlab/WorkItem/1',
+ areWorkItemsToAddValid: true,
+ });
+ });
+
+ describe('linking a work item', () => {
+ const selectWorkItemTokens = (workItems) => {
+ findWorkItemTokenInput().vm.$emit('input', workItems);
+ };
+
+ it('enables add button when work item is selected', async () => {
+ await selectWorkItemTokens([
+ {
+ id: 'gid://gitlab/WorkItem/644',
+ },
+ ]);
+ expect(findLinkWorkItemButton().attributes('disabled')).toBeUndefined();
+ });
+
+ it('disables button when more than 10 work items are selected', async () => {
+ await selectWorkItemTokens(generateWorkItemsListWithId(MAX_WORK_ITEMS + 1));
+
+ expect(findWorkItemTokenInput().props('areWorkItemsToAddValid')).toBe(false);
+ expect(findLinkWorkItemButton().attributes('disabled')).toBe('true');
+ });
+
+ it.each`
+ assertionName | linkTypeInput
+ ${'related'} | ${LINKED_ITEM_TYPE_VALUE.RELATED}
+ ${'blocking'} | ${LINKED_ITEM_TYPE_VALUE.BLOCKED_BY}
+ `('selects and links $assertionName work item', async ({ linkTypeInput }) => {
+ findRadioGroup().vm.$emit('input', linkTypeInput);
+ await selectWorkItemTokens([
+ {
+ id: 'gid://gitlab/WorkItem/641',
+ },
+ {
+ id: 'gid://gitlab/WorkItem/642',
+ },
+ ]);
+
+ expect(findWorkItemTokenInput().props('areWorkItemsToAddValid')).toBe(true);
+
+ findLinkWorkItemForm().vm.$emit('submit', {
+ preventDefault: jest.fn(),
+ stopPropagation: jest.fn(),
+ });
+ await waitForPromises();
+
+ expect(linkedWorkItemsSuccessMutationHandler).toHaveBeenCalledWith({
+ input: {
+ id: 'gid://gitlab/WorkItem/1',
+ linkType: linkTypeInput,
+ workItemsIds: ['gid://gitlab/WorkItem/641', 'gid://gitlab/WorkItem/642'],
+ },
+ });
+ });
+
+ it.each`
+ errorType | mutationMock | errorMessage
+ ${'an error in the mutation response'} | ${jest.fn().mockResolvedValue(linkedWorkItemResponse({}, ['Linked Item failed']))} | ${'Linked Item failed'}
+ ${'a network error'} | ${jest.fn().mockRejectedValue(new Error('Network Error'))} | ${'Something went wrong when trying to link a item. Please try again.'}
+ `('shows an error message when there is $errorType', async ({ mutationMock, errorMessage }) => {
+ createComponent({ linkedWorkItemsMutationHandler: mutationMock });
+ await selectWorkItemTokens([
+ {
+ id: 'gid://gitlab/WorkItem/641',
+ },
+ ]);
+
+ findLinkWorkItemForm().vm.$emit('submit', {
+ preventDefault: jest.fn(),
+ stopPropagation: jest.fn(),
+ });
+ await waitForPromises();
+
+ expect(findGlAlert().exists()).toBe(true);
+ expect(findGlAlert().text()).toBe(errorMessage);
+ });
+ });
+});
diff --git a/spec/frontend/work_items/components/work_item_relationships/work_item_relationship_list_spec.js b/spec/frontend/work_items/components/work_item_relationships/work_item_relationship_list_spec.js
index 759ab7e14da..e26bea46ab1 100644
--- a/spec/frontend/work_items/components/work_item_relationships/work_item_relationship_list_spec.js
+++ b/spec/frontend/work_items/components/work_item_relationships/work_item_relationship_list_spec.js
@@ -14,7 +14,6 @@ describe('WorkItemRelationshipList', () => {
linkedItems,
heading,
canUpdate,
- workItemFullPath: 'test-project-path',
},
});
};
@@ -35,7 +34,7 @@ describe('WorkItemRelationshipList', () => {
expect(findWorkItemLinkChildContents().props()).toMatchObject({
childItem: mockLinkedItems[0].workItem,
canUpdate: true,
- childPath: '/test-project-path/-/work_items/83',
+ showTaskIcon: true,
});
});
});
diff --git a/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js b/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js
index c9a2499b127..7178fa1aae7 100644
--- a/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js
+++ b/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js
@@ -9,12 +9,17 @@ import waitForPromises from 'helpers/wait_for_promises';
import WidgetWrapper from '~/work_items/components/widget_wrapper.vue';
import WorkItemRelationships from '~/work_items/components/work_item_relationships/work_item_relationships.vue';
import WorkItemRelationshipList from '~/work_items/components/work_item_relationships/work_item_relationship_list.vue';
+import WorkItemAddRelationshipForm from '~/work_items/components/work_item_relationships/work_item_add_relationship_form.vue';
+import groupWorkItemByIidQuery from '~/work_items/graphql/group_work_item_by_iid.query.graphql';
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
+import removeLinkedItemsMutation from '~/work_items/graphql/remove_linked_items.mutation.graphql';
import {
+ groupWorkItemByIidResponseFactory,
workItemByIidResponseFactory,
mockLinkedItems,
mockBlockingLinkedItem,
+ removeLinkedWorkItemResponse,
} from '../../mock_data';
describe('WorkItemRelationships', () => {
@@ -24,23 +29,44 @@ describe('WorkItemRelationships', () => {
const emptyLinkedWorkItemsQueryHandler = jest
.fn()
.mockResolvedValue(workItemByIidResponseFactory());
- const linkedWorkItemsQueryHandler = jest
+ const groupWorkItemsQueryHandler = jest
.fn()
- .mockResolvedValue(workItemByIidResponseFactory({ linkedItems: mockLinkedItems }));
- const blockingLinkedWorkItemQueryHandler = jest
+ .mockResolvedValue(groupWorkItemByIidResponseFactory());
+ const removeLinkedWorkItemSuccessMutationHandler = jest
.fn()
- .mockResolvedValue(workItemByIidResponseFactory({ linkedItems: mockBlockingLinkedItem }));
+ .mockResolvedValue(removeLinkedWorkItemResponse('Successfully unlinked IDs: 2.'));
+ const removeLinkedWorkItemErrorMutationHandler = jest
+ .fn()
+ .mockResolvedValue(removeLinkedWorkItemResponse(null, ['Linked item removal failed']));
+ const $toast = {
+ show: jest.fn(),
+ };
const createComponent = async ({
workItemQueryHandler = emptyLinkedWorkItemsQueryHandler,
+ workItemType = 'Task',
+ isGroup = false,
+ removeLinkedWorkItemMutationHandler = removeLinkedWorkItemSuccessMutationHandler,
} = {}) => {
- const mockApollo = createMockApollo([[workItemByIidQuery, workItemQueryHandler]]);
+ const mockApollo = createMockApollo([
+ [workItemByIidQuery, workItemQueryHandler],
+ [removeLinkedItemsMutation, removeLinkedWorkItemMutationHandler],
+ [groupWorkItemByIidQuery, groupWorkItemsQueryHandler],
+ ]);
wrapper = shallowMountExtended(WorkItemRelationships, {
apolloProvider: mockApollo,
propsData: {
+ workItemId: 'gid://gitlab/WorkItem/1',
workItemIid: '1',
workItemFullPath: 'test-project-path',
+ workItemType,
+ },
+ provide: {
+ isGroup,
+ },
+ mocks: {
+ $toast,
},
});
@@ -51,8 +77,11 @@ describe('WorkItemRelationships', () => {
const findWidgetWrapper = () => wrapper.findComponent(WidgetWrapper);
const findEmptyRelatedMessageContainer = () => wrapper.findByTestId('links-empty');
const findLinkedItemsCountContainer = () => wrapper.findByTestId('linked-items-count');
+ const findLinkedItemsHelpLink = () => wrapper.findByTestId('help-link');
const findAllWorkItemRelationshipListComponents = () =>
wrapper.findAllComponents(WorkItemRelationshipList);
+ const findAddButton = () => wrapper.findByTestId('link-item-add-button');
+ const findWorkItemRelationshipForm = () => wrapper.findComponent(WorkItemAddRelationshipForm);
it('shows loading icon when query is not processed', () => {
createComponent();
@@ -60,22 +89,35 @@ describe('WorkItemRelationships', () => {
expect(findLoadingIcon().exists()).toBe(true);
});
- it('renders the component with empty message when there are no items', async () => {
+ it('renders the component with with defaults', async () => {
await createComponent();
expect(wrapper.find('.work-item-relationships').exists()).toBe(true);
expect(findEmptyRelatedMessageContainer().exists()).toBe(true);
+ expect(findAddButton().exists()).toBe(true);
+ expect(findWorkItemRelationshipForm().exists()).toBe(false);
+ expect(findLinkedItemsHelpLink().attributes('href')).toBe(
+ '/help/user/okrs.md#linked-items-in-okrs',
+ );
});
it('renders blocking linked item lists', async () => {
- await createComponent({ workItemQueryHandler: blockingLinkedWorkItemQueryHandler });
+ await createComponent({
+ workItemQueryHandler: jest
+ .fn()
+ .mockResolvedValue(workItemByIidResponseFactory({ linkedItems: mockBlockingLinkedItem })),
+ });
expect(findAllWorkItemRelationshipListComponents().length).toBe(1);
expect(findLinkedItemsCountContainer().text()).toBe('1');
});
it('renders blocking, blocked by and related to linked item lists with proper count', async () => {
- await createComponent({ workItemQueryHandler: linkedWorkItemsQueryHandler });
+ await createComponent({
+ workItemQueryHandler: jest
+ .fn()
+ .mockResolvedValue(workItemByIidResponseFactory({ linkedItems: mockLinkedItems })),
+ });
// renders all 3 lists: blocking, blocked by and related to
expect(findAllWorkItemRelationshipListComponents().length).toBe(3);
@@ -90,4 +132,103 @@ describe('WorkItemRelationships', () => {
expect(findWidgetWrapper().props('error')).toBe(errorMessage);
});
+
+ it('does not render add button when there is no permission', async () => {
+ await createComponent({
+ workItemQueryHandler: jest
+ .fn()
+ .mockResolvedValue(workItemByIidResponseFactory({ canAdminWorkItemLink: false })),
+ });
+
+ expect(findAddButton().exists()).toBe(false);
+ });
+
+ it('shows form on add button and hides when cancel button is clicked', async () => {
+ await createComponent();
+
+ await findAddButton().vm.$emit('click');
+ expect(findWorkItemRelationshipForm().exists()).toBe(true);
+
+ await findWorkItemRelationshipForm().vm.$emit('cancel');
+ expect(findWorkItemRelationshipForm().exists()).toBe(false);
+ });
+
+ describe('when project context', () => {
+ it('calls the project work item query', () => {
+ createComponent();
+
+ expect(emptyLinkedWorkItemsQueryHandler).toHaveBeenCalled();
+ });
+
+ it('skips calling the group work item query', () => {
+ createComponent();
+
+ expect(groupWorkItemsQueryHandler).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when group context', () => {
+ it('skips calling the project work item query', () => {
+ createComponent({ isGroup: true });
+
+ expect(emptyLinkedWorkItemsQueryHandler).not.toHaveBeenCalled();
+ });
+
+ it('calls the group work item query', () => {
+ createComponent({ isGroup: true });
+
+ expect(groupWorkItemsQueryHandler).toHaveBeenCalled();
+ });
+ });
+
+ it('removes linked item and shows toast message when removeLinkedItem event is emitted', async () => {
+ await createComponent({
+ workItemQueryHandler: jest
+ .fn()
+ .mockResolvedValue(workItemByIidResponseFactory({ linkedItems: mockLinkedItems })),
+ });
+
+ expect(findLinkedItemsCountContainer().text()).toBe('3');
+
+ await findAllWorkItemRelationshipListComponents()
+ .at(0)
+ .vm.$emit('removeLinkedItem', { id: 'gid://gitlab/WorkItem/2' });
+
+ await waitForPromises();
+
+ expect(removeLinkedWorkItemSuccessMutationHandler).toHaveBeenCalledWith({
+ input: {
+ id: 'gid://gitlab/WorkItem/1',
+ workItemsIds: ['gid://gitlab/WorkItem/2'],
+ },
+ });
+
+ expect($toast.show).toHaveBeenCalledWith('Linked item removed');
+
+ expect(findLinkedItemsCountContainer().text()).toBe('2');
+ });
+
+ it.each`
+ errorType | mutationMock | errorMessage
+ ${'an error in the mutation response'} | ${removeLinkedWorkItemErrorMutationHandler} | ${'Linked item removal failed'}
+ ${'a network error'} | ${jest.fn().mockRejectedValue(new Error('Network Error'))} | ${'Something went wrong when removing item. Please refresh this page.'}
+ `(
+ 'shows an error message when there is $errorType while removing items',
+ async ({ mutationMock, errorMessage }) => {
+ await createComponent({
+ workItemQueryHandler: jest
+ .fn()
+ .mockResolvedValue(workItemByIidResponseFactory({ linkedItems: mockLinkedItems })),
+ removeLinkedWorkItemMutationHandler: mutationMock,
+ });
+
+ await findAllWorkItemRelationshipListComponents()
+ .at(0)
+ .vm.$emit('removeLinkedItem', { id: 'gid://gitlab/WorkItem/2' });
+
+ await waitForPromises();
+
+ expect(findWidgetWrapper().props('error')).toBe(errorMessage);
+ },
+ );
});
diff --git a/spec/frontend/work_items/components/work_item_todos_spec.js b/spec/frontend/work_items/components/work_item_todos_spec.js
index 454bd97bbee..c76cdbcee53 100644
--- a/spec/frontend/work_items/components/work_item_todos_spec.js
+++ b/spec/frontend/work_items/components/work_item_todos_spec.js
@@ -86,6 +86,9 @@ describe('WorkItemTodo component', () => {
workItemFullpath: mockWorkItemFullpath,
currentUserTodos,
},
+ provide: {
+ isGroup: false,
+ },
});
};
diff --git a/spec/frontend/work_items/graphql/cache_utils_spec.js b/spec/frontend/work_items/graphql/cache_utils_spec.js
index 6d0083790d1..64ef1bdbb88 100644
--- a/spec/frontend/work_items/graphql/cache_utils_spec.js
+++ b/spec/frontend/work_items/graphql/cache_utils_spec.js
@@ -43,7 +43,7 @@ describe('work items graphql cache utils', () => {
title: 'New child',
};
- addHierarchyChild(mockCache, fullPath, iid, child);
+ addHierarchyChild({ cache: mockCache, fullPath, iid, workItem: child });
expect(mockCache.writeQuery).toHaveBeenCalledWith({
query: workItemByIidQuery,
@@ -88,7 +88,7 @@ describe('work items graphql cache utils', () => {
title: 'New child',
};
- addHierarchyChild(mockCache, fullPath, iid, child);
+ addHierarchyChild({ cache: mockCache, fullPath, iid, workItem: child });
expect(mockCache.writeQuery).not.toHaveBeenCalled();
});
@@ -106,7 +106,7 @@ describe('work items graphql cache utils', () => {
title: 'Child',
};
- removeHierarchyChild(mockCache, fullPath, iid, childToRemove);
+ removeHierarchyChild({ cache: mockCache, fullPath, iid, workItem: childToRemove });
expect(mockCache.writeQuery).toHaveBeenCalledWith({
query: workItemByIidQuery,
@@ -145,7 +145,7 @@ describe('work items graphql cache utils', () => {
title: 'Child',
};
- removeHierarchyChild(mockCache, fullPath, iid, childToRemove);
+ removeHierarchyChild({ cache: mockCache, fullPath, iid, workItem: childToRemove });
expect(mockCache.writeQuery).not.toHaveBeenCalled();
});
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index ba244b19eb5..9eb604c81cb 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -146,6 +146,7 @@ export const workItemQueryResponse = {
setWorkItemMetadata: false,
adminParentLink: false,
createNote: false,
+ adminWorkItemLink: true,
__typename: 'WorkItemPermissions',
},
widgets: [
@@ -193,6 +194,7 @@ export const workItemQueryResponse = {
confidential: false,
title: '123',
state: 'OPEN',
+ webUrl: '/gitlab-org/gitlab-test/-/work_items/4',
workItemType: {
id: '1',
name: 'Task',
@@ -251,6 +253,7 @@ export const updateWorkItemMutationResponse = {
setWorkItemMetadata: false,
adminParentLink: false,
createNote: false,
+ adminWorkItemLink: true,
__typename: 'WorkItemPermissions',
},
reference: 'test-project-path#1',
@@ -269,6 +272,7 @@ export const updateWorkItemMutationResponse = {
confidential: false,
title: '123',
state: 'OPEN',
+ webUrl: '/gitlab-org/gitlab-test/-/work_items/4',
workItemType: {
id: '1',
name: 'Task',
@@ -360,6 +364,7 @@ export const convertWorkItemMutationResponse = {
setWorkItemMetadata: false,
adminParentLink: false,
createNote: false,
+ adminWorkItemLink: true,
__typename: 'WorkItemPermissions',
},
reference: 'gitlab-org/gitlab-test#1',
@@ -378,6 +383,7 @@ export const convertWorkItemMutationResponse = {
confidential: false,
title: '123',
state: 'OPEN',
+ webUrl: '/gitlab-org/gitlab-test/-/work_items/4',
workItemType: {
id: '1',
name: 'Task',
@@ -486,6 +492,7 @@ export const mockBlockingLinkedItem = {
state: 'OPEN',
createdAt: '2023-03-28T10:50:16Z',
closedAt: null,
+ webUrl: '/gitlab-org/gitlab-test/-/work_items/83',
widgets: [],
__typename: 'WorkItem',
},
@@ -518,6 +525,7 @@ export const mockLinkedItems = {
state: 'OPEN',
createdAt: '2023-03-28T10:50:16Z',
closedAt: null,
+ webUrl: '/gitlab-org/gitlab-test/-/work_items/83',
widgets: [],
__typename: 'WorkItem',
},
@@ -540,6 +548,7 @@ export const mockLinkedItems = {
state: 'OPEN',
createdAt: '2023-03-28T10:50:16Z',
closedAt: null,
+ webUrl: '/gitlab-org/gitlab-test/-/work_items/55',
widgets: [],
__typename: 'WorkItem',
},
@@ -562,6 +571,7 @@ export const mockLinkedItems = {
state: 'OPEN',
createdAt: '2023-03-28T10:50:16Z',
closedAt: null,
+ webUrl: '/gitlab-org/gitlab-test/-/work_items/56',
widgets: [],
__typename: 'WorkItem',
},
@@ -579,6 +589,7 @@ export const workItemResponseFactory = ({
canDelete = false,
canCreateNote = false,
adminParentLink = false,
+ canAdminWorkItemLink = true,
notificationsWidgetPresent = true,
currentUserTodosWidgetPresent = true,
awardEmojiWidgetPresent = true,
@@ -636,6 +647,7 @@ export const workItemResponseFactory = ({
updateWorkItem: canUpdate,
setWorkItemMetadata: canUpdate,
adminParentLink,
+ adminWorkItemLink: canAdminWorkItemLink,
createNote: canCreateNote,
__typename: 'WorkItemPermissions',
},
@@ -756,6 +768,7 @@ export const workItemResponseFactory = ({
confidential: false,
title: '123',
state: 'OPEN',
+ webUrl: '/gitlab-org/gitlab-test/-/work_items/5',
workItemType: {
id: '1',
name: 'Task',
@@ -828,13 +841,16 @@ export const workItemByIidResponseFactory = (options) => {
};
};
-export const updateWorkItemMutationResponseFactory = (options) => {
+export const groupWorkItemByIidResponseFactory = (options) => {
const response = workItemResponseFactory(options);
return {
data: {
- workItemUpdate: {
- workItem: response.data.workItem,
- errors: [],
+ workspace: {
+ __typename: 'Group',
+ id: 'gid://gitlab/Group/1',
+ workItems: {
+ nodes: [response.data.workItem],
+ },
},
},
};
@@ -914,6 +930,7 @@ export const createWorkItemMutationResponse = {
setWorkItemMetadata: false,
adminParentLink: false,
createNote: false,
+ adminWorkItemLink: true,
__typename: 'WorkItemPermissions',
},
reference: 'test-project-path#1',
@@ -996,6 +1013,7 @@ export const workItemHierarchyEmptyResponse = {
setWorkItemMetadata: false,
adminParentLink: false,
createNote: false,
+ adminWorkItemLink: true,
__typename: 'WorkItemPermissions',
},
confidential: false,
@@ -1046,6 +1064,7 @@ export const workItemHierarchyNoUpdatePermissionResponse = {
setWorkItemMetadata: false,
adminParentLink: false,
createNote: false,
+ adminWorkItemLink: true,
__typename: 'WorkItemPermissions',
},
project: {
@@ -1077,6 +1096,7 @@ export const workItemHierarchyNoUpdatePermissionResponse = {
confidential: false,
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
+ webUrl: '/gitlab-org/gitlab-test/-/work_items/2',
widgets: [
{
type: 'HIERARCHY',
@@ -1110,6 +1130,7 @@ export const workItemTask = {
confidential: false,
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
+ webUrl: '/gitlab-org/gitlab-test/-/work_items/4',
widgets: [],
__typename: 'WorkItem',
};
@@ -1128,6 +1149,7 @@ export const confidentialWorkItemTask = {
confidential: true,
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
+ webUrl: '/gitlab-org/gitlab-test/-/work_items/2',
widgets: [],
__typename: 'WorkItem',
};
@@ -1146,6 +1168,7 @@ export const closedWorkItemTask = {
confidential: false,
createdAt: '2022-08-03T12:41:54Z',
closedAt: '2022-08-12T13:07:52Z',
+ webUrl: '/gitlab-org/gitlab-test/-/work_items/3',
widgets: [],
__typename: 'WorkItem',
};
@@ -1168,6 +1191,7 @@ export const childrenWorkItems = [
confidential: false,
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
+ webUrl: '/gitlab-org/gitlab-test/-/work_items/5',
widgets: [],
__typename: 'WorkItem',
},
@@ -1196,6 +1220,7 @@ export const workItemHierarchyResponse = {
setWorkItemMetadata: true,
adminParentLink: true,
createNote: true,
+ adminWorkItemLink: true,
__typename: 'WorkItemPermissions',
},
author: {
@@ -1297,6 +1322,7 @@ export const workItemObjectiveWithChild = {
setWorkItemMetadata: true,
adminParentLink: true,
createNote: true,
+ adminWorkItemLink: true,
__typename: 'WorkItemPermissions',
},
author: {
@@ -1368,6 +1394,7 @@ export const workItemHierarchyTreeResponse = {
setWorkItemMetadata: true,
adminParentLink: true,
createNote: true,
+ adminWorkItemLink: true,
__typename: 'WorkItemPermissions',
},
confidential: false,
@@ -1403,6 +1430,7 @@ export const workItemHierarchyTreeResponse = {
confidential: false,
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
+ webUrl: '/gitlab-org/gitlab-test/-/work_items/13',
widgets: [
{
type: 'HIERARCHY',
@@ -1449,6 +1477,7 @@ export const changeIndirectWorkItemParentMutationResponse = {
setWorkItemMetadata: true,
adminParentLink: true,
createNote: true,
+ adminWorkItemLink: true,
__typename: 'WorkItemPermissions',
},
description: null,
@@ -1517,6 +1546,7 @@ export const changeWorkItemParentMutationResponse = {
setWorkItemMetadata: true,
adminParentLink: true,
createNote: true,
+ adminWorkItemLink: true,
__typename: 'WorkItemPermissions',
},
description: null,
@@ -1568,6 +1598,7 @@ export const availableWorkItemsResponse = {
nodes: [
{
id: 'gid://gitlab/WorkItem/458',
+ iid: '2',
title: 'Task 1',
state: 'OPEN',
createdAt: '2022-08-03T12:41:54Z',
@@ -1576,6 +1607,7 @@ export const availableWorkItemsResponse = {
},
{
id: 'gid://gitlab/WorkItem/459',
+ iid: '3',
title: 'Task 2',
state: 'OPEN',
createdAt: '2022-08-03T12:41:54Z',
@@ -1584,6 +1616,7 @@ export const availableWorkItemsResponse = {
},
{
id: 'gid://gitlab/WorkItem/460',
+ iid: '4',
title: 'Task 3',
state: 'OPEN',
createdAt: '2022-08-03T12:41:54Z',
@@ -1596,6 +1629,64 @@ export const availableWorkItemsResponse = {
},
};
+export const availableObjectivesResponse = {
+ data: {
+ workspace: {
+ __typename: 'Project',
+ id: 'gid://gitlab/Project/2',
+ workItems: {
+ nodes: [
+ {
+ id: 'gid://gitlab/WorkItem/716',
+ iid: '122',
+ title: 'Objective 101',
+ state: 'OPEN',
+ confidential: false,
+ __typename: 'WorkItem',
+ },
+ {
+ id: 'gid://gitlab/WorkItem/712',
+ iid: '118',
+ title: 'Objective 103',
+ state: 'OPEN',
+ confidential: false,
+ __typename: 'WorkItem',
+ },
+ {
+ id: 'gid://gitlab/WorkItem/711',
+ iid: '117',
+ title: 'Objective 102',
+ state: 'OPEN',
+ confidential: false,
+ __typename: 'WorkItem',
+ },
+ ],
+ },
+ },
+ },
+};
+
+export const searchedObjectiveResponse = {
+ data: {
+ workspace: {
+ __typename: 'Project',
+ id: 'gid://gitlab/Project/2',
+ workItems: {
+ nodes: [
+ {
+ id: 'gid://gitlab/WorkItem/716',
+ iid: '122',
+ title: 'Objective 101',
+ state: 'OPEN',
+ confidential: false,
+ __typename: 'WorkItem',
+ },
+ ],
+ },
+ },
+ },
+};
+
export const searchedWorkItemsResponse = {
data: {
workspace: {
@@ -1605,6 +1696,7 @@ export const searchedWorkItemsResponse = {
nodes: [
{
id: 'gid://gitlab/WorkItem/459',
+ iid: '3',
title: 'Task 2',
state: 'OPEN',
createdAt: '2022-08-03T12:41:54Z',
@@ -1931,6 +2023,21 @@ export const mockMilestoneWidgetResponse = {
title: 'v4.0',
};
+export const mockParentWidgetResponse = {
+ id: 'gid://gitlab/WorkItem/716',
+ iid: '122',
+ title: 'Objective 101',
+ confidential: false,
+ webUrl: 'http://127.0.0.1:3000/gitlab-org/gitlab-test/-/work_items/122',
+ workItemType: {
+ id: 'gid://gitlab/WorkItems::Type/6',
+ name: 'Objective',
+ iconName: 'issue-type-objective',
+ __typename: 'WorkItemType',
+ },
+ __typename: 'WorkItem',
+};
+
export const projectMilestonesResponse = {
data: {
workspace: {
@@ -3439,6 +3546,31 @@ export const getTodosMutationResponse = (state) => {
};
};
+export const linkedWorkItemResponse = (options, errors = []) => {
+ const response = workItemResponseFactory(options);
+ return {
+ data: {
+ workItemAddLinkedItems: {
+ workItem: response.data.workItem,
+ errors,
+ __typename: 'WorkItemAddLinkedItemsPayload',
+ },
+ },
+ };
+};
+
+export const removeLinkedWorkItemResponse = (message, errors = []) => {
+ return {
+ data: {
+ workItemRemoveLinkedItems: {
+ errors,
+ message,
+ __typename: 'WorkItemRemoveLinkedItemsPayload',
+ },
+ },
+ };
+};
+
export const groupWorkItemsQueryResponse = {
data: {
group: {
@@ -3498,3 +3630,36 @@ export const groupWorkItemsQueryResponse = {
},
},
};
+
+export const updateWorkItemMutationResponseFactory = (options) => {
+ const response = workItemResponseFactory(options);
+ return {
+ data: {
+ workItemUpdate: {
+ workItem: response.data.workItem,
+ errors: [],
+ },
+ },
+ };
+};
+
+export const updateWorkItemNotificationsMutationResponse = (subscribed) => ({
+ data: {
+ workItemSubscribe: {
+ workItem: {
+ id: 'gid://gitlab/WorkItem/1',
+ widgets: [
+ {
+ __typename: 'WorkItemWidgetNotifications',
+ type: 'NOTIFICATIONS',
+ subscribed,
+ },
+ ],
+ },
+ errors: [],
+ },
+ },
+});
+
+export const generateWorkItemsListWithId = (count) =>
+ Array.from({ length: count }, (_, i) => ({ id: `gid://gitlab/WorkItem/${i + 1}` }));
diff --git a/spec/frontend/work_items/pages/create_work_item_spec.js b/spec/frontend/work_items/pages/create_work_item_spec.js
index c369a454286..527f5890338 100644
--- a/spec/frontend/work_items/pages/create_work_item_spec.js
+++ b/spec/frontend/work_items/pages/create_work_item_spec.js
@@ -65,6 +65,7 @@ describe('Create work item component', () => {
},
provide: {
fullPath: 'full-path',
+ isGroup: false,
},
});
};
@@ -199,8 +200,6 @@ describe('Create work item component', () => {
wrapper.find('form').trigger('submit');
await waitForPromises();
- expect(findAlert().text()).toBe(
- 'Something went wrong when creating work item. Please try again.',
- );
+ expect(findAlert().text()).toBe('Something went wrong when creating item. Please try again.');
});
});
diff --git a/spec/frontend/work_items/router_spec.js b/spec/frontend/work_items/router_spec.js
index 79ba31e7012..d4efcf78189 100644
--- a/spec/frontend/work_items/router_spec.js
+++ b/spec/frontend/work_items/router_spec.js
@@ -41,6 +41,7 @@ describe('Work items router', () => {
router,
provide: {
fullPath: 'full-path',
+ isGroup: false,
issuesListPath: 'full-path/-/issues',
hasIssueWeightsFeature: false,
hasIterationsFeature: false,
diff --git a/spec/frontend/work_items/utils_spec.js b/spec/frontend/work_items/utils_spec.js
index 8a49140119d..aa24b80cf08 100644
--- a/spec/frontend/work_items/utils_spec.js
+++ b/spec/frontend/work_items/utils_spec.js
@@ -1,4 +1,4 @@
-import { autocompleteDataSources, markdownPreviewPath, workItemPath } from '~/work_items/utils';
+import { autocompleteDataSources, markdownPreviewPath } from '~/work_items/utils';
describe('autocompleteDataSources', () => {
beforeEach(() => {
@@ -25,14 +25,3 @@ describe('markdownPreviewPath', () => {
);
});
});
-
-describe('workItemPath', () => {
- it('returns corrrect data sources', () => {
- expect(workItemPath('project/group', '2')).toEqual('/project/group/-/work_items/2');
- });
-
- it('returns corrrect data sources with relative url root', () => {
- gon.relative_url_root = '/foobar';
- expect(workItemPath('project/group', '2')).toEqual('/foobar/project/group/-/work_items/2');
- });
-});