diff options
Diffstat (limited to 'spec/frontend/issues')
12 files changed, 257 insertions, 241 deletions
diff --git a/spec/frontend/issues/create_merge_request_dropdown_spec.js b/spec/frontend/issues/create_merge_request_dropdown_spec.js index c2cfb16fdf7..20b26f5abba 100644 --- a/spec/frontend/issues/create_merge_request_dropdown_spec.js +++ b/spec/frontend/issues/create_merge_request_dropdown_spec.js @@ -15,7 +15,7 @@ describe('CreateMergeRequestDropdown', () => { <div id="dummy-wrapper-element"> <div class="available"></div> <div class="unavailable"> - <div class="gl-spinner"></div> + <div class="js-create-mr-spinner"></div> <div class="text"></div> </div> <div class="js-ref"></div> @@ -38,21 +38,16 @@ describe('CreateMergeRequestDropdown', () => { }); describe('getRef', () => { - it('escapes branch names correctly', (done) => { + it('escapes branch names correctly', async () => { const endpoint = `${dropdown.refsPath}contains%23hash`; jest.spyOn(axios, 'get'); axiosMock.onGet(endpoint).replyOnce({}); - dropdown - .getRef('contains#hash') - .then(() => { - expect(axios.get).toHaveBeenCalledWith( - endpoint, - expect.objectContaining({ cancelToken: expect.anything() }), - ); - }) - .then(done) - .catch(done.fail); + await dropdown.getRef('contains#hash'); + expect(axios.get).toHaveBeenCalledWith( + endpoint, + expect.objectContaining({ cancelToken: expect.anything() }), + ); }); }); diff --git a/spec/frontend/issues/list/components/issue_card_time_info_spec.js b/spec/frontend/issues/list/components/issue_card_time_info_spec.js index e9c48b60da4..c3f13ca6f9a 100644 --- a/spec/frontend/issues/list/components/issue_card_time_info_spec.js +++ b/spec/frontend/issues/list/components/issue_card_time_info_spec.js @@ -1,10 +1,11 @@ import { GlIcon, GlLink } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { useFakeDate } from 'helpers/fake_date'; +import { IssuableStatus } from '~/issues/constants'; import IssueCardTimeInfo from '~/issues/list/components/issue_card_time_info.vue'; describe('CE IssueCardTimeInfo component', () => { - useFakeDate(2020, 11, 11); + useFakeDate(2020, 11, 11); // 2020 Dec 11 let wrapper; @@ -24,7 +25,7 @@ describe('CE IssueCardTimeInfo component', () => { const findDueDate = () => wrapper.find('[data-testid="issuable-due-date"]'); const mountComponent = ({ - closedAt = null, + state = IssuableStatus.Open, dueDate = issue.dueDate, milestoneDueDate = issue.milestone.dueDate, milestoneStartDate = issue.milestone.startDate, @@ -38,7 +39,7 @@ describe('CE IssueCardTimeInfo component', () => { dueDate: milestoneDueDate, startDate: milestoneStartDate, }, - closedAt, + state, dueDate, }, }, @@ -91,7 +92,7 @@ describe('CE IssueCardTimeInfo component', () => { describe('when in the past', () => { describe('when issue is open', () => { it('renders in red', () => { - wrapper = mountComponent({ dueDate: new Date('2020-10-10') }); + wrapper = mountComponent({ dueDate: '2020-10-10' }); expect(findDueDate().classes()).toContain('gl-text-red-500'); }); @@ -100,8 +101,8 @@ describe('CE IssueCardTimeInfo component', () => { describe('when issue is closed', () => { it('does not render in red', () => { wrapper = mountComponent({ - dueDate: new Date('2020-10-10'), - closedAt: '2020-09-05T13:06:25Z', + dueDate: '2020-10-10', + state: IssuableStatus.Closed, }); expect(findDueDate().classes()).not.toContain('gl-text-red-500'); diff --git a/spec/frontend/issues/list/components/issues_list_app_spec.js b/spec/frontend/issues/list/components/issues_list_app_spec.js index 33c7ccac180..5a9bd1ff8e4 100644 --- a/spec/frontend/issues/list/components/issues_list_app_spec.js +++ b/spec/frontend/issues/list/components/issues_list_app_spec.js @@ -452,13 +452,26 @@ describe('CE IssuesListApp component', () => { }); describe('IssuableByEmail component', () => { - describe.each([true, false])(`when issue creation by email is enabled=%s`, (enabled) => { - it(`${enabled ? 'renders' : 'does not render'}`, () => { - wrapper = mountComponent({ provide: { initialEmail: enabled } }); - - expect(findIssuableByEmail().exists()).toBe(enabled); - }); - }); + describe.each` + initialEmail | hasAnyIssues | isSignedIn | exists + ${false} | ${false} | ${false} | ${false} + ${false} | ${true} | ${false} | ${false} + ${false} | ${false} | ${true} | ${false} + ${false} | ${true} | ${true} | ${false} + ${true} | ${false} | ${false} | ${false} + ${true} | ${true} | ${false} | ${false} + ${true} | ${false} | ${true} | ${true} + ${true} | ${true} | ${true} | ${true} + `( + `when issue creation by email is enabled=$initialEmail`, + ({ initialEmail, hasAnyIssues, isSignedIn, exists }) => { + it(`${initialEmail ? 'renders' : 'does not render'}`, () => { + wrapper = mountComponent({ provide: { initialEmail, hasAnyIssues, isSignedIn } }); + + expect(findIssuableByEmail().exists()).toBe(exists); + }); + }, + ); }); describe('empty states', () => { diff --git a/spec/frontend/issues/list/mock_data.js b/spec/frontend/issues/list/mock_data.js index c883b20682e..b1a135ceb18 100644 --- a/spec/frontend/issues/list/mock_data.js +++ b/spec/frontend/issues/list/mock_data.js @@ -21,7 +21,6 @@ export const getIssuesQueryResponse = { __typename: 'Issue', id: 'gid://gitlab/Issue/123456', iid: '789', - closedAt: null, confidential: false, createdAt: '2021-05-22T04:08:01Z', downvotes: 2, @@ -30,6 +29,7 @@ export const getIssuesQueryResponse = { humanTimeEstimate: null, mergeRequestsCount: false, moved: false, + state: 'opened', title: 'Issue title', updatedAt: '2021-05-22T04:08:01Z', upvotes: 3, diff --git a/spec/frontend/issues/related_merge_requests/store/actions_spec.js b/spec/frontend/issues/related_merge_requests/store/actions_spec.js index 5f232fee09b..4327fac15d4 100644 --- a/spec/frontend/issues/related_merge_requests/store/actions_spec.js +++ b/spec/frontend/issues/related_merge_requests/store/actions_spec.js @@ -23,90 +23,82 @@ describe('RelatedMergeRequest store actions', () => { }); describe('setInitialState', () => { - it('commits types.SET_INITIAL_STATE with given props', (done) => { + it('commits types.SET_INITIAL_STATE with given props', () => { const props = { a: 1, b: 2 }; - testAction( + return testAction( actions.setInitialState, props, {}, [{ type: types.SET_INITIAL_STATE, payload: props }], [], - done, ); }); }); describe('requestData', () => { - it('commits types.REQUEST_DATA', (done) => { - testAction(actions.requestData, null, {}, [{ type: types.REQUEST_DATA }], [], done); + it('commits types.REQUEST_DATA', () => { + return testAction(actions.requestData, null, {}, [{ type: types.REQUEST_DATA }], []); }); }); describe('receiveDataSuccess', () => { - it('commits types.RECEIVE_DATA_SUCCESS with data', (done) => { + it('commits types.RECEIVE_DATA_SUCCESS with data', () => { const data = { a: 1, b: 2 }; - testAction( + return testAction( actions.receiveDataSuccess, data, {}, [{ type: types.RECEIVE_DATA_SUCCESS, payload: data }], [], - done, ); }); }); describe('receiveDataError', () => { - it('commits types.RECEIVE_DATA_ERROR', (done) => { - testAction( + it('commits types.RECEIVE_DATA_ERROR', () => { + return testAction( actions.receiveDataError, null, {}, [{ type: types.RECEIVE_DATA_ERROR }], [], - done, ); }); }); describe('fetchMergeRequests', () => { describe('for a successful request', () => { - it('should dispatch success action', (done) => { + it('should dispatch success action', () => { const data = { a: 1 }; mock.onGet(`${state.apiEndpoint}?per_page=100`).replyOnce(200, data, { 'x-total': 2 }); - testAction( + return testAction( actions.fetchMergeRequests, null, state, [], [{ type: 'requestData' }, { type: 'receiveDataSuccess', payload: { data, total: 2 } }], - done, ); }); }); describe('for a failing request', () => { - it('should dispatch error action', (done) => { + it('should dispatch error action', async () => { mock.onGet(`${state.apiEndpoint}?per_page=100`).replyOnce(400); - testAction( + await testAction( actions.fetchMergeRequests, null, state, [], [{ type: 'requestData' }, { type: 'receiveDataError' }], - () => { - expect(createFlash).toHaveBeenCalledTimes(1); - expect(createFlash).toHaveBeenCalledWith({ - message: expect.stringMatching('Something went wrong'), - }); - - done(); - }, ); + expect(createFlash).toHaveBeenCalledTimes(1); + expect(createFlash).toHaveBeenCalledWith({ + message: expect.stringMatching('Something went wrong'), + }); }); }); }); diff --git a/spec/frontend/issues/show/components/app_spec.js b/spec/frontend/issues/show/components/app_spec.js index ac2717a5028..5ab64d8e9ca 100644 --- a/spec/frontend/issues/show/components/app_spec.js +++ b/spec/frontend/issues/show/components/app_spec.js @@ -2,11 +2,14 @@ import { GlIntersectionObserver } from '@gitlab/ui'; import MockAdapter from 'axios-mock-adapter'; import { nextTick } from 'vue'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; -import { mountExtended } from 'helpers/vue_test_utils_helper'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import '~/behaviors/markdown/render_gfm'; import { IssuableStatus, IssuableStatusText } from '~/issues/constants'; import IssuableApp from '~/issues/show/components/app.vue'; import DescriptionComponent from '~/issues/show/components/description.vue'; +import EditedComponent from '~/issues/show/components/edited.vue'; +import FormComponent from '~/issues/show/components/form.vue'; +import TitleComponent from '~/issues/show/components/title.vue'; import IncidentTabs from '~/issues/show/components/incidents/incident_tabs.vue'; import PinnedLinks from '~/issues/show/components/pinned_links.vue'; import { POLLING_DELAY } from '~/issues/show/constants'; @@ -21,10 +24,6 @@ import { zoomMeetingUrl, } from '../mock_data/mock_data'; -function formatText(text) { - return text.trim().replace(/\s\s+/g, ' '); -} - jest.mock('~/lib/utils/url_utility'); jest.mock('~/issues/show/event_hub'); @@ -39,10 +38,15 @@ describe('Issuable output', () => { const findLockedBadge = () => wrapper.findByTestId('locked'); const findConfidentialBadge = () => wrapper.findByTestId('confidential'); const findHiddenBadge = () => wrapper.findByTestId('hidden'); - const findAlert = () => wrapper.find('.alert'); + + const findTitle = () => wrapper.findComponent(TitleComponent); + const findDescription = () => wrapper.findComponent(DescriptionComponent); + const findEdited = () => wrapper.findComponent(EditedComponent); + const findForm = () => wrapper.findComponent(FormComponent); + const findPinnedLinks = () => wrapper.findComponent(PinnedLinks); const mountComponent = (props = {}, options = {}, data = {}) => { - wrapper = mountExtended(IssuableApp, { + wrapper = shallowMountExtended(IssuableApp, { directives: { GlTooltip: createMockDirective(), }, @@ -104,23 +108,15 @@ describe('Issuable output', () => { }); it('should render a title/description/edited and update title/description/edited on update', () => { - let editedText; return axios .waitForAll() .then(() => { - editedText = wrapper.find('.edited-text'); - }) - .then(() => { - expect(document.querySelector('title').innerText).toContain('this is a title (#1)'); - expect(wrapper.find('.title').text()).toContain('this is a title'); - expect(wrapper.find('.md').text()).toContain('this is a description!'); - expect(wrapper.find('.js-task-list-field').element.value).toContain( - 'this is a description', - ); + expect(findTitle().props('titleText')).toContain('this is a title'); + expect(findDescription().props('descriptionText')).toContain('this is a description'); - expect(formatText(editedText.text())).toMatch(/Edited[\s\S]+?by Some User/); - expect(editedText.find('.author-link').attributes('href')).toMatch(/\/some_user$/); - expect(editedText.find('time').text()).toBeTruthy(); + expect(findEdited().exists()).toBe(true); + expect(findEdited().props('updatedByPath')).toMatch(/\/some_user$/); + expect(findEdited().props('updatedAt')).toBeTruthy(); expect(wrapper.vm.state.lock_version).toBe(initialRequest.lock_version); }) .then(() => { @@ -128,20 +124,13 @@ describe('Issuable output', () => { return axios.waitForAll(); }) .then(() => { - expect(document.querySelector('title').innerText).toContain('2 (#1)'); - expect(wrapper.find('.title').text()).toContain('2'); - expect(wrapper.find('.md').text()).toContain('42'); - expect(wrapper.find('.js-task-list-field').element.value).toContain('42'); - expect(wrapper.find('.edited-text').text()).toBeTruthy(); - expect(formatText(wrapper.find('.edited-text').text())).toMatch( - /Edited[\s\S]+?by Other User/, - ); + expect(findTitle().props('titleText')).toContain('2'); + expect(findDescription().props('descriptionText')).toContain('42'); - expect(editedText.find('.author-link').attributes('href')).toMatch(/\/other_user$/); - expect(editedText.find('time').text()).toBeTruthy(); - // As the lock_version value does not differ from the server, - // we should not see an alert - expect(findAlert().exists()).toBe(false); + expect(findEdited().exists()).toBe(true); + expect(findEdited().props('updatedByName')).toBe('Other User'); + expect(findEdited().props('updatedByPath')).toMatch(/\/other_user$/); + expect(findEdited().props('updatedAt')).toBeTruthy(); }); }); @@ -149,7 +138,7 @@ describe('Issuable output', () => { wrapper.vm.showForm = true; await nextTick(); - expect(wrapper.find('.markdown-selector').exists()).toBe(true); + expect(findForm().exists()).toBe(true); }); it('does not show actions if permissions are incorrect', async () => { @@ -157,7 +146,7 @@ describe('Issuable output', () => { wrapper.setProps({ canUpdate: false }); await nextTick(); - expect(wrapper.find('.markdown-selector').exists()).toBe(false); + expect(findForm().exists()).toBe(false); }); it('does not update formState if form is already open', async () => { @@ -177,8 +166,7 @@ describe('Issuable output', () => { ${'zoomMeetingUrl'} | ${zoomMeetingUrl} ${'publishedIncidentUrl'} | ${publishedIncidentUrl} `('sets the $prop correctly on underlying pinned links', ({ prop, value }) => { - expect(wrapper.vm[prop]).toBe(value); - expect(wrapper.find(`[data-testid="${prop}"]`).attributes('href')).toBe(value); + expect(findPinnedLinks().props(prop)).toBe(value); }); }); @@ -327,7 +315,6 @@ describe('Issuable output', () => { expect(wrapper.vm.formState.lockedWarningVisible).toBe(true); expect(wrapper.vm.formState.lock_version).toBe(1); - expect(findAlert().exists()).toBe(true); }); }); @@ -374,15 +361,22 @@ describe('Issuable output', () => { }); describe('show inline edit button', () => { - it('should not render by default', () => { - expect(wrapper.find('.btn-edit').exists()).toBe(true); + it('should render by default', () => { + expect(findTitle().props('showInlineEditButton')).toBe(true); }); it('should render if showInlineEditButton', async () => { wrapper.setProps({ showInlineEditButton: true }); await nextTick(); - expect(wrapper.find('.btn-edit').exists()).toBe(true); + expect(findTitle().props('showInlineEditButton')).toBe(true); + }); + + it('should not render if showInlineEditButton is false', async () => { + wrapper.setProps({ showInlineEditButton: false }); + + await nextTick(); + expect(findTitle().props('showInlineEditButton')).toBe(false); }); }); @@ -533,13 +527,11 @@ describe('Issuable output', () => { describe('Composable description component', () => { const findIncidentTabs = () => wrapper.findComponent(IncidentTabs); - const findDescriptionComponent = () => wrapper.findComponent(DescriptionComponent); - const findPinnedLinks = () => wrapper.findComponent(PinnedLinks); const borderClass = 'gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid gl-mb-6'; describe('when using description component', () => { it('renders the description component', () => { - expect(findDescriptionComponent().exists()).toBe(true); + expect(findDescription().exists()).toBe(true); }); it('does not render incident tabs', () => { @@ -572,8 +564,8 @@ describe('Issuable output', () => { ); }); - it('renders the description component', () => { - expect(findDescriptionComponent().exists()).toBe(true); + it('does not the description component', () => { + expect(findDescription().exists()).toBe(false); }); it('renders incident tabs', () => { diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js index 08f8996de6f..0b3daadae1d 100644 --- a/spec/frontend/issues/show/components/description_spec.js +++ b/spec/frontend/issues/show/components/description_spec.js @@ -1,26 +1,35 @@ import $ from 'jquery'; import { nextTick } from 'vue'; import '~/behaviors/markdown/render_gfm'; -import { GlPopover, GlModal } from '@gitlab/ui'; +import { GlTooltip, GlModal } from '@gitlab/ui'; +import setWindowLocation from 'helpers/set_window_location_helper'; import { stubComponent } from 'helpers/stub_component'; import { TEST_HOST } from 'helpers/test_constants'; import { mockTracking } from 'helpers/tracking_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import createFlash from '~/flash'; import Description from '~/issues/show/components/description.vue'; +import { updateHistory } from '~/lib/utils/url_utility'; import TaskList from '~/task_list'; import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue'; import CreateWorkItem from '~/work_items/pages/create_work_item.vue'; import { descriptionProps as initialProps, descriptionHtmlWithCheckboxes, + descriptionHtmlWithTask, } from '../mock_data/mock_data'; jest.mock('~/flash'); +jest.mock('~/lib/utils/url_utility', () => ({ + ...jest.requireActual('~/lib/utils/url_utility'), + updateHistory: jest.fn(), +})); jest.mock('~/task_list'); const showModal = jest.fn(); const hideModal = jest.fn(); +const $toast = { + show: jest.fn(), +}; describe('Description component', () => { let wrapper; @@ -28,10 +37,9 @@ describe('Description component', () => { 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('[data-testid="convert-to-task"]'); - const findTaskSvg = () => wrapper.find('[data-testid="issue-open-m-icon"]'); + const findConvertToTaskButton = () => wrapper.find('.js-add-task'); - const findPopovers = () => wrapper.findAllComponents(GlPopover); + const findTooltips = () => wrapper.findAllComponents(GlTooltip); const findModal = () => wrapper.findComponent(GlModal); const findCreateWorkItem = () => wrapper.findComponent(CreateWorkItem); const findWorkItemDetailModal = () => wrapper.findComponent(WorkItemDetailModal); @@ -39,10 +47,14 @@ describe('Description component', () => { function createComponent({ props = {}, provide = {} } = {}) { wrapper = shallowMountExtended(Description, { propsData: { + issueId: 1, ...initialProps, ...props, }, provide, + mocks: { + $toast, + }, stubs: { GlModal: stubComponent(GlModal, { methods: { @@ -50,12 +62,13 @@ describe('Description component', () => { hide: hideModal, }, }), - GlPopover, }, }); } beforeEach(() => { + setWindowLocation(TEST_HOST); + if (!document.querySelector('.issuable-meta')) { const metaData = document.createElement('div'); metaData.classList.add('issuable-meta'); @@ -253,9 +266,9 @@ describe('Description component', () => { expect(findTaskActionButtons()).toHaveLength(3); }); - it('renders a list of popovers corresponding to checkboxes in description HTML', () => { - expect(findPopovers()).toHaveLength(3); - expect(findPopovers().at(0).props('target')).toBe( + 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'), ); }); @@ -264,92 +277,113 @@ describe('Description component', () => { expect(findModal().props('visible')).toBe(false); }); - it('opens a modal when a button on popover is clicked and displays correct title', async () => { - findConvertToTaskButton().vm.$emit('click'); - expect(showModal).toHaveBeenCalled(); - await nextTick(); + it('opens a modal when a button is clicked and displays correct title', async () => { + await findConvertToTaskButton().trigger('click'); expect(findCreateWorkItem().props('initialTitle').trim()).toBe('todo 1'); }); - it('closes the modal on `closeCreateTaskModal` event', () => { - findConvertToTaskButton().vm.$emit('click'); + it('closes the modal on `closeCreateTaskModal` event', async () => { + await findConvertToTaskButton().trigger('click'); findCreateWorkItem().vm.$emit('closeModal'); expect(hideModal).toHaveBeenCalled(); }); - it('updates description HTML on `onCreate` event', async () => { - const newTitle = 'New title'; - findConvertToTaskButton().vm.$emit('click'); - findCreateWorkItem().vm.$emit('onCreate', { title: newTitle }); + it('emits `updateDescription` on `onCreate` event', () => { + const newDescription = `<p>New description</p>`; + findCreateWorkItem().vm.$emit('onCreate', newDescription); expect(hideModal).toHaveBeenCalled(); - await nextTick(); + expect(wrapper.emitted('updateDescription')).toEqual([[newDescription]]); + }); + + it('shows toast after delete success', async () => { + findWorkItemDetailModal().vm.$emit('workItemDeleted'); - expect(findTaskSvg().exists()).toBe(true); - expect(wrapper.text()).toContain(newTitle); + expect($toast.show).toHaveBeenCalledWith('Work item deleted'); }); }); describe('work items detail', () => { - const id = '1'; - const title = 'my first task'; - const type = 'task'; + const findTaskLink = () => wrapper.find('a.gfm-issue'); - const createThenClickOnTask = () => { - findConvertToTaskButton().vm.$emit('click'); - findCreateWorkItem().vm.$emit('onCreate', { id, title, type }); - return wrapper.findByRole('button', { name: title }).trigger('click'); - }; - - beforeEach(() => { - createComponent({ - props: { - descriptionHtml: descriptionHtmlWithCheckboxes, - }, - provide: { - glFeatures: { workItems: true }, - }, + describe('when opening and closing', () => { + beforeEach(() => { + createComponent({ + props: { + descriptionHtml: descriptionHtmlWithTask, + }, + provide: { + glFeatures: { workItems: true }, + }, + }); + return nextTick(); }); - return nextTick(); - }); - it('opens when task button is clicked', async () => { - expect(findWorkItemDetailModal().props('visible')).toBe(false); + it('opens when task button is clicked', async () => { + expect(findWorkItemDetailModal().props('visible')).toBe(false); - await createThenClickOnTask(); + await findTaskLink().trigger('click'); - expect(findWorkItemDetailModal().props('visible')).toBe(true); - }); + expect(findWorkItemDetailModal().props('visible')).toBe(true); + expect(updateHistory).toHaveBeenCalledWith({ + url: `${TEST_HOST}/?work_item_id=2`, + replace: true, + }); + }); - it('closes from an open state', async () => { - await createThenClickOnTask(); + it('closes from an open state', async () => { + await findTaskLink().trigger('click'); - expect(findWorkItemDetailModal().props('visible')).toBe(true); + expect(findWorkItemDetailModal().props('visible')).toBe(true); - findWorkItemDetailModal().vm.$emit('close'); - await nextTick(); + findWorkItemDetailModal().vm.$emit('close'); + await nextTick(); - expect(findWorkItemDetailModal().props('visible')).toBe(false); - }); + expect(findWorkItemDetailModal().props('visible')).toBe(false); + expect(updateHistory).toHaveBeenLastCalledWith({ + url: `${TEST_HOST}/`, + replace: true, + }); + }); - it('shows error on error', async () => { - const message = 'I am error'; + it('tracks when opened', async () => { + const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - await createThenClickOnTask(); - findWorkItemDetailModal().vm.$emit('error', message); + await findTaskLink().trigger('click'); - expect(createFlash).toHaveBeenCalledWith({ message }); + expect(trackingSpy).toHaveBeenCalledWith( + 'workItems:show', + 'viewed_work_item_from_modal', + { + category: 'workItems:show', + label: 'work_item_view', + property: 'type_task', + }, + ); + }); }); - it('tracks when opened', async () => { - const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - - await createThenClickOnTask(); - - expect(trackingSpy).toHaveBeenCalledWith('workItems:show', 'viewed_work_item_from_modal', { - category: 'workItems:show', - label: 'work_item_view', - property: 'type_task', - }); + describe('when url query `work_item_id` exists', () => { + it.each` + behavior | workItemId | visible + ${'opens'} | ${'123'} | ${true} + ${'does not open'} | ${'123e'} | ${false} + ${'does not open'} | ${'12e3'} | ${false} + ${'does not open'} | ${'1e23'} | ${false} + ${'does not open'} | ${'x'} | ${false} + ${'does not open'} | ${'undefined'} | ${false} + `( + '$behavior when url contains `work_item_id=$workItemId`', + async ({ workItemId, visible }) => { + setWindowLocation(`?work_item_id=${workItemId}`); + + createComponent({ + props: { descriptionHtml: descriptionHtmlWithTask }, + provide: { glFeatures: { workItems: true } }, + }); + + expect(findWorkItemDetailModal().props('visible')).toBe(visible); + }, + ); }); }); }); diff --git a/spec/frontend/issues/show/components/fields/description_spec.js b/spec/frontend/issues/show/components/fields/description_spec.js index dd511c3945c..0dcd70ac19b 100644 --- a/spec/frontend/issues/show/components/fields/description_spec.js +++ b/spec/frontend/issues/show/components/fields/description_spec.js @@ -14,9 +14,8 @@ describe('Description field component', () => { propsData: { markdownPreviewPath: '/', markdownDocsPath: '/', - formState: { - description, - }, + quickActionsDocsPath: '/', + value: description, }, stubs: { MarkdownField, diff --git a/spec/frontend/issues/show/components/fields/description_template_spec.js b/spec/frontend/issues/show/components/fields/description_template_spec.js index abe2805e5b2..79a3bfa9840 100644 --- a/spec/frontend/issues/show/components/fields/description_template_spec.js +++ b/spec/frontend/issues/show/components/fields/description_template_spec.js @@ -1,74 +1,65 @@ -import Vue from 'vue'; +import { shallowMount } from '@vue/test-utils'; import descriptionTemplate from '~/issues/show/components/fields/description_template.vue'; describe('Issue description template component with templates as hash', () => { - let vm; - let formState; + let wrapper; + const defaultOptions = { + propsData: { + value: 'test', + issuableTemplates: { + test: [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }], + }, + projectId: 1, + projectPath: '/', + namespacePath: '/', + projectNamespace: '/', + }, + }; - beforeEach(() => { - const Component = Vue.extend(descriptionTemplate); - formState = { - description: 'test', - }; + const findIssuableSelector = () => wrapper.find('.js-issuable-selector'); - vm = new Component({ - propsData: { - formState, - issuableTemplates: { - test: [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }], - }, - projectId: 1, - projectPath: '/', - namespacePath: '/', - projectNamespace: '/', - }, - }).$mount(); + const createComponent = (options = defaultOptions) => { + wrapper = shallowMount(descriptionTemplate, options); + }; + + afterEach(() => { + wrapper.destroy(); }); it('renders templates as JSON hash in data attribute', () => { - expect(vm.$el.querySelector('.js-issuable-selector').getAttribute('data-data')).toBe( + createComponent(); + expect(findIssuableSelector().attributes('data-data')).toBe( '{"test":[{"name":"test","id":"test","project_path":"/","namespace_path":"/"}]}', ); }); - it('updates formState when changing template', () => { - vm.issuableTemplate.editor.setValue('test new template'); + it('emits input event', () => { + createComponent(); + wrapper.vm.issuableTemplate.editor.setValue('test new template'); - expect(formState.description).toBe('test new template'); + expect(wrapper.emitted('input')).toEqual([['test new template']]); }); - it('returns formState description with editor getValue', () => { - formState.description = 'testing new template'; - - expect(vm.issuableTemplate.editor.getValue()).toBe('testing new template'); + it('returns value with editor getValue', () => { + createComponent(); + expect(wrapper.vm.issuableTemplate.editor.getValue()).toBe('test'); }); -}); - -describe('Issue description template component with templates as array', () => { - let vm; - let formState; - beforeEach(() => { - const Component = Vue.extend(descriptionTemplate); - formState = { - description: 'test', - }; - - vm = new Component({ - propsData: { - formState, - issuableTemplates: [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }], - projectId: 1, - projectPath: '/', - namespacePath: '/', - projectNamespace: '/', - }, - }).$mount(); - }); - - it('renders templates as JSON array in data attribute', () => { - expect(vm.$el.querySelector('.js-issuable-selector').getAttribute('data-data')).toBe( - '[{"name":"test","id":"test","project_path":"/","namespace_path":"/"}]', - ); + describe('Issue description template component with templates as array', () => { + it('renders templates as JSON array in data attribute', () => { + createComponent({ + propsData: { + value: 'test', + issuableTemplates: [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }], + projectId: 1, + projectPath: '/', + namespacePath: '/', + projectNamespace: '/', + }, + }); + expect(findIssuableSelector().attributes('data-data')).toBe( + '[{"name":"test","id":"test","project_path":"/","namespace_path":"/"}]', + ); + }); }); }); diff --git a/spec/frontend/issues/show/components/fields/title_spec.js b/spec/frontend/issues/show/components/fields/title_spec.js index efd0b6fbd30..de04405d89b 100644 --- a/spec/frontend/issues/show/components/fields/title_spec.js +++ b/spec/frontend/issues/show/components/fields/title_spec.js @@ -12,9 +12,7 @@ describe('Title field component', () => { wrapper = shallowMount(TitleField, { propsData: { - formState: { - title: 'test', - }, + value: 'test', }, }); }); diff --git a/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js b/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js index 20c6cda33d4..35acca60de7 100644 --- a/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js +++ b/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js @@ -34,8 +34,9 @@ describe('Incident Tabs component', () => { provide: { fullPath: '', iid: '', + projectId: '', uploadMetricsFeatureAvailable: true, - glFeatures: { incidentTimelineEventTab: true, incidentTimelineEvents: true }, + glFeatures: { incidentTimeline: true, incidentTimelineEvents: true }, }, data() { return { alert: mockAlert, ...data }; @@ -57,7 +58,6 @@ describe('Incident Tabs component', () => { const findTabs = () => wrapper.findAll(GlTab); const findSummaryTab = () => findTabs().at(0); - const findMetricsTab = () => wrapper.find('[data-testid="metrics-tab"]'); const findAlertDetailsTab = () => wrapper.find('[data-testid="alert-details-tab"]'); const findAlertDetailsComponent = () => wrapper.find(AlertDetailsTable); const findDescriptionComponent = () => wrapper.find(DescriptionComponent); @@ -111,20 +111,6 @@ describe('Incident Tabs component', () => { }); }); - describe('upload metrics feature available', () => { - it('shows the metric tab when metrics are available', () => { - mountComponent({}, { provide: { uploadMetricsFeatureAvailable: true } }); - - expect(findMetricsTab().exists()).toBe(true); - }); - - it('hides the tab when metrics are not available', () => { - mountComponent({}, { provide: { uploadMetricsFeatureAvailable: false } }); - - expect(findMetricsTab().exists()).toBe(false); - }); - }); - describe('Snowplow tracking', () => { beforeEach(() => { jest.spyOn(Tracking, 'event'); diff --git a/spec/frontend/issues/show/mock_data/mock_data.js b/spec/frontend/issues/show/mock_data/mock_data.js index 89653ff82b2..7b0b8ca686a 100644 --- a/spec/frontend/issues/show/mock_data/mock_data.js +++ b/spec/frontend/issues/show/mock_data/mock_data.js @@ -72,3 +72,18 @@ export const descriptionHtmlWithCheckboxes = ` </li> </ul> `; + +export const descriptionHtmlWithTask = ` + <ul data-sourcepos="1:1-3:7" class="task-list" dir="auto"> + <li data-sourcepos="1:1-1:10" class="task-list-item"> + <input type="checkbox" class="task-list-item-checkbox" disabled> + <a href="/gitlab-org/gitlab-test/-/issues/48" data-original="#48+" data-link="false" data-link-reference="false" data-project="1" data-issue="2" data-reference-format="+" data-reference-type="task" data-container="body" data-placement="top" title="1" class="gfm gfm-issue has-tooltip">1 (#48)</a> + </li> + <li data-sourcepos="2:1-2:7" class="task-list-item"> + <input type="checkbox" class="task-list-item-checkbox" disabled> 2 + </li> + <li data-sourcepos="3:1-3:7" class="task-list-item"> + <input type="checkbox" class="task-list-item-checkbox" disabled> 3 + </li> + </ul> +`; |