diff options
Diffstat (limited to 'spec/frontend/issues/show/components/description_spec.js')
-rw-r--r-- | spec/frontend/issues/show/components/description_spec.js | 237 |
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(); |