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>2022-12-13 18:07:56 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-12-13 18:07:56 +0300
commit0d55697d64b5f053bbd0f69da2962e7478097de3 (patch)
tree33dc75892313554223fb7dadd88e1c8875053d88 /spec/frontend
parent9fdb3dbd6bacb125d40290aac8409da2f9fe19fc (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/__helpers__/graphql_helpers.js14
-rw-r--r--spec/frontend/__helpers__/graphql_helpers_spec.js23
-rw-r--r--spec/frontend/diffs/store/actions_spec.js44
-rw-r--r--spec/frontend/diffs/utils/merge_request_spec.js40
-rw-r--r--spec/frontend/packages_and_registries/dependency_proxy/app_spec.js3
-rw-r--r--spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js8
-rw-r--r--spec/frontend/projects/project_new_spec.js55
-rw-r--r--spec/frontend/work_items/components/work_item_detail_spec.js23
-rw-r--r--spec/frontend/work_items/components/work_item_links/okr_actions_split_button_spec.js10
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js112
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_links_spec.js6
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js24
-rw-r--r--spec/frontend/work_items/mock_data.js186
13 files changed, 477 insertions, 71 deletions
diff --git a/spec/frontend/__helpers__/graphql_helpers.js b/spec/frontend/__helpers__/graphql_helpers.js
deleted file mode 100644
index 63123aa046f..00000000000
--- a/spec/frontend/__helpers__/graphql_helpers.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * Returns a clone of the given object with all __typename keys omitted,
- * including deeply nested ones.
- *
- * Only works with JSON-serializable objects.
- *
- * @param {object} An object with __typename keys (e.g., a GraphQL response)
- * @returns {object} A new object with no __typename keys
- */
-export const stripTypenames = (object) => {
- return JSON.parse(
- JSON.stringify(object, (key, value) => (key === '__typename' ? undefined : value)),
- );
-};
diff --git a/spec/frontend/__helpers__/graphql_helpers_spec.js b/spec/frontend/__helpers__/graphql_helpers_spec.js
deleted file mode 100644
index dd23fbbf4e9..00000000000
--- a/spec/frontend/__helpers__/graphql_helpers_spec.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import { stripTypenames } from './graphql_helpers';
-
-describe('stripTypenames', () => {
- it.each`
- input | expected
- ${{}} | ${{}}
- ${{ __typename: 'Foo' }} | ${{}}
- ${{ bar: 'bar', __typename: 'Foo' }} | ${{ bar: 'bar' }}
- ${{ bar: { __typename: 'Bar' }, __typename: 'Foo' }} | ${{ bar: {} }}
- ${{ bar: [{ __typename: 'Bar' }], __typename: 'Foo' }} | ${{ bar: [{}] }}
- ${[]} | ${[]}
- ${[{ __typename: 'Foo' }]} | ${[{}]}
- ${[{ bar: [{ a: 1, __typename: 'Bar' }] }]} | ${[{ bar: [{ a: 1 }] }]}
- `('given $input returns $expected, with all __typename keys removed', ({ input, expected }) => {
- const actual = stripTypenames(input);
- expect(actual).toEqual(expected);
- expect(input).not.toBe(actual);
- });
-
- it('given null returns null', () => {
- expect(stripTypenames(null)).toEqual(null);
- });
-});
diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js
index 87366cdbfc5..9e0ffbf757f 100644
--- a/spec/frontend/diffs/store/actions_spec.js
+++ b/spec/frontend/diffs/store/actions_spec.js
@@ -606,6 +606,50 @@ describe('DiffsStoreActions', () => {
params: { commit_id: '123', w: '0' },
});
});
+
+ describe('version parameters', () => {
+ const diffId = '4';
+ const startSha = 'abc';
+ const pathRoot = 'a/a/-/merge_requests/1';
+ let file;
+ let getters;
+
+ beforeAll(() => {
+ file = { load_collapsed_diff_url: '/load/collapsed/diff/url' };
+ getters = {};
+ });
+
+ beforeEach(() => {
+ jest.spyOn(axios, 'get').mockReturnValue(Promise.resolve({ data: {} }));
+ });
+
+ it('fetches the data when there is no mergeRequestDiff', () => {
+ diffActions.loadCollapsedDiff({ commit() {}, getters, state }, file);
+
+ expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, {
+ params: expect.any(Object),
+ });
+ });
+
+ it.each`
+ desc | versionPath | start_sha | diff_id
+ ${'no additional version information'} | ${`${pathRoot}?search=terms`} | ${undefined} | ${undefined}
+ ${'the diff_id'} | ${`${pathRoot}?diff_id=${diffId}`} | ${undefined} | ${diffId}
+ ${'the start_sha'} | ${`${pathRoot}?start_sha=${startSha}`} | ${startSha} | ${undefined}
+ ${'all available version information'} | ${`${pathRoot}?diff_id=${diffId}&start_sha=${startSha}`} | ${startSha} | ${diffId}
+ `('fetches the data and includes $desc', ({ versionPath, start_sha, diff_id }) => {
+ jest.spyOn(axios, 'get').mockReturnValue(Promise.resolve({ data: {} }));
+
+ diffActions.loadCollapsedDiff(
+ { commit() {}, getters, state: { mergeRequestDiff: { version_path: versionPath } } },
+ file,
+ );
+
+ expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, {
+ params: expect.objectContaining({ start_sha, diff_id }),
+ });
+ });
+ });
});
describe('toggleFileDiscussions', () => {
diff --git a/spec/frontend/diffs/utils/merge_request_spec.js b/spec/frontend/diffs/utils/merge_request_spec.js
index 8c7b1e1f2a5..c070e8c004d 100644
--- a/spec/frontend/diffs/utils/merge_request_spec.js
+++ b/spec/frontend/diffs/utils/merge_request_spec.js
@@ -2,30 +2,64 @@ import { getDerivedMergeRequestInformation } from '~/diffs/utils/merge_request';
import { diffMetadata } from '../mock_data/diff_metadata';
describe('Merge Request utilities', () => {
- const derivedMrInfo = {
+ const derivedBaseInfo = {
mrPath: '/gitlab-org/gitlab-test/-/merge_requests/4',
userOrGroup: 'gitlab-org',
project: 'gitlab-test',
id: '4',
};
+ const derivedVersionInfo = {
+ diffId: '4',
+ startSha: 'eb227b3e214624708c474bdab7bde7afc17cefcc',
+ };
+ const noVersion = {
+ diffId: undefined,
+ startSha: undefined,
+ };
const unparseableEndpoint = {
mrPath: undefined,
userOrGroup: undefined,
project: undefined,
id: undefined,
+ ...noVersion,
};
describe('getDerivedMergeRequestInformation', () => {
- const endpoint = `${diffMetadata.latest_version_path}.json?searchParam=irrelevant`;
+ let endpoint = `${diffMetadata.latest_version_path}.json?searchParam=irrelevant`;
it.each`
argument | response
- ${{ endpoint }} | ${derivedMrInfo}
+ ${{ endpoint }} | ${{ ...derivedBaseInfo, ...noVersion }}
${{}} | ${unparseableEndpoint}
${{ endpoint: undefined }} | ${unparseableEndpoint}
${{ endpoint: null }} | ${unparseableEndpoint}
`('generates the correct derived results based on $argument', ({ argument, response }) => {
expect(getDerivedMergeRequestInformation(argument)).toStrictEqual(response);
});
+
+ describe('version information', () => {
+ const bare = diffMetadata.latest_version_path;
+ endpoint = diffMetadata.merge_request_diffs[0].compare_path;
+
+ it('still gets the correct derived information', () => {
+ expect(getDerivedMergeRequestInformation({ endpoint })).toMatchObject(derivedBaseInfo);
+ });
+
+ it.each`
+ url | versionPart
+ ${endpoint} | ${derivedVersionInfo}
+ ${`${bare}?diff_id=${derivedVersionInfo.diffId}`} | ${{ ...derivedVersionInfo, startSha: undefined }}
+ ${`${bare}?start_sha=${derivedVersionInfo.startSha}`} | ${{ ...derivedVersionInfo, diffId: undefined }}
+ `(
+ 'generates the correct derived version information based on $url',
+ ({ url, versionPart }) => {
+ expect(getDerivedMergeRequestInformation({ endpoint: url })).toMatchObject(versionPart);
+ },
+ );
+
+ it('extracts nothing if there is no available version-like information in the URL', () => {
+ expect(getDerivedMergeRequestInformation({ endpoint: bare })).toMatchObject(noVersion);
+ });
+ });
});
});
diff --git a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
index fb50d623543..329cc15df97 100644
--- a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
+++ b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
@@ -14,7 +14,6 @@ import VueApollo from 'vue-apollo';
import MockAdapter from 'axios-mock-adapter';
import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { stripTypenames } from 'helpers/graphql_helpers';
import waitForPromises from 'helpers/wait_for_promises';
import { GRAPHQL_PAGE_SIZE } from '~/packages_and_registries/dependency_proxy/constants';
import axios from '~/lib/utils/axios_utils';
@@ -190,7 +189,7 @@ describe('DependencyProxyApp', () => {
it('shows list', () => {
expect(findManifestList().props()).toMatchObject({
manifests: proxyManifests(),
- pagination: stripTypenames(pagination()),
+ pagination: pagination(),
});
});
diff --git a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js
index 9e4c747a1bd..2f415bfd6f9 100644
--- a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js
+++ b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js
@@ -1,5 +1,4 @@
import { GlKeysetPagination } from '@gitlab/ui';
-import { stripTypenames } from 'helpers/graphql_helpers';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ManifestRow from '~/packages_and_registries/dependency_proxy/components/manifest_row.vue';
@@ -14,7 +13,7 @@ describe('Manifests List', () => {
const defaultProps = {
manifests: proxyManifests(),
- pagination: stripTypenames(pagination()),
+ pagination: pagination(),
};
const createComponent = (propsData = defaultProps) => {
@@ -60,9 +59,8 @@ describe('Manifests List', () => {
it('has the correct props', () => {
createComponent();
- expect(findPagination().props()).toMatchObject({
- ...defaultProps.pagination,
- });
+ const { __typename, ...paginationProps } = defaultProps.pagination;
+ expect(findPagination().props()).toMatchObject(paginationProps);
});
it('emits the next-page event', () => {
diff --git a/spec/frontend/projects/project_new_spec.js b/spec/frontend/projects/project_new_spec.js
index 4fcecc3a307..d69bfc4ec92 100644
--- a/spec/frontend/projects/project_new_spec.js
+++ b/spec/frontend/projects/project_new_spec.js
@@ -1,12 +1,14 @@
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
import projectNew from '~/projects/project_new';
+import { checkRules } from '~/projects/project_name_rules';
import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
describe('New Project', () => {
let $projectImportUrl;
let $projectPath;
let $projectName;
+ let $projectNameError;
const mockKeyup = (el) => el.dispatchEvent(new KeyboardEvent('keyup'));
const mockChange = (el) => el.dispatchEvent(new Event('change'));
@@ -29,6 +31,7 @@ describe('New Project', () => {
</div>
</div>
<input id="project_name" />
+ <div class="gl-field-error hidden" id="project_name_error" />
<input id="project_path" />
</div>
<div class="js-user-readme-repo"></div>
@@ -41,6 +44,7 @@ describe('New Project', () => {
$projectImportUrl = document.querySelector('#project_import_url');
$projectPath = document.querySelector('#project_path');
$projectName = document.querySelector('#project_name');
+ $projectNameError = document.querySelector('#project_name_error');
});
afterEach(() => {
@@ -84,6 +88,57 @@ describe('New Project', () => {
});
});
+ describe('tracks manual name input', () => {
+ beforeEach(() => {
+ projectNew.bindEvents();
+ });
+
+ afterEach(() => {
+ unmockTracking();
+ });
+
+ it('no error message by default', () => {
+ expect($projectNameError.classList.contains('hidden')).toBe(true);
+ });
+
+ it('show error message if name is validate', () => {
+ $projectName.value = '.validate!Name';
+ triggerEvent($projectName, 'change');
+
+ expect($projectNameError.innerText).toBe(
+ "Name must start with a letter, digit, emoji, or '_'",
+ );
+ expect($projectNameError.classList.contains('hidden')).toBe(false);
+ });
+ });
+
+ describe('project name rule', () => {
+ describe("Name must start with a letter, digit, emoji, or '_'", () => {
+ const errormsg = "Name must start with a letter, digit, emoji, or '_'";
+ it("'.foo' should error", () => {
+ const text = '.foo';
+ expect(checkRules(text)).toBe(errormsg);
+ });
+ it('_foo should passed', () => {
+ const text = '_foo';
+ expect(checkRules(text)).toBe('');
+ });
+ });
+
+ describe("Name can contain only letters, digits, emojis, '_', '.', '+', dashes, or spaces", () => {
+ const errormsg =
+ "Name can contain only letters, digits, emojis, '_', '.', '+', dashes, or spaces";
+ it("'foo(#^.^#)foo' should error", () => {
+ const text = 'foo(#^.^#)foo';
+ expect(checkRules(text)).toBe(errormsg);
+ });
+ it("'foo123😊_.+- ' should passed", () => {
+ const text = 'foo123😊_.+- ';
+ expect(checkRules(text)).toBe('');
+ });
+ });
+ });
+
describe('deriveProjectPathFromUrl', () => {
const dummyImportUrl = `${TEST_HOST}/dummy/import/url.git`;
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 30475b36561..a2b34fe38a9 100644
--- a/spec/frontend/work_items/components/work_item_detail_spec.js
+++ b/spec/frontend/work_items/components/work_item_detail_spec.js
@@ -21,6 +21,7 @@ import WorkItemTitle from '~/work_items/components/work_item_title.vue';
import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue';
import WorkItemLabels from '~/work_items/components/work_item_labels.vue';
import WorkItemMilestone from '~/work_items/components/work_item_milestone.vue';
+import WorkItemTree from '~/work_items/components/work_item_links/work_item_tree.vue';
import { i18n } from '~/work_items/constants';
import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
@@ -38,6 +39,7 @@ import {
workItemAssigneesSubscriptionResponse,
workItemMilestoneSubscriptionResponse,
projectWorkItemResponse,
+ objectiveType,
} from '../mock_data';
describe('WorkItemDetail component', () => {
@@ -78,6 +80,7 @@ describe('WorkItemDetail component', () => {
const findParentButton = () => findParent().findComponent(GlButton);
const findCloseButton = () => wrapper.find('[data-testid="work-item-close"]');
const findWorkItemType = () => wrapper.find('[data-testid="work-item-type"]');
+ const findHierarchyTree = () => wrapper.findComponent(WorkItemTree);
const createComponent = ({
isModal = false,
@@ -638,4 +641,24 @@ describe('WorkItemDetail component', () => {
iid: '1',
});
});
+
+ describe('hierarchy widget', () => {
+ it('does not render children tree by default', async () => {
+ createComponent();
+ await waitForPromises();
+
+ expect(findHierarchyTree().exists()).toBe(false);
+ });
+
+ it('renders children tree when work item is an Objective', async () => {
+ const objectiveWorkItem = workItemResponseFactory({
+ workItemType: objectiveType,
+ });
+ const handler = jest.fn().mockResolvedValue(objectiveWorkItem);
+ createComponent({ handler });
+ await waitForPromises();
+
+ expect(findHierarchyTree().exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/work_items/components/work_item_links/okr_actions_split_button_spec.js b/spec/frontend/work_items/components/work_item_links/okr_actions_split_button_spec.js
index 5563ba12a45..48711ddf15d 100644
--- a/spec/frontend/work_items/components/work_item_links/okr_actions_split_button_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/okr_actions_split_button_spec.js
@@ -25,9 +25,13 @@ describe('RelatedItemsTree', () => {
expect(wrapper.findAllComponents(GlDropdownSectionHeader).at(0).text()).toContain(
'Objective',
);
- expect(wrapper.findAllComponents(GlDropdownSectionHeader).at(1).text()).toContain(
- 'Key result',
- );
+
+ // TODO: Uncomment once following two issues addressed
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/381833
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/385084
+ // expect(wrapper.findAllComponents(GlDropdownSectionHeader).at(1).text()).toContain(
+ // 'Key result',
+ // );
});
});
});
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 e345e5fc7cd..3a8e785bc80 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
@@ -1,33 +1,68 @@
import { GlButton, GlIcon } from '@gitlab/ui';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { createAlert } from '~/flash';
import RichTimestampTooltip from '~/vue_shared/components/rich_timestamp_tooltip.vue';
+import getWorkItemTreeQuery from '~/work_items/graphql/work_item_tree.query.graphql';
import WorkItemLinkChild from '~/work_items/components/work_item_links/work_item_link_child.vue';
import WorkItemLinksMenu from '~/work_items/components/work_item_links/work_item_links_menu.vue';
-
-import { workItemTask, confidentialWorkItemTask, closedWorkItemTask } from '../../mock_data';
+import WorkItemTreeChildren from '~/work_items/components/work_item_links/work_item_tree_children.vue';
+import {
+ WIDGET_TYPE_HIERARCHY,
+ TASK_TYPE_NAME,
+ WORK_ITEM_TYPE_VALUE_OBJECTIVE,
+} from '~/work_items/constants';
+
+import {
+ workItemTask,
+ workItemObjectiveWithChild,
+ confidentialWorkItemTask,
+ closedWorkItemTask,
+ workItemHierarchyTreeResponse,
+ workItemHierarchyTreeFailureResponse,
+} from '../../mock_data';
+
+jest.mock('~/flash');
describe('WorkItemLinkChild', () => {
const WORK_ITEM_ID = 'gid://gitlab/WorkItem/2';
let wrapper;
+ let getWorkItemTreeQueryHandler;
+
+ Vue.use(VueApollo);
const createComponent = ({
projectPath = 'gitlab-org/gitlab-test',
canUpdate = true,
issuableGid = WORK_ITEM_ID,
childItem = workItemTask,
+ workItemType = TASK_TYPE_NAME,
+ apolloProvider = null,
} = {}) => {
+ getWorkItemTreeQueryHandler = jest.fn().mockResolvedValue(workItemHierarchyTreeResponse);
+
wrapper = shallowMountExtended(WorkItemLinkChild, {
+ apolloProvider:
+ apolloProvider || createMockApollo([[getWorkItemTreeQuery, getWorkItemTreeQueryHandler]]),
propsData: {
projectPath,
canUpdate,
issuableGid,
childItem,
+ workItemType,
},
});
};
+ beforeEach(() => {
+ createAlert.mockClear();
+ });
+
afterEach(() => {
wrapper.destroy();
});
@@ -121,7 +156,78 @@ describe('WorkItemLinkChild', () => {
it('removeChild event on menu triggers `click-remove-child` event', () => {
itemMenuEl.vm.$emit('removeChild');
- expect(wrapper.emitted('remove')).toEqual([[workItemTask.id]]);
+ expect(wrapper.emitted('removeChild')).toEqual([[workItemTask.id]]);
+ });
+ });
+
+ describe('nested children', () => {
+ const findExpandButton = () => wrapper.findByTestId('expand-child');
+ const findTreeChildren = () => wrapper.findComponent(WorkItemTreeChildren);
+
+ beforeEach(() => {
+ getWorkItemTreeQueryHandler.mockClear();
+ createComponent({
+ childItem: workItemObjectiveWithChild,
+ workItemType: WORK_ITEM_TYPE_VALUE_OBJECTIVE,
+ });
+ });
+
+ it('displays expand button when item has children, children are not displayed by default', () => {
+ expect(findExpandButton().exists()).toBe(true);
+ expect(findTreeChildren().exists()).toBe(false);
+ });
+
+ it('fetches and displays children of item when clicking on expand button', async () => {
+ await findExpandButton().vm.$emit('click');
+
+ expect(findExpandButton().props('loading')).toBe(true);
+ await waitForPromises();
+
+ expect(getWorkItemTreeQueryHandler).toHaveBeenCalled();
+ expect(findTreeChildren().exists()).toBe(true);
+
+ const widgetHierarchy = workItemHierarchyTreeResponse.data.workItem.widgets.find(
+ (widget) => widget.type === WIDGET_TYPE_HIERARCHY,
+ );
+ expect(findTreeChildren().props('children')).toEqual(widgetHierarchy.children.nodes);
+ });
+
+ it('does not fetch children if already fetched once while clicking expand button', async () => {
+ findExpandButton().vm.$emit('click'); // Expand for the first time
+ await waitForPromises();
+
+ expect(findTreeChildren().exists()).toBe(true);
+
+ await findExpandButton().vm.$emit('click'); // Collapse
+ findExpandButton().vm.$emit('click'); // Expand again
+ await waitForPromises();
+
+ expect(getWorkItemTreeQueryHandler).toHaveBeenCalledTimes(1); // ensure children were fetched only once.
+ expect(findTreeChildren().exists()).toBe(true);
+ });
+
+ it('calls createAlert when children fetch request fails on clicking expand button', async () => {
+ const getWorkItemTreeQueryFailureHandler = jest
+ .fn()
+ .mockRejectedValue(workItemHierarchyTreeFailureResponse);
+ const apolloProvider = createMockApollo([
+ [getWorkItemTreeQuery, getWorkItemTreeQueryFailureHandler],
+ ]);
+
+ createComponent({
+ childItem: workItemObjectiveWithChild,
+ workItemType: WORK_ITEM_TYPE_VALUE_OBJECTIVE,
+ apolloProvider,
+ });
+
+ findExpandButton().vm.$emit('click');
+ await waitForPromises();
+
+ expect(createAlert).toHaveBeenCalledWith({
+ captureError: true,
+ error: expect.any(Object),
+ message: 'Something went wrong while fetching children.',
+ });
});
});
});
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 fe95a985177..a61de78c623 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
@@ -257,7 +257,7 @@ describe('WorkItemLinks', () => {
});
it('calls correct mutation with correct variables', async () => {
- firstChild.vm.$emit('remove', firstChild.vm.childItem.id);
+ firstChild.vm.$emit('removeChild', firstChild.vm.childItem.id);
await waitForPromises();
@@ -272,7 +272,7 @@ describe('WorkItemLinks', () => {
});
it('shows toast when mutation succeeds', async () => {
- firstChild.vm.$emit('remove', firstChild.vm.childItem.id);
+ firstChild.vm.$emit('removeChild', firstChild.vm.childItem.id);
await waitForPromises();
@@ -284,7 +284,7 @@ describe('WorkItemLinks', () => {
it('renders correct number of children after removal', async () => {
expect(findWorkItemLinkChildItems()).toHaveLength(4);
- firstChild.vm.$emit('remove', firstChild.vm.childItem.id);
+ firstChild.vm.$emit('removeChild', firstChild.vm.childItem.id);
await waitForPromises();
expect(findWorkItemLinkChildItems()).toHaveLength(3);
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 9c1e9ccb6e8..cc2e174dfda 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
@@ -2,12 +2,14 @@ import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import WorkItemTree from '~/work_items/components/work_item_links/work_item_tree.vue';
import WorkItemLinksForm from '~/work_items/components/work_item_links/work_item_links_form.vue';
+import WorkItemLinkChild from '~/work_items/components/work_item_links/work_item_link_child.vue';
import OkrActionsSplitButton from '~/work_items/components/work_item_links/okr_actions_split_button.vue';
import {
FORM_TYPES,
WORK_ITEM_TYPE_ENUM_OBJECTIVE,
WORK_ITEM_TYPE_ENUM_KEY_RESULT,
} from '~/work_items/constants';
+import { childrenWorkItems } from '../../mock_data';
describe('WorkItemTree', () => {
let wrapper;
@@ -17,10 +19,16 @@ describe('WorkItemTree', () => {
const findEmptyState = () => wrapper.findByTestId('tree-empty');
const findToggleFormSplitButton = () => wrapper.findComponent(OkrActionsSplitButton);
const findForm = () => wrapper.findComponent(WorkItemLinksForm);
+ const findWorkItemLinkChildItems = () => wrapper.findAllComponents(WorkItemLinkChild);
- const createComponent = () => {
+ const createComponent = ({ children = childrenWorkItems } = {}) => {
wrapper = shallowMountExtended(WorkItemTree, {
- propsData: { workItemType: 'Objective', workItemId: 'gid://gitlab/WorkItem/515' },
+ propsData: {
+ workItemType: 'Objective',
+ workItemId: 'gid://gitlab/WorkItem/515',
+ children,
+ projectPath: 'test/project',
+ },
});
};
@@ -47,9 +55,14 @@ describe('WorkItemTree', () => {
});
it('displays empty state if there are no children', () => {
+ createComponent({ children: [] });
expect(findEmptyState().exists()).toBe(true);
});
+ it('renders all hierarchy widget children', () => {
+ expect(findWorkItemLinkChildItems()).toHaveLength(4);
+ });
+
it('does not display form by default', () => {
expect(findForm().exists()).toBe(false);
});
@@ -71,4 +84,11 @@ describe('WorkItemTree', () => {
expect(findForm().props('childrenType')).toBe(childType);
},
);
+
+ it('remove event on child triggers `removeChild` event', () => {
+ const firstChild = findWorkItemLinkChildItems().at(0);
+ firstChild.vm.$emit('removeChild', 'gid://gitlab/WorkItem/2');
+
+ expect(wrapper.emitted('removeChild')).toEqual([['gid://gitlab/WorkItem/2']]);
+ });
});
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index a4c16e014ef..7bade734586 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -85,6 +85,7 @@ export const workItemQueryResponse = {
{
__typename: 'WorkItemWidgetHierarchy',
type: 'HIERARCHY',
+ hasChildren: true,
parent: {
id: 'gid://gitlab/Issue/1',
iid: '5',
@@ -108,7 +109,15 @@ export const workItemQueryResponse = {
state: 'OPEN',
workItemType: {
id: '1',
+ name: 'Task',
+ iconName: 'issue-type-task',
},
+ widgets: [
+ {
+ type: 'HIERARCHY',
+ hasChildren: false,
+ },
+ ],
},
],
},
@@ -150,6 +159,7 @@ export const updateWorkItemMutationResponse = {
},
widgets: [
{
+ type: 'HIERARCHY',
children: {
nodes: [
{
@@ -161,10 +171,13 @@ export const updateWorkItemMutationResponse = {
state: 'OPEN',
workItemType: {
id: '1',
+ name: 'Task',
+ iconName: 'issue-type-task',
},
},
],
},
+ __typename: 'WorkItemConnection',
},
{
__typename: 'WorkItemWidgetAssignees',
@@ -219,6 +232,20 @@ export const descriptionHtmlWithCheckboxes = `
</ul>
`;
+const taskType = {
+ __typename: 'WorkItemType',
+ id: 'gid://gitlab/WorkItems::Type/5',
+ name: 'Task',
+ iconName: 'issue-type-task',
+};
+
+export const objectiveType = {
+ __typename: 'WorkItemType',
+ id: 'gid://gitlab/WorkItems::Type/2411',
+ name: 'Objective',
+ iconName: 'issue-type-objective',
+};
+
export const workItemResponseFactory = ({
canUpdate = false,
canDelete = false,
@@ -236,6 +263,7 @@ export const workItemResponseFactory = ({
lastEditedBy = null,
withCheckboxes = false,
parent = mockParent.parent,
+ workItemType = taskType,
} = {}) => ({
data: {
workItem: {
@@ -253,12 +281,7 @@ export const workItemResponseFactory = ({
id: '1',
fullPath: 'test-project-path',
},
- workItemType: {
- __typename: 'WorkItemType',
- id: 'gid://gitlab/WorkItems::Type/5',
- name: 'Task',
- iconName: 'issue-type-task',
- },
+ workItemType,
userPermissions: {
deleteWorkItem: canDelete,
updateWorkItem: canUpdate,
@@ -338,6 +361,7 @@ export const workItemResponseFactory = ({
{
__typename: 'WorkItemWidgetHierarchy',
type: 'HIERARCHY',
+ hasChildren: true,
children: {
nodes: [
{
@@ -349,7 +373,15 @@ export const workItemResponseFactory = ({
state: 'OPEN',
workItemType: {
id: '1',
+ name: 'Task',
+ iconName: 'issue-type-task',
},
+ widgets: [
+ {
+ type: 'HIERARCHY',
+ hasChildren: false,
+ },
+ ],
},
],
},
@@ -669,6 +701,8 @@ export const workItemHierarchyEmptyResponse = {
id: 'gid://gitlab/WorkItem/1',
workItemType: {
id: 'gid://gitlab/WorkItems::Type/6',
+ name: 'Issue',
+ iconName: 'issue-type-issue',
__typename: 'WorkItemType',
},
title: 'New title',
@@ -692,6 +726,7 @@ export const workItemHierarchyEmptyResponse = {
{
type: 'HIERARCHY',
parent: null,
+ hasChildren: false,
children: {
nodes: [],
__typename: 'WorkItemConnection',
@@ -710,6 +745,8 @@ export const workItemHierarchyNoUpdatePermissionResponse = {
id: 'gid://gitlab/WorkItem/1',
workItemType: {
id: 'gid://gitlab/WorkItems::Type/6',
+ name: 'Issue',
+ iconName: 'issue-type-issue',
__typename: 'WorkItemType',
},
title: 'New title',
@@ -731,6 +768,7 @@ export const workItemHierarchyNoUpdatePermissionResponse = {
{
type: 'HIERARCHY',
parent: null,
+ hasChildren: true,
children: {
nodes: [
{
@@ -738,6 +776,8 @@ export const workItemHierarchyNoUpdatePermissionResponse = {
iid: '2',
workItemType: {
id: 'gid://gitlab/WorkItems::Type/5',
+ name: 'Task',
+ iconName: 'issue-type-task',
__typename: 'WorkItemType',
},
title: 'xyz',
@@ -745,6 +785,12 @@ export const workItemHierarchyNoUpdatePermissionResponse = {
confidential: false,
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
+ widgets: [
+ {
+ type: 'HIERARCHY',
+ hasChildren: false,
+ },
+ ],
__typename: 'WorkItem',
},
],
@@ -763,6 +809,8 @@ export const workItemTask = {
iid: '4',
workItemType: {
id: 'gid://gitlab/WorkItems::Type/5',
+ name: 'Task',
+ iconName: 'issue-type-task',
__typename: 'WorkItemType',
},
title: 'bar',
@@ -778,6 +826,8 @@ export const confidentialWorkItemTask = {
iid: '2',
workItemType: {
id: 'gid://gitlab/WorkItems::Type/5',
+ name: 'Task',
+ iconName: 'issue-type-task',
__typename: 'WorkItemType',
},
title: 'xyz',
@@ -793,6 +843,8 @@ export const closedWorkItemTask = {
iid: '3',
workItemType: {
id: 'gid://gitlab/WorkItems::Type/5',
+ name: 'Task',
+ iconName: 'issue-type-task',
__typename: 'WorkItemType',
},
title: 'abc',
@@ -803,6 +855,28 @@ export const closedWorkItemTask = {
__typename: 'WorkItem',
};
+export const childrenWorkItems = [
+ confidentialWorkItemTask,
+ closedWorkItemTask,
+ workItemTask,
+ {
+ id: 'gid://gitlab/WorkItem/5',
+ iid: '5',
+ workItemType: {
+ id: 'gid://gitlab/WorkItems::Type/5',
+ name: 'Task',
+ iconName: 'issue-type-task',
+ __typename: 'WorkItemType',
+ },
+ title: 'foobar',
+ state: 'OPEN',
+ confidential: false,
+ createdAt: '2022-08-03T12:41:54Z',
+ closedAt: null,
+ __typename: 'WorkItem',
+ },
+];
+
export const workItemHierarchyResponse = {
data: {
workItem: {
@@ -810,6 +884,8 @@ export const workItemHierarchyResponse = {
iid: '1',
workItemType: {
id: 'gid://gitlab/WorkItems::Type/6',
+ name: 'Objective',
+ iconName: 'issue-type-objective',
__typename: 'WorkItemType',
},
title: 'New title',
@@ -831,23 +907,97 @@ export const workItemHierarchyResponse = {
{
type: 'HIERARCHY',
parent: null,
+ hasChildren: true,
+ children: {
+ nodes: childrenWorkItems,
+ __typename: 'WorkItemConnection',
+ },
+ __typename: 'WorkItemWidgetHierarchy',
+ },
+ ],
+ __typename: 'WorkItem',
+ },
+ },
+};
+
+export const workItemObjectiveWithChild = {
+ id: 'gid://gitlab/WorkItem/12',
+ iid: '12',
+ workItemType: {
+ id: 'gid://gitlab/WorkItems::Type/2411',
+ name: 'Objective',
+ iconName: 'issue-type-objective',
+ __typename: 'WorkItemType',
+ },
+ title: 'Objective',
+ state: 'OPEN',
+ confidential: false,
+ createdAt: '2022-08-03T12:41:54Z',
+ closedAt: null,
+ widgets: [
+ {
+ type: 'HIERARCHY',
+ hasChildren: true,
+ __typename: 'WorkItemWidgetHierarchy',
+ },
+ ],
+ __typename: 'WorkItem',
+};
+
+export const workItemHierarchyTreeResponse = {
+ data: {
+ workItem: {
+ id: 'gid://gitlab/WorkItem/2',
+ iid: '2',
+ workItemType: {
+ id: 'gid://gitlab/WorkItems::Type/2411',
+ name: 'Objective',
+ iconName: 'issue-type-objective',
+ __typename: 'WorkItemType',
+ },
+ title: 'New title',
+ userPermissions: {
+ deleteWorkItem: true,
+ updateWorkItem: true,
+ },
+ confidential: false,
+ project: {
+ __typename: 'Project',
+ id: '1',
+ fullPath: 'test-project-path',
+ },
+ widgets: [
+ {
+ type: 'DESCRIPTION',
+ __typename: 'WorkItemWidgetDescription',
+ },
+ {
+ type: 'HIERARCHY',
+ parent: null,
+ hasChildren: true,
children: {
nodes: [
- confidentialWorkItemTask,
- closedWorkItemTask,
- workItemTask,
{
- id: 'gid://gitlab/WorkItem/5',
- iid: '5',
+ id: 'gid://gitlab/WorkItem/13',
+ iid: '13',
workItemType: {
- id: 'gid://gitlab/WorkItems::Type/5',
+ id: 'gid://gitlab/WorkItems::Type/2411',
+ name: 'Objective',
+ iconName: 'issue-type-objective',
__typename: 'WorkItemType',
},
- title: 'foobar',
+ title: 'Objective 2',
state: 'OPEN',
confidential: false,
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
+ widgets: [
+ {
+ type: 'HIERARCHY',
+ hasChildren: true,
+ __typename: 'WorkItemWidgetHierarchy',
+ },
+ ],
__typename: 'WorkItem',
},
],
@@ -861,6 +1011,15 @@ export const workItemHierarchyResponse = {
},
};
+export const workItemHierarchyTreeFailureResponse = {
+ data: {},
+ errors: [
+ {
+ message: 'Something went wrong',
+ },
+ ],
+};
+
export const changeWorkItemParentMutationResponse = {
data: {
workItemUpdate: {
@@ -894,6 +1053,7 @@ export const changeWorkItemParentMutationResponse = {
__typename: 'WorkItemWidgetHierarchy',
type: 'HIERARCHY',
parent: null,
+ hasChildren: false,
children: {
nodes: [],
},