summaryrefslogtreecommitdiff
path: root/spec/frontend/work_items
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-04-20 10:00:54 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-04-20 10:00:54 +0000
commit3cccd102ba543e02725d247893729e5c73b38295 (patch)
treef36a04ec38517f5deaaacb5acc7d949688d1e187 /spec/frontend/work_items
parent205943281328046ef7b4528031b90fbda70c75ac (diff)
downloadgitlab-ce-3cccd102ba543e02725d247893729e5c73b38295.tar.gz
Add latest changes from gitlab-org/gitlab@14-10-stable-eev14.10.0-rc42
Diffstat (limited to 'spec/frontend/work_items')
-rw-r--r--spec/frontend/work_items/components/item_title_spec.js4
-rw-r--r--spec/frontend/work_items/components/work_item_actions_spec.js103
-rw-r--r--spec/frontend/work_items/components/work_item_detail_modal_spec.js58
-rw-r--r--spec/frontend/work_items/components/work_item_detail_spec.js40
-rw-r--r--spec/frontend/work_items/components/work_item_title_spec.js117
-rw-r--r--spec/frontend/work_items/mock_data.js97
-rw-r--r--spec/frontend/work_items/pages/create_work_item_spec.js73
-rw-r--r--spec/frontend/work_items/pages/work_item_detail_spec.js99
-rw-r--r--spec/frontend/work_items/pages/work_item_root_spec.js91
-rw-r--r--spec/frontend/work_items/router_spec.js2
10 files changed, 492 insertions, 192 deletions
diff --git a/spec/frontend/work_items/components/item_title_spec.js b/spec/frontend/work_items/components/item_title_spec.js
index 0f6e7091c59..0d85df25b4f 100644
--- a/spec/frontend/work_items/components/item_title_spec.js
+++ b/spec/frontend/work_items/components/item_title_spec.js
@@ -4,10 +4,10 @@ import ItemTitle from '~/work_items/components/item_title.vue';
jest.mock('lodash/escape', () => jest.fn((fn) => fn));
-const createComponent = ({ initialTitle = 'Sample title', disabled = false } = {}) =>
+const createComponent = ({ title = 'Sample title', disabled = false } = {}) =>
shallowMount(ItemTitle, {
propsData: {
- initialTitle,
+ title,
disabled,
},
});
diff --git a/spec/frontend/work_items/components/work_item_actions_spec.js b/spec/frontend/work_items/components/work_item_actions_spec.js
new file mode 100644
index 00000000000..d0e9cfee353
--- /dev/null
+++ b/spec/frontend/work_items/components/work_item_actions_spec.js
@@ -0,0 +1,103 @@
+import { GlDropdownItem, GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import waitForPromises from 'helpers/wait_for_promises';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import WorkItemActions from '~/work_items/components/work_item_actions.vue';
+import deleteWorkItem from '~/work_items/graphql/delete_work_item.mutation.graphql';
+import { deleteWorkItemResponse, deleteWorkItemFailureResponse } from '../mock_data';
+
+describe('WorkItemActions component', () => {
+ let wrapper;
+ let glModalDirective;
+
+ Vue.use(VueApollo);
+
+ const findModal = () => wrapper.findComponent(GlModal);
+ const findDeleteButton = () => wrapper.findComponent(GlDropdownItem);
+
+ const createComponent = ({
+ canUpdate = true,
+ deleteWorkItemHandler = jest.fn().mockResolvedValue(deleteWorkItemResponse),
+ } = {}) => {
+ glModalDirective = jest.fn();
+ wrapper = shallowMount(WorkItemActions, {
+ apolloProvider: createMockApollo([[deleteWorkItem, deleteWorkItemHandler]]),
+ propsData: { workItemId: '123', canUpdate },
+ directives: {
+ glModal: {
+ bind(_, { value }) {
+ glModalDirective(value);
+ },
+ },
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders modal', () => {
+ createComponent();
+
+ expect(findModal().exists()).toBe(true);
+ expect(findModal().props('visible')).toBe(false);
+ });
+
+ it('shows confirm modal when clicking Delete work item', () => {
+ createComponent();
+
+ findDeleteButton().vm.$emit('click');
+
+ expect(glModalDirective).toHaveBeenCalled();
+ });
+
+ it('calls delete mutation when clicking OK button', () => {
+ const deleteWorkItemHandler = jest.fn().mockResolvedValue(deleteWorkItemResponse);
+
+ createComponent({
+ deleteWorkItemHandler,
+ });
+
+ findModal().vm.$emit('ok');
+
+ expect(deleteWorkItemHandler).toHaveBeenCalled();
+ expect(wrapper.emitted('error')).toBeUndefined();
+ });
+
+ it('emits event after delete success', async () => {
+ createComponent();
+
+ findModal().vm.$emit('ok');
+
+ await waitForPromises();
+
+ expect(wrapper.emitted('workItemDeleted')).not.toBeUndefined();
+ expect(wrapper.emitted('error')).toBeUndefined();
+ });
+
+ it('emits error event after delete failure', async () => {
+ createComponent({
+ deleteWorkItemHandler: jest.fn().mockResolvedValue(deleteWorkItemFailureResponse),
+ });
+
+ findModal().vm.$emit('ok');
+
+ await waitForPromises();
+
+ expect(wrapper.emitted('error')[0]).toEqual([
+ "The resource that you are attempting to access does not exist or you don't have permission to perform this action",
+ ]);
+ expect(wrapper.emitted('workItemDeleted')).toBeUndefined();
+ });
+
+ it('does not render when canUpdate is false', () => {
+ createComponent({
+ canUpdate: false,
+ });
+
+ expect(wrapper.html()).toBe('');
+ });
+});
diff --git a/spec/frontend/work_items/components/work_item_detail_modal_spec.js b/spec/frontend/work_items/components/work_item_detail_modal_spec.js
new file mode 100644
index 00000000000..9f35ccb853b
--- /dev/null
+++ b/spec/frontend/work_items/components/work_item_detail_modal_spec.js
@@ -0,0 +1,58 @@
+import { GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import WorkItemDetail from '~/work_items/components/work_item_detail.vue';
+import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue';
+import WorkItemActions from '~/work_items/components/work_item_actions.vue';
+
+describe('WorkItemDetailModal component', () => {
+ let wrapper;
+
+ Vue.use(VueApollo);
+
+ const findModal = () => wrapper.findComponent(GlModal);
+ const findWorkItemActions = () => wrapper.findComponent(WorkItemActions);
+ const findWorkItemDetail = () => wrapper.findComponent(WorkItemDetail);
+
+ const createComponent = ({ visible = true, workItemId = '1', canUpdate = false } = {}) => {
+ wrapper = shallowMount(WorkItemDetailModal, {
+ propsData: { visible, workItemId, canUpdate },
+ stubs: {
+ GlModal,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe.each([true, false])('when visible=%s', (visible) => {
+ it(`${visible ? 'renders' : 'does not render'} modal`, () => {
+ createComponent({ visible });
+
+ expect(findModal().props('visible')).toBe(visible);
+ });
+ });
+
+ it('renders heading', () => {
+ createComponent();
+
+ expect(wrapper.find('h2').text()).toBe('Work Item');
+ });
+
+ it('renders WorkItemDetail', () => {
+ createComponent();
+
+ expect(findWorkItemDetail().props()).toEqual({ workItemId: '1' });
+ });
+
+ it('shows work item actions', () => {
+ createComponent({
+ canUpdate: true,
+ });
+
+ expect(findWorkItemActions().exists()).toBe(true);
+ });
+});
diff --git a/spec/frontend/work_items/components/work_item_detail_spec.js b/spec/frontend/work_items/components/work_item_detail_spec.js
deleted file mode 100644
index 305f43ad8ba..00000000000
--- a/spec/frontend/work_items/components/work_item_detail_spec.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import { GlModal } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
-import VueApollo from 'vue-apollo';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import WorkItemTitle from '~/work_items/components/item_title.vue';
-import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue';
-import { resolvers } from '~/work_items/graphql/resolvers';
-
-describe('WorkItemDetailModal component', () => {
- let wrapper;
-
- Vue.use(VueApollo);
-
- const findModal = () => wrapper.findComponent(GlModal);
- const findWorkItemTitle = () => wrapper.findComponent(WorkItemTitle);
-
- const createComponent = () => {
- wrapper = shallowMount(WorkItemDetailModal, {
- apolloProvider: createMockApollo([], resolvers),
- propsData: { visible: true },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('renders modal', () => {
- createComponent();
-
- expect(findModal().props()).toMatchObject({ visible: true });
- });
-
- it('renders work item title', () => {
- createComponent();
-
- expect(findWorkItemTitle().exists()).toBe(true);
- });
-});
diff --git a/spec/frontend/work_items/components/work_item_title_spec.js b/spec/frontend/work_items/components/work_item_title_spec.js
new file mode 100644
index 00000000000..9b1ef2d14e4
--- /dev/null
+++ b/spec/frontend/work_items/components/work_item_title_spec.js
@@ -0,0 +1,117 @@
+import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { mockTracking } from 'helpers/tracking_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import ItemTitle from '~/work_items/components/item_title.vue';
+import WorkItemTitle from '~/work_items/components/work_item_title.vue';
+import { i18n } from '~/work_items/constants';
+import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
+import { updateWorkItemMutationResponse, workItemQueryResponse } from '../mock_data';
+
+describe('WorkItemTitle component', () => {
+ let wrapper;
+
+ Vue.use(VueApollo);
+
+ const mutationSuccessHandler = jest.fn().mockResolvedValue(updateWorkItemMutationResponse);
+
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findItemTitle = () => wrapper.findComponent(ItemTitle);
+
+ const createComponent = ({ loading = false, mutationHandler = mutationSuccessHandler } = {}) => {
+ const { id, title, workItemType } = workItemQueryResponse.data.workItem;
+ wrapper = shallowMount(WorkItemTitle, {
+ apolloProvider: createMockApollo([[updateWorkItemMutation, mutationHandler]]),
+ propsData: {
+ loading,
+ workItemId: id,
+ workItemTitle: title,
+ workItemType: workItemType.name,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when loading', () => {
+ beforeEach(() => {
+ createComponent({ loading: true });
+ });
+
+ it('renders loading spinner', () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+ });
+
+ it('does not render title', () => {
+ expect(findItemTitle().exists()).toBe(false);
+ });
+ });
+
+ describe('when loaded', () => {
+ beforeEach(() => {
+ createComponent({ loading: false });
+ });
+
+ it('does not render loading spinner', () => {
+ expect(findLoadingIcon().exists()).toBe(false);
+ });
+
+ it('renders title', () => {
+ expect(findItemTitle().props('title')).toBe(workItemQueryResponse.data.workItem.title);
+ });
+ });
+
+ describe('when updating the title', () => {
+ it('calls a mutation', () => {
+ const title = 'new title!';
+
+ createComponent();
+
+ findItemTitle().vm.$emit('title-changed', title);
+
+ expect(mutationSuccessHandler).toHaveBeenCalledWith({
+ input: {
+ id: workItemQueryResponse.data.workItem.id,
+ title,
+ },
+ });
+ });
+
+ it('does not call a mutation when the title has not changed', () => {
+ createComponent();
+
+ findItemTitle().vm.$emit('title-changed', workItemQueryResponse.data.workItem.title);
+
+ expect(mutationSuccessHandler).not.toHaveBeenCalled();
+ });
+
+ it('emits an error message when the mutation was unsuccessful', async () => {
+ createComponent({ mutationHandler: jest.fn().mockRejectedValue('Error!') });
+
+ findItemTitle().vm.$emit('title-changed', 'new title');
+ await waitForPromises();
+
+ expect(wrapper.emitted('error')).toEqual([[i18n.updateError]]);
+ });
+
+ it('tracks editing the title', async () => {
+ const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+
+ createComponent();
+
+ findItemTitle().vm.$emit('title-changed', 'new title');
+ await waitForPromises();
+
+ expect(trackingSpy).toHaveBeenCalledWith('workItems:show', 'updated_title', {
+ category: 'workItems:show',
+ label: 'item_title',
+ property: 'type_Task',
+ });
+ });
+ });
+});
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index 832795fc4ac..722e1708c15 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -1,21 +1,14 @@
export const workItemQueryResponse = {
- workItem: {
- __typename: 'WorkItem',
- id: '1',
- title: 'Test',
- workItemType: {
- __typename: 'WorkItemType',
- id: 'work-item-type-1',
- },
- widgets: {
- __typename: 'LocalWorkItemWidgetConnection',
- nodes: [
- {
- __typename: 'LocalTitleWidget',
- type: 'TITLE',
- contentText: 'Test',
- },
- ],
+ data: {
+ workItem: {
+ __typename: 'WorkItem',
+ id: 'gid://gitlab/WorkItem/1',
+ title: 'Test',
+ workItemType: {
+ __typename: 'WorkItemType',
+ id: 'gid://gitlab/WorkItems::Type/5',
+ name: 'Task',
+ },
},
},
};
@@ -23,25 +16,15 @@ export const workItemQueryResponse = {
export const updateWorkItemMutationResponse = {
data: {
workItemUpdate: {
- __typename: 'LocalUpdateWorkItemPayload',
+ __typename: 'WorkItemUpdatePayload',
workItem: {
- __typename: 'LocalWorkItem',
- id: '1',
+ __typename: 'WorkItem',
+ id: 'gid://gitlab/WorkItem/1',
title: 'Updated title',
workItemType: {
__typename: 'WorkItemType',
- id: 'work-item-type-1',
- },
- widgets: {
- __typename: 'LocalWorkItemWidgetConnection',
- nodes: [
- {
- __typename: 'LocalTitleWidget',
- type: 'TITLE',
- enabled: true,
- contentText: 'Updated title',
- },
- ],
+ id: 'gid://gitlab/WorkItems::Type/5',
+ name: 'Task',
},
},
},
@@ -51,11 +34,11 @@ export const updateWorkItemMutationResponse = {
export const projectWorkItemTypesQueryResponse = {
data: {
workspace: {
- id: '1',
+ id: 'gid://gitlab/WorkItem/1',
workItemTypes: {
nodes: [
- { id: 'work-item-1', name: 'Issue' },
- { id: 'work-item-2', name: 'Incident' },
+ { id: 'gid://gitlab/WorkItems::Type/1', name: 'Issue' },
+ { id: 'gid://gitlab/WorkItems::Type/2', name: 'Incident' },
],
},
},
@@ -68,13 +51,53 @@ export const createWorkItemMutationResponse = {
__typename: 'WorkItemCreatePayload',
workItem: {
__typename: 'WorkItem',
- id: '1',
+ id: 'gid://gitlab/WorkItem/1',
title: 'Updated title',
workItemType: {
__typename: 'WorkItemType',
- id: 'work-item-type-1',
+ id: 'gid://gitlab/WorkItems::Type/5',
+ name: 'Task',
},
},
},
},
};
+
+export const createWorkItemFromTaskMutationResponse = {
+ data: {
+ workItemCreateFromTask: {
+ __typename: 'WorkItemCreateFromTaskPayload',
+ errors: [],
+ workItem: {
+ descriptionHtml: '<p>New description</p>',
+ id: 'gid://gitlab/WorkItem/13',
+ __typename: 'WorkItem',
+ },
+ },
+ },
+};
+
+export const deleteWorkItemResponse = {
+ data: { workItemDelete: { errors: [], __typename: 'WorkItemDeletePayload' } },
+};
+
+export const deleteWorkItemFailureResponse = {
+ data: { workItemDelete: null },
+ errors: [
+ {
+ message:
+ "The resource that you are attempting to access does not exist or you don't have permission to perform this action",
+ locations: [{ line: 2, column: 3 }],
+ path: ['workItemDelete'],
+ },
+ ],
+};
+
+export const workItemTitleSubscriptionResponse = {
+ data: {
+ issuableTitleUpdated: {
+ id: 'gid://gitlab/WorkItem/1',
+ title: 'new title',
+ },
+ },
+};
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 185b05c5191..fb1f1d56356 100644
--- a/spec/frontend/work_items/pages/create_work_item_spec.js
+++ b/spec/frontend/work_items/pages/create_work_item_spec.js
@@ -1,15 +1,19 @@
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-import { GlAlert, GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { GlAlert, GlFormSelect } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import CreateWorkItem from '~/work_items/pages/create_work_item.vue';
import ItemTitle from '~/work_items/components/item_title.vue';
-import { resolvers } from '~/work_items/graphql/resolvers';
import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
import createWorkItemMutation from '~/work_items/graphql/create_work_item.mutation.graphql';
-import { projectWorkItemTypesQueryResponse, createWorkItemMutationResponse } from '../mock_data';
+import createWorkItemFromTaskMutation from '~/work_items/graphql/create_work_item_from_task.mutation.graphql';
+import {
+ projectWorkItemTypesQueryResponse,
+ createWorkItemMutationResponse,
+ createWorkItemFromTaskMutationResponse,
+} from '../mock_data';
jest.mock('~/lib/utils/uuids', () => ({ uuids: () => ['testuuid'] }));
@@ -20,12 +24,15 @@ describe('Create work item component', () => {
let fakeApollo;
const querySuccessHandler = jest.fn().mockResolvedValue(projectWorkItemTypesQueryResponse);
- const mutationSuccessHandler = jest.fn().mockResolvedValue(createWorkItemMutationResponse);
+ const createWorkItemSuccessHandler = jest.fn().mockResolvedValue(createWorkItemMutationResponse);
+ const createWorkItemFromTaskSuccessHandler = jest
+ .fn()
+ .mockResolvedValue(createWorkItemFromTaskMutationResponse);
+ const errorHandler = jest.fn().mockRejectedValue('Houston, we have a problem');
const findAlert = () => wrapper.findComponent(GlAlert);
const findTitleInput = () => wrapper.findComponent(ItemTitle);
- const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findSelect = () => wrapper.findComponent(GlFormSelect);
const findCreateButton = () => wrapper.find('[data-testid="create-button"]');
const findCancelButton = () => wrapper.find('[data-testid="cancel-button"]');
@@ -36,15 +43,13 @@ describe('Create work item component', () => {
data = {},
props = {},
queryHandler = querySuccessHandler,
- mutationHandler = mutationSuccessHandler,
+ mutationHandler = createWorkItemSuccessHandler,
} = {}) => {
- fakeApollo = createMockApollo(
- [
- [projectWorkItemTypesQuery, queryHandler],
- [createWorkItemMutation, mutationHandler],
- ],
- resolvers,
- );
+ fakeApollo = createMockApollo([
+ [projectWorkItemTypesQuery, queryHandler],
+ [createWorkItemMutation, mutationHandler],
+ [createWorkItemFromTaskMutation, mutationHandler],
+ ]);
wrapper = shallowMount(CreateWorkItem, {
apolloProvider: fakeApollo,
data() {
@@ -123,6 +128,7 @@ describe('Create work item component', () => {
props: {
isModal: true,
},
+ mutationHandler: createWorkItemFromTaskSuccessHandler,
});
});
@@ -133,14 +139,12 @@ describe('Create work item component', () => {
});
it('emits `onCreate` on successful mutation', async () => {
- const mockTitle = 'Test title';
findTitleInput().vm.$emit('title-input', 'Test title');
wrapper.find('form').trigger('submit');
await waitForPromises();
- const expected = { id: '1', title: mockTitle };
- expect(wrapper.emitted('onCreate')).toEqual([[expected]]);
+ expect(wrapper.emitted('onCreate')).toEqual([['<p>New description</p>']]);
});
it('does not right margin for create button', () => {
@@ -177,16 +181,14 @@ describe('Create work item component', () => {
});
it('displays a list of work item types', () => {
- expect(findDropdownItems()).toHaveLength(2);
- expect(findDropdownItems().at(0).text()).toContain('Issue');
+ expect(findSelect().attributes('options').split(',')).toHaveLength(3);
});
it('selects a work item type on click', async () => {
- expect(findDropdown().props('text')).toBe('Type');
- findDropdownItems().at(0).vm.$emit('click');
+ const mockId = 'work-item-1';
+ findSelect().vm.$emit('input', mockId);
await nextTick();
-
- expect(findDropdown().props('text')).toBe('Issue');
+ expect(findSelect().attributes('value')).toBe(mockId);
});
});
@@ -206,21 +208,36 @@ describe('Create work item component', () => {
createComponent({
props: { initialTitle },
});
- expect(findTitleInput().props('initialTitle')).toBe(initialTitle);
+ expect(findTitleInput().props('title')).toBe(initialTitle);
});
describe('when title input field has a text', () => {
- beforeEach(() => {
+ beforeEach(async () => {
const mockTitle = 'Test title';
createComponent();
+ await waitForPromises();
findTitleInput().vm.$emit('title-input', mockTitle);
});
- it('renders a non-disabled Create button', () => {
+ it('renders a disabled Create button', () => {
+ expect(findCreateButton().props('disabled')).toBe(true);
+ });
+
+ it('renders a non-disabled Create button when work item type is selected', async () => {
+ findSelect().vm.$emit('input', 'work-item-1');
+ await nextTick();
expect(findCreateButton().props('disabled')).toBe(false);
});
+ });
+
+ it('shows an alert on mutation error', async () => {
+ createComponent({ mutationHandler: errorHandler });
+ await waitForPromises();
+ findTitleInput().vm.$emit('title-input', 'some title');
+ findSelect().vm.$emit('input', 'work-item-1');
+ wrapper.find('form').trigger('submit');
+ await waitForPromises();
- // TODO: write a proper test here when we have a backend implementation
- it.todo('shows an alert on mutation error');
+ expect(findAlert().text()).toBe(CreateWorkItem.createErrorText);
});
});
diff --git a/spec/frontend/work_items/pages/work_item_detail_spec.js b/spec/frontend/work_items/pages/work_item_detail_spec.js
new file mode 100644
index 00000000000..1eb6c0145e7
--- /dev/null
+++ b/spec/frontend/work_items/pages/work_item_detail_spec.js
@@ -0,0 +1,99 @@
+import { GlAlert } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import WorkItemDetail from '~/work_items/components/work_item_detail.vue';
+import WorkItemTitle from '~/work_items/components/work_item_title.vue';
+import { i18n } from '~/work_items/constants';
+import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
+import workItemTitleSubscription from '~/work_items/graphql/work_item_title.subscription.graphql';
+import { workItemTitleSubscriptionResponse, workItemQueryResponse } from '../mock_data';
+
+describe('WorkItemDetail component', () => {
+ let wrapper;
+
+ Vue.use(VueApollo);
+
+ const successHandler = jest.fn().mockResolvedValue(workItemQueryResponse);
+ const initialSubscriptionHandler = jest.fn().mockResolvedValue(workItemTitleSubscriptionResponse);
+
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findWorkItemTitle = () => wrapper.findComponent(WorkItemTitle);
+
+ const createComponent = ({
+ workItemId = workItemQueryResponse.data.workItem.id,
+ handler = successHandler,
+ subscriptionHandler = initialSubscriptionHandler,
+ } = {}) => {
+ wrapper = shallowMount(WorkItemDetail, {
+ apolloProvider: createMockApollo([
+ [workItemQuery, handler],
+ [workItemTitleSubscription, subscriptionHandler],
+ ]),
+ propsData: { workItemId },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when there is no `workItemId` prop', () => {
+ beforeEach(() => {
+ createComponent({ workItemId: null });
+ });
+
+ it('skips the work item query', () => {
+ expect(successHandler).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when loading', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders WorkItemTitle in loading state', () => {
+ expect(findWorkItemTitle().props('loading')).toBe(true);
+ });
+ });
+
+ describe('when loaded', () => {
+ beforeEach(() => {
+ createComponent();
+ return waitForPromises();
+ });
+
+ it('does not render WorkItemTitle in loading state', () => {
+ expect(findWorkItemTitle().props('loading')).toBe(false);
+ });
+ });
+
+ it('shows an error message when the work item query was unsuccessful', async () => {
+ const errorHandler = jest.fn().mockRejectedValue('Oops');
+ createComponent({ handler: errorHandler });
+ await waitForPromises();
+
+ expect(errorHandler).toHaveBeenCalled();
+ expect(findAlert().text()).toBe(i18n.fetchError);
+ });
+
+ it('shows an error message when WorkItemTitle emits an `error` event', async () => {
+ createComponent();
+
+ findWorkItemTitle().vm.$emit('error', i18n.updateError);
+ await waitForPromises();
+
+ expect(findAlert().text()).toBe(i18n.updateError);
+ });
+
+ it('calls the subscription', () => {
+ createComponent();
+
+ expect(initialSubscriptionHandler).toHaveBeenCalledWith({
+ issuableId: workItemQueryResponse.data.workItem.id,
+ });
+ });
+});
diff --git a/spec/frontend/work_items/pages/work_item_root_spec.js b/spec/frontend/work_items/pages/work_item_root_spec.js
index 728495e0e23..2803724b9af 100644
--- a/spec/frontend/work_items/pages/work_item_root_spec.js
+++ b/spec/frontend/work_items/pages/work_item_root_spec.js
@@ -1,108 +1,31 @@
-import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import waitForPromises from 'helpers/wait_for_promises';
-import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
-import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
-import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
+import WorkItemDetail from '~/work_items/components/work_item_detail.vue';
import WorkItemsRoot from '~/work_items/pages/work_item_root.vue';
-import ItemTitle from '~/work_items/components/item_title.vue';
-import { resolvers } from '~/work_items/graphql/resolvers';
-import { workItemQueryResponse, updateWorkItemMutationResponse } from '../mock_data';
Vue.use(VueApollo);
-const WORK_ITEM_ID = '1';
-const WORK_ITEM_GID = `gid://gitlab/WorkItem/${WORK_ITEM_ID}`;
-
describe('Work items root component', () => {
- const mockUpdatedTitle = 'Updated title';
let wrapper;
- let fakeApollo;
-
- const findTitle = () => wrapper.findComponent(ItemTitle);
- const createComponent = ({ queryResponse = workItemQueryResponse } = {}) => {
- fakeApollo = createMockApollo(
- [[updateWorkItemMutation, jest.fn().mockResolvedValue(updateWorkItemMutationResponse)]],
- resolvers,
- {
- possibleTypes: {
- LocalWorkItemWidget: ['LocalTitleWidget'],
- },
- },
- );
- fakeApollo.clients.defaultClient.cache.writeQuery({
- query: workItemQuery,
- variables: {
- id: WORK_ITEM_GID,
- },
- data: queryResponse,
- });
+ const findWorkItemDetail = () => wrapper.findComponent(WorkItemDetail);
+ const createComponent = () => {
wrapper = shallowMount(WorkItemsRoot, {
propsData: {
- id: WORK_ITEM_ID,
+ id: '1',
},
- apolloProvider: fakeApollo,
});
};
afterEach(() => {
wrapper.destroy();
- fakeApollo = null;
});
- it('renders the title', () => {
+ it('renders WorkItemDetail', () => {
createComponent();
- expect(findTitle().exists()).toBe(true);
- expect(findTitle().props('initialTitle')).toBe('Test');
- });
-
- it('updates the title when it is edited', async () => {
- createComponent();
- jest.spyOn(wrapper.vm.$apollo, 'mutate');
-
- await findTitle().vm.$emit('title-changed', mockUpdatedTitle);
-
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: updateWorkItemMutation,
- variables: {
- input: {
- id: WORK_ITEM_GID,
- title: mockUpdatedTitle,
- },
- },
- });
- });
-
- describe('tracking', () => {
- let trackingSpy;
-
- beforeEach(() => {
- trackingSpy = mockTracking('_category_', undefined, jest.spyOn);
-
- createComponent();
- });
-
- afterEach(() => {
- unmockTracking();
- });
-
- it('tracks item title updates', async () => {
- await findTitle().vm.$emit('title-changed', mockUpdatedTitle);
-
- await waitForPromises();
-
- expect(trackingSpy).toHaveBeenCalledTimes(1);
- expect(trackingSpy).toHaveBeenCalledWith('workItems:show', undefined, {
- action: 'updated_title',
- category: 'workItems:show',
- label: 'item_title',
- property: '[type_work_item]',
- });
- });
+ expect(findWorkItemDetail().props()).toEqual({ workItemId: 'gid://gitlab/WorkItem/1' });
});
});
diff --git a/spec/frontend/work_items/router_spec.js b/spec/frontend/work_items/router_spec.js
index 8c9054920a8..7e68c5e4f0e 100644
--- a/spec/frontend/work_items/router_spec.js
+++ b/spec/frontend/work_items/router_spec.js
@@ -37,7 +37,7 @@ describe('Work items router', () => {
it('renders work item on `/1` route', async () => {
await createComponent('/1');
- expect(wrapper.find(WorkItemsRoot).exists()).toBe(true);
+ expect(wrapper.findComponent(WorkItemsRoot).exists()).toBe(true);
});
it('renders create work item page on `/new` route', async () => {