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:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-09-14 06:11:17 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-09-14 06:11:17 +0300
commit886077c08875d595fc88a689f1ac841252813513 (patch)
treef47e7078289041816fb8a540bb12218a2cfccc27 /spec/frontend/work_items
parent71df3555b295779dec870c8ad59c30b6a47c837e (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/work_items')
-rw-r--r--spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js14
-rw-r--r--spec/frontend/work_items/components/work_item_detail_spec.js81
-rw-r--r--spec/frontend/work_items/components/work_item_relationships/__snapshots__/work_item_relationship_list_spec.js.snap29
-rw-r--r--spec/frontend/work_items/components/work_item_relationships/work_item_relationship_list_spec.js41
-rw-r--r--spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js76
-rw-r--r--spec/frontend/work_items/mock_data.js125
6 files changed, 350 insertions, 16 deletions
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 9a20e2ec98f..b86f9ff34ae 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
@@ -10,7 +10,7 @@ import RichTimestampTooltip from '~/vue_shared/components/rich_timestamp_tooltip
import WorkItemLinkChildContents from '~/work_items/components/shared/work_item_link_child_contents.vue';
import WorkItemLinksMenu from '~/work_items/components/shared/work_item_links_menu.vue';
-import { TASK_TYPE_NAME, WORK_ITEM_TYPE_VALUE_OBJECTIVE } from '~/work_items/constants';
+import { WORK_ITEM_TYPE_VALUE_OBJECTIVE } from '~/work_items/constants';
import {
workItemTask,
@@ -26,11 +26,9 @@ jest.mock('~/alert');
describe('WorkItemLinkChildContents', () => {
Vue.use(VueApollo);
- const WORK_ITEM_ID = 'gid://gitlab/WorkItem/2';
let wrapper;
const { LABELS } = workItemObjectiveMetadataWidgets;
const mockLabels = LABELS.labels.nodes;
- const mockFullPath = 'gitlab-org/gitlab-test';
const findStatusIconComponent = () =>
wrapper.findByTestId('item-status-icon').findComponent(GlIcon);
@@ -43,19 +41,11 @@ describe('WorkItemLinkChildContents', () => {
const findScopedLabel = () => findAllLabels().at(1);
const findLinksMenuComponent = () => wrapper.findComponent(WorkItemLinksMenu);
- const createComponent = ({
- canUpdate = true,
- parentWorkItemId = WORK_ITEM_ID,
- childItem = workItemTask,
- workItemType = TASK_TYPE_NAME,
- } = {}) => {
+ const createComponent = ({ canUpdate = true, childItem = workItemTask } = {}) => {
wrapper = shallowMountExtended(WorkItemLinkChildContents, {
propsData: {
canUpdate,
- parentWorkItemId,
childItem,
- workItemType,
- fullPath: mockFullPath,
childPath: '/gitlab-org/gitlab-test/-/work_items/4',
},
});
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 638a92dbd17..fec6d0673c6 100644
--- a/spec/frontend/work_items/components/work_item_detail_spec.js
+++ b/spec/frontend/work_items/components/work_item_detail_spec.js
@@ -20,6 +20,7 @@ import WorkItemCreatedUpdated from '~/work_items/components/work_item_created_up
import WorkItemAttributesWrapper from '~/work_items/components/work_item_attributes_wrapper.vue';
import WorkItemTitle from '~/work_items/components/work_item_title.vue';
import WorkItemTree from '~/work_items/components/work_item_links/work_item_tree.vue';
+import WorkItemRelationships from '~/work_items/components/work_item_relationships/work_item_relationships.vue';
import WorkItemNotes from '~/work_items/components/work_item_notes.vue';
import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue';
import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
@@ -37,6 +38,7 @@ import {
workItemByIidResponseFactory,
objectiveType,
mockWorkItemCommentNote,
+ mockBlockingLinkedItem,
} from '../mock_data';
jest.mock('~/lib/utils/common_utils');
@@ -76,6 +78,7 @@ describe('WorkItemDetail component', () => {
const findCloseButton = () => wrapper.findByTestId('work-item-close');
const findWorkItemType = () => wrapper.findByTestId('work-item-type');
const findHierarchyTree = () => wrapper.findComponent(WorkItemTree);
+ const findWorkItemRelationships = () => wrapper.findComponent(WorkItemRelationships);
const findNotesWidget = () => wrapper.findComponent(WorkItemNotes);
const findModal = () => wrapper.findComponent(WorkItemDetailModal);
const findAbuseCategorySelector = () => wrapper.findComponent(AbuseCategorySelector);
@@ -96,6 +99,7 @@ describe('WorkItemDetail component', () => {
confidentialityMock = [updateWorkItemMutation, jest.fn()],
error = undefined,
workItemsMvc2Enabled = false,
+ linkedWorkItemsEnabled = false,
} = {}) => {
const handlers = [
[workItemByIidQuery, handler],
@@ -119,6 +123,7 @@ describe('WorkItemDetail component', () => {
provide: {
glFeatures: {
workItemsMvc2: workItemsMvc2Enabled,
+ linkedWorkItems: linkedWorkItemsEnabled,
},
hasIssueWeightsFeature: true,
hasIterationsFeature: true,
@@ -581,6 +586,82 @@ describe('WorkItemDetail component', () => {
});
});
+ describe('relationship widget', () => {
+ it('does not render linked items by default', async () => {
+ createComponent();
+ await waitForPromises();
+
+ expect(findWorkItemRelationships().exists()).toBe(false);
+ });
+
+ describe('work item has children', () => {
+ const mockWorkItemLinkedItem = workItemByIidResponseFactory({
+ linkedItems: mockBlockingLinkedItem,
+ });
+ const handler = jest.fn().mockResolvedValue(mockWorkItemLinkedItem);
+
+ it('renders relationship widget when work item has linked items', async () => {
+ createComponent({ handler, linkedWorkItemsEnabled: true });
+ await waitForPromises();
+
+ expect(findWorkItemRelationships().exists()).toBe(true);
+ });
+
+ it('opens the modal with the linked item when `showModal` is emitted', async () => {
+ createComponent({
+ handler,
+ linkedWorkItemsEnabled: true,
+ workItemsMvc2Enabled: true,
+ });
+ await waitForPromises();
+
+ const event = {
+ preventDefault: jest.fn(),
+ };
+
+ findWorkItemRelationships().vm.$emit('showModal', {
+ event,
+ modalWorkItem: { id: 'childWorkItemId' },
+ });
+ await waitForPromises();
+
+ expect(findModal().props().workItemId).toBe('childWorkItemId');
+ expect(showModalHandler).toHaveBeenCalled();
+ });
+
+ describe('linked work item is rendered in a modal and has linked items', () => {
+ beforeEach(async () => {
+ createComponent({
+ isModal: true,
+ handler,
+ workItemsMvc2Enabled: true,
+ linkedWorkItemsEnabled: true,
+ });
+
+ await waitForPromises();
+ });
+
+ it('does not render a new modal', () => {
+ expect(findModal().exists()).toBe(false);
+ });
+
+ it('emits `update-modal` when `show-modal` is emitted', async () => {
+ const event = {
+ preventDefault: jest.fn(),
+ };
+
+ findWorkItemRelationships().vm.$emit('showModal', {
+ event,
+ modalWorkItem: { id: 'childWorkItemId' },
+ });
+ await waitForPromises();
+
+ expect(wrapper.emitted('update-modal')).toBeDefined();
+ });
+ });
+ });
+ });
+
describe('notes widget', () => {
it('renders notes by default', async () => {
createComponent();
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
new file mode 100644
index 00000000000..9105e4de5e0
--- /dev/null
+++ b/spec/frontend/work_items/components/work_item_relationships/__snapshots__/work_item_relationship_list_spec.js.snap
@@ -0,0 +1,29 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`WorkItemRelationshipList renders linked item list 1`] = `
+<div>
+ <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"
+ >
+ Blocking
+ </h4>
+ <div
+ class="work-items-list-body"
+ >
+ <ul
+ class="content-list work-items-list"
+ >
+ <li
+ class="gl-border-b-0! gl-pb-0! gl-pt-0!"
+ >
+ <work-item-link-child-contents-stub
+ canupdate="true"
+ childitem="[object Object]"
+ childpath="/test-project-path/-/work_items/83"
+ />
+ </li>
+ </ul>
+ </div>
+</div>
+`;
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
new file mode 100644
index 00000000000..759ab7e14da
--- /dev/null
+++ b/spec/frontend/work_items/components/work_item_relationships/work_item_relationship_list_spec.js
@@ -0,0 +1,41 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import WorkItemRelationshipList from '~/work_items/components/work_item_relationships/work_item_relationship_list.vue';
+import WorkItemLinkChildContents from '~/work_items/components/shared/work_item_link_child_contents.vue';
+
+import { mockBlockingLinkedItem } from '../../mock_data';
+
+describe('WorkItemRelationshipList', () => {
+ let wrapper;
+ const mockLinkedItems = mockBlockingLinkedItem.linkedItems.nodes;
+
+ const createComponent = ({ linkedItems = [], heading = 'Blocking', canUpdate = true } = {}) => {
+ wrapper = shallowMountExtended(WorkItemRelationshipList, {
+ propsData: {
+ linkedItems,
+ heading,
+ canUpdate,
+ workItemFullPath: 'test-project-path',
+ },
+ });
+ };
+
+ const findHeading = () => wrapper.findByTestId('work-items-list-heading');
+ const findWorkItemLinkChildContents = () => wrapper.findComponent(WorkItemLinkChildContents);
+
+ beforeEach(() => {
+ createComponent({ linkedItems: mockLinkedItems });
+ });
+
+ it('renders linked item list', () => {
+ expect(findHeading().text()).toBe('Blocking');
+ expect(wrapper.html()).toMatchSnapshot();
+ });
+
+ it('renders work item link child contents with correct props', () => {
+ expect(findWorkItemLinkChildContents().props()).toMatchObject({
+ childItem: mockLinkedItems[0].workItem,
+ canUpdate: true,
+ childPath: '/test-project-path/-/work_items/83',
+ });
+ });
+});
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 18d13aa251e..c9a2499b127 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
@@ -1,25 +1,93 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { GlLoadingIcon } from '@gitlab/ui';
+
+import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
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 workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
+
+import {
+ workItemByIidResponseFactory,
+ mockLinkedItems,
+ mockBlockingLinkedItem,
+} from '../../mock_data';
describe('WorkItemRelationships', () => {
+ Vue.use(VueApollo);
+
let wrapper;
+ const emptyLinkedWorkItemsQueryHandler = jest
+ .fn()
+ .mockResolvedValue(workItemByIidResponseFactory());
+ const linkedWorkItemsQueryHandler = jest
+ .fn()
+ .mockResolvedValue(workItemByIidResponseFactory({ linkedItems: mockLinkedItems }));
+ const blockingLinkedWorkItemQueryHandler = jest
+ .fn()
+ .mockResolvedValue(workItemByIidResponseFactory({ linkedItems: mockBlockingLinkedItem }));
+
+ const createComponent = async ({
+ workItemQueryHandler = emptyLinkedWorkItemsQueryHandler,
+ } = {}) => {
+ const mockApollo = createMockApollo([[workItemByIidQuery, workItemQueryHandler]]);
- const createComponent = async () => {
wrapper = shallowMountExtended(WorkItemRelationships, {
+ apolloProvider: mockApollo,
propsData: {
- workItem: {},
workItemIid: '1',
- workItemFullpath: 'gitlab/path',
+ workItemFullPath: 'test-project-path',
},
});
await waitForPromises();
};
- it('renders the component', () => {
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findWidgetWrapper = () => wrapper.findComponent(WidgetWrapper);
+ const findEmptyRelatedMessageContainer = () => wrapper.findByTestId('links-empty');
+ const findLinkedItemsCountContainer = () => wrapper.findByTestId('linked-items-count');
+ const findAllWorkItemRelationshipListComponents = () =>
+ wrapper.findAllComponents(WorkItemRelationshipList);
+
+ it('shows loading icon when query is not processed', () => {
createComponent();
+ expect(findLoadingIcon().exists()).toBe(true);
+ });
+
+ it('renders the component with empty message when there are no items', async () => {
+ await createComponent();
+
expect(wrapper.find('.work-item-relationships').exists()).toBe(true);
+ expect(findEmptyRelatedMessageContainer().exists()).toBe(true);
+ });
+
+ it('renders blocking linked item lists', async () => {
+ await createComponent({ workItemQueryHandler: blockingLinkedWorkItemQueryHandler });
+
+ 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 });
+
+ // renders all 3 lists: blocking, blocked by and related to
+ expect(findAllWorkItemRelationshipListComponents().length).toBe(3);
+ expect(findLinkedItemsCountContainer().text()).toBe('3');
+ });
+
+ it('shows an alert when list loading fails', async () => {
+ const errorMessage = 'Some error';
+ await createComponent({
+ workItemQueryHandler: jest.fn().mockRejectedValue(new Error(errorMessage)),
+ });
+
+ expect(findWidgetWrapper().props('error')).toBe(errorMessage);
});
});
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index 3c430996a59..ba244b19eb5 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -1,3 +1,5 @@
+import { WIDGET_TYPE_LINKED_ITEMS } from '~/work_items/constants';
+
export const mockAssignees = [
{
__typename: 'UserCore',
@@ -451,6 +453,126 @@ export const objectiveType = {
iconName: 'issue-type-objective',
};
+export const mockEmptyLinkedItems = {
+ type: WIDGET_TYPE_LINKED_ITEMS,
+ blocked: false,
+ blockedByCount: 0,
+ blockingCount: 0,
+ linkedItems: {
+ nodes: [],
+ __typename: 'LinkedWorkItemTypeConnection',
+ },
+ __typename: 'WorkItemWidgetLinkedItems',
+};
+
+export const mockBlockingLinkedItem = {
+ type: WIDGET_TYPE_LINKED_ITEMS,
+ linkedItems: {
+ nodes: [
+ {
+ linkId: 'gid://gitlab/WorkItems::RelatedWorkItemLink/8',
+ linkType: 'blocks',
+ workItem: {
+ id: 'gid://gitlab/WorkItem/675',
+ iid: '83',
+ confidential: true,
+ workItemType: {
+ id: 'gid://gitlab/WorkItems::Type/5',
+ name: 'Task',
+ iconName: 'issue-type-task',
+ __typename: 'WorkItemType',
+ },
+ title: 'Task 1201',
+ state: 'OPEN',
+ createdAt: '2023-03-28T10:50:16Z',
+ closedAt: null,
+ widgets: [],
+ __typename: 'WorkItem',
+ },
+ __typename: 'LinkedWorkItemType',
+ },
+ ],
+ __typename: 'LinkedWorkItemTypeConnection',
+ },
+ __typename: 'WorkItemWidgetLinkedItems',
+};
+
+export const mockLinkedItems = {
+ type: WIDGET_TYPE_LINKED_ITEMS,
+ linkedItems: {
+ nodes: [
+ {
+ linkId: 'gid://gitlab/WorkItems::RelatedWorkItemLink/8',
+ linkType: 'relates_to',
+ workItem: {
+ id: 'gid://gitlab/WorkItem/675',
+ iid: '83',
+ confidential: true,
+ workItemType: {
+ id: 'gid://gitlab/WorkItems::Type/5',
+ name: 'Task',
+ iconName: 'issue-type-task',
+ __typename: 'WorkItemType',
+ },
+ title: 'Task 1201',
+ state: 'OPEN',
+ createdAt: '2023-03-28T10:50:16Z',
+ closedAt: null,
+ widgets: [],
+ __typename: 'WorkItem',
+ },
+ __typename: 'LinkedWorkItemType',
+ },
+ {
+ linkId: 'gid://gitlab/WorkItems::RelatedWorkItemLink/9',
+ linkType: 'is_blocked_by',
+ workItem: {
+ id: 'gid://gitlab/WorkItem/646',
+ iid: '55',
+ confidential: true,
+ workItemType: {
+ id: 'gid://gitlab/WorkItems::Type/6',
+ name: 'Objective',
+ iconName: 'issue-type-objective',
+ __typename: 'WorkItemType',
+ },
+ title: 'Multilevel Objective 1',
+ state: 'OPEN',
+ createdAt: '2023-03-28T10:50:16Z',
+ closedAt: null,
+ widgets: [],
+ __typename: 'WorkItem',
+ },
+ __typename: 'LinkedWorkItemType',
+ },
+ {
+ linkId: 'gid://gitlab/WorkItems::RelatedWorkItemLink/10',
+ linkType: 'blocks',
+ workItem: {
+ id: 'gid://gitlab/WorkItem/647',
+ iid: '56',
+ confidential: true,
+ workItemType: {
+ id: 'gid://gitlab/WorkItems::Type/6',
+ name: 'Objective',
+ iconName: 'issue-type-objective',
+ __typename: 'WorkItemType',
+ },
+ title: 'Multilevel Objective 2',
+ state: 'OPEN',
+ createdAt: '2023-03-28T10:50:16Z',
+ closedAt: null,
+ widgets: [],
+ __typename: 'WorkItem',
+ },
+ __typename: 'LinkedWorkItemType',
+ },
+ ],
+ __typename: 'LinkedWorkItemTypeConnection',
+ },
+ __typename: 'WorkItemWidgetLinkedItems',
+};
+
export const workItemResponseFactory = ({
iid = '1',
canUpdate = false,
@@ -473,6 +595,7 @@ export const workItemResponseFactory = ({
confidential = false,
canInviteMembers = false,
labelsWidgetPresent = true,
+ linkedItemsWidgetPresent = true,
labels = mockLabels,
allowsScopedLabels = false,
lastEditedAt = null,
@@ -485,6 +608,7 @@ export const workItemResponseFactory = ({
updatedAt = '2022-08-08T12:32:54Z',
awardEmoji = mockAwardsWidget,
state = 'OPEN',
+ linkedItems = mockEmptyLinkedItems,
} = {}) => ({
data: {
workItem: {
@@ -683,6 +807,7 @@ export const workItemResponseFactory = ({
awardEmoji,
}
: { type: 'MOCK TYPE' },
+ linkedItemsWidgetPresent ? linkedItems : { type: 'MOCK TYPE' },
],
},
},