summaryrefslogtreecommitdiff
path: root/spec/frontend/issues/show/components/description_spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/issues/show/components/description_spec.js')
-rw-r--r--spec/frontend/issues/show/components/description_spec.js237
1 files changed, 171 insertions, 66 deletions
diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js
index 889ff450825..3f4513e6bfa 100644
--- a/spec/frontend/issues/show/components/description_spec.js
+++ b/spec/frontend/issues/show/components/description_spec.js
@@ -1,8 +1,8 @@
import $ from 'jquery';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-import { GlTooltip, GlModal } from '@gitlab/ui';
-
+import { GlModal } from '@gitlab/ui';
+import getIssueDetailsQuery from 'ee_else_ce/work_items/graphql/get_issue_details.query.graphql';
import setWindowLocation from 'helpers/set_window_location_helper';
import { stubComponent } from 'helpers/stub_component';
import { TEST_HOST } from 'helpers/test_constants';
@@ -10,23 +10,26 @@ import { mockTracking } from 'helpers/tracking_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-
import { createAlert } from '~/flash';
import Description from '~/issues/show/components/description.vue';
+import eventHub from '~/issues/show/event_hub';
import { updateHistory } from '~/lib/utils/url_utility';
import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
+import createWorkItemMutation from '~/work_items/graphql/create_work_item.mutation.graphql';
import workItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
-import createWorkItemFromTaskMutation from '~/work_items/graphql/create_work_item_from_task.mutation.graphql';
import TaskList from '~/task_list';
import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue';
import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
import { renderGFM } from '~/behaviors/markdown/render_gfm';
import {
+ createWorkItemMutationErrorResponse,
+ createWorkItemMutationResponse,
+ getIssueDetailsResponse,
projectWorkItemTypesQueryResponse,
- createWorkItemFromTaskMutationResponse,
} from 'jest/work_items/mock_data';
import {
descriptionProps as initialProps,
+ descriptionHtmlWithList,
descriptionHtmlWithCheckboxes,
descriptionHtmlWithTask,
} from '../mock_data/mock_data';
@@ -39,6 +42,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
jest.mock('~/task_list');
jest.mock('~/behaviors/markdown/render_gfm');
+const mockSpriteIcons = '/icons.svg';
const showModal = jest.fn();
const hideModal = jest.fn();
const showDetailsModal = jest.fn();
@@ -46,6 +50,7 @@ const $toast = {
show: jest.fn(),
};
+const issueDetailsResponse = getIssueDetailsResponse();
const workItemQueryResponse = {
data: {
workItem: null,
@@ -54,44 +59,45 @@ const workItemQueryResponse = {
const queryHandler = jest.fn().mockResolvedValue(workItemQueryResponse);
const workItemTypesQueryHandler = jest.fn().mockResolvedValue(projectWorkItemTypesQueryResponse);
-const createWorkItemFromTaskSuccessHandler = jest
- .fn()
- .mockResolvedValue(createWorkItemFromTaskMutationResponse);
describe('Description component', () => {
let wrapper;
+ let originalGon;
Vue.use(VueApollo);
const findGfmContent = () => wrapper.find('[data-testid="gfm-content"]');
const findTextarea = () => wrapper.find('[data-testid="textarea"]');
- const findTaskActionButtons = () => wrapper.findAll('.js-add-task');
- const findConvertToTaskButton = () => wrapper.find('.js-add-task');
+ const findListItems = () => findGfmContent().findAll('ul > li');
+ const findTaskActionButtons = () => wrapper.findAll('.task-list-item-actions');
const findTaskLink = () => wrapper.find('a.gfm-issue');
-
- const findTooltips = () => wrapper.findAllComponents(GlTooltip);
const findModal = () => wrapper.findComponent(GlModal);
const findWorkItemDetailModal = () => wrapper.findComponent(WorkItemDetailModal);
function createComponent({
props = {},
provide,
- createWorkItemFromTaskHandler = createWorkItemFromTaskSuccessHandler,
+ issueDetailsQueryHandler = jest.fn().mockResolvedValue(issueDetailsResponse),
+ createWorkItemMutationHandler,
+ ...options
} = {}) {
wrapper = shallowMountExtended(Description, {
propsData: {
issueId: 1,
+ issueIid: 1,
...initialProps,
...props,
},
provide: {
fullPath: 'gitlab-org/gitlab-test',
+ hasIterationsFeature: true,
...provide,
},
apolloProvider: createMockApollo([
[workItemQuery, queryHandler],
[workItemTypesQuery, workItemTypesQueryHandler],
- [createWorkItemFromTaskMutation, createWorkItemFromTaskHandler],
+ [getIssueDetailsQuery, issueDetailsQueryHandler],
+ [createWorkItemMutation, createWorkItemMutationHandler],
]),
mocks: {
$toast,
@@ -109,10 +115,14 @@ describe('Description component', () => {
},
}),
},
+ ...options,
});
}
beforeEach(() => {
+ originalGon = window.gon;
+ window.gon = { sprite_icons: mockSpriteIcons };
+
setWindowLocation(TEST_HOST);
if (!document.querySelector('.issuable-meta')) {
@@ -125,11 +135,9 @@ describe('Description component', () => {
}
});
- afterEach(() => {
- wrapper.destroy();
- });
-
afterAll(() => {
+ window.gon = originalGon;
+
$('.issuable-meta .flash-container').remove();
});
@@ -271,7 +279,38 @@ describe('Description component', () => {
});
});
- describe('with work_items_create_from_markdown feature flag enabled', () => {
+ describe('with list', () => {
+ beforeEach(async () => {
+ createComponent({
+ props: {
+ descriptionHtml: descriptionHtmlWithList,
+ },
+ attachTo: document.body,
+ });
+ await nextTick();
+ });
+
+ it('shows list items', () => {
+ expect(findListItems()).toHaveLength(3);
+ });
+
+ it('shows list items drag icons', () => {
+ const dragIcon = findListItems().at(0).find('.drag-icon');
+
+ expect(dragIcon.classes()).toEqual(
+ expect.arrayContaining(['s14', 'gl-icon', 'gl-cursor-grab', 'gl-opacity-0']),
+ );
+ expect(dragIcon.attributes()).toMatchObject({
+ 'aria-hidden': 'true',
+ role: 'img',
+ });
+ expect(dragIcon.find('use').attributes()).toEqual({
+ href: `${mockSpriteIcons}#grip`,
+ });
+ });
+ });
+
+ describe('with work_items_mvc feature flag enabled', () => {
describe('empty description', () => {
beforeEach(() => {
createComponent({
@@ -280,7 +319,7 @@ describe('Description component', () => {
},
provide: {
glFeatures: {
- workItemsCreateFromMarkdown: true,
+ workItemsMvc: true,
},
},
});
@@ -300,7 +339,7 @@ describe('Description component', () => {
},
provide: {
glFeatures: {
- workItemsCreateFromMarkdown: true,
+ workItemsMvc: true,
},
},
});
@@ -311,13 +350,6 @@ describe('Description component', () => {
expect(findTaskActionButtons()).toHaveLength(3);
});
- it('renders a list of tooltips corresponding to checkboxes in description HTML', () => {
- expect(findTooltips()).toHaveLength(3);
- expect(findTooltips().at(0).props('target')).toBe(
- findTaskActionButtons().at(0).attributes('id'),
- );
- });
-
it('does not show a modal by default', () => {
expect(findModal().exists()).toBe(false);
});
@@ -331,50 +363,123 @@ describe('Description component', () => {
});
});
- describe('creating work item from checklist item', () => {
- it('emits `updateDescription` after creating new work item', async () => {
- createComponent({
- props: {
- descriptionHtml: descriptionHtmlWithCheckboxes,
- },
- provide: {
- glFeatures: {
- workItemsCreateFromMarkdown: true,
- },
- },
- });
+ describe('task list item actions', () => {
+ describe('converting the task list item to a task', () => {
+ describe('when successful', () => {
+ let createWorkItemMutationHandler;
- const newDescription = `<p>New description</p>`;
+ beforeEach(async () => {
+ createWorkItemMutationHandler = jest
+ .fn()
+ .mockResolvedValue(createWorkItemMutationResponse);
+ const descriptionText = `Tasks
- await findConvertToTaskButton().trigger('click');
+1. [ ] item 1
+ 1. [ ] item 2
- await waitForPromises();
+ paragraph text
- expect(wrapper.emitted('updateDescription')).toEqual([[newDescription]]);
- });
+ 1. [ ] item 3
+ 1. [ ] item 4;`;
+ createComponent({
+ props: { descriptionText },
+ provide: { glFeatures: { workItemsMvc: true } },
+ createWorkItemMutationHandler,
+ });
+ await waitForPromises();
- it('shows flash message when creating task fails', async () => {
- createComponent({
- props: {
- descriptionHtml: descriptionHtmlWithCheckboxes,
- },
- provide: {
- glFeatures: {
- workItemsCreateFromMarkdown: true,
- },
- },
- createWorkItemFromTaskHandler: jest.fn().mockRejectedValue({}),
+ eventHub.$emit('convert-task-list-item', '4:4-8:19');
+ await waitForPromises();
+ });
+
+ it('emits an event to update the description with the deleted task list item omitted', () => {
+ const newDescriptionText = `Tasks
+
+1. [ ] item 1
+ 1. [ ] item 3
+ 1. [ ] item 4;`;
+
+ expect(wrapper.emitted('saveDescription')).toEqual([[newDescriptionText]]);
+ });
+
+ it('calls a mutation to create a task', () => {
+ const {
+ confidential,
+ iteration,
+ milestone,
+ } = issueDetailsResponse.data.workspace.issuable;
+ expect(createWorkItemMutationHandler).toHaveBeenCalledWith({
+ input: {
+ confidential,
+ description: '\nparagraph text\n',
+ hierarchyWidget: {
+ parentId: 'gid://gitlab/WorkItem/1',
+ },
+ iterationWidget: {
+ iterationId: IS_EE ? iteration.id : null,
+ },
+ milestoneWidget: {
+ milestoneId: milestone.id,
+ },
+ projectPath: 'gitlab-org/gitlab-test',
+ title: 'item 2',
+ workItemTypeId: 'gid://gitlab/WorkItems::Type/3',
+ },
+ });
+ });
+
+ it('shows a toast to confirm the creation of the task', () => {
+ expect($toast.show).toHaveBeenCalledWith('Converted to task', expect.any(Object));
+ });
});
- await findConvertToTaskButton().trigger('click');
+ describe('when unsuccessful', () => {
+ beforeEach(async () => {
+ createComponent({
+ props: { descriptionText: 'description' },
+ provide: { glFeatures: { workItemsMvc: true } },
+ createWorkItemMutationHandler: jest
+ .fn()
+ .mockResolvedValue(createWorkItemMutationErrorResponse),
+ });
+ await waitForPromises();
- await waitForPromises();
+ eventHub.$emit('convert-task-list-item', '1:1-1:11');
+ await waitForPromises();
+ });
- expect(createAlert).toHaveBeenCalledWith(
- expect.objectContaining({
- message: 'Something went wrong when creating task. Please try again.',
- }),
- );
+ it('shows an alert with an error message', () => {
+ expect(createAlert).toHaveBeenCalledWith({
+ message: 'Something went wrong when creating task. Please try again.',
+ error: new Error('an error'),
+ captureError: true,
+ });
+ });
+ });
+ });
+
+ describe('deleting the task list item', () => {
+ it('emits an event to update the description with the deleted task list item', () => {
+ const descriptionText = `Tasks
+
+1. [ ] item 1
+ 1. [ ] item 2
+ 1. [ ] item 3
+ 1. [ ] item 4;`;
+ const newDescriptionText = `Tasks
+
+1. [ ] item 1
+ 1. [ ] item 3
+ 1. [ ] item 4;`;
+ createComponent({
+ props: { descriptionText },
+ provide: { glFeatures: { workItemsMvc: true } },
+ });
+
+ eventHub.$emit('delete-task-list-item', '4:4-5:19');
+
+ expect(wrapper.emitted('saveDescription')).toEqual([[newDescriptionText]]);
+ });
});
});
@@ -386,7 +491,7 @@ describe('Description component', () => {
descriptionHtml: descriptionHtmlWithTask,
},
provide: {
- glFeatures: { workItemsCreateFromMarkdown: true },
+ glFeatures: { workItemsMvc: true },
},
});
return nextTick();
@@ -448,7 +553,7 @@ describe('Description component', () => {
createComponent({
props: { descriptionHtml: descriptionHtmlWithTask },
- provide: { glFeatures: { workItemsCreateFromMarkdown: true } },
+ provide: { glFeatures: { workItemsMvc: true } },
});
expect(showDetailsModal).toHaveBeenCalledTimes(modalOpened);
@@ -464,7 +569,7 @@ describe('Description component', () => {
descriptionHtml: descriptionHtmlWithTask,
},
provide: {
- glFeatures: { workItemsCreateFromMarkdown: true },
+ glFeatures: { workItemsMvc: true },
},
});
return nextTick();