diff options
Diffstat (limited to 'spec/frontend/issues/show')
6 files changed, 183 insertions, 26 deletions
diff --git a/spec/frontend/issues/show/components/app_spec.js b/spec/frontend/issues/show/components/app_spec.js index 5ab64d8e9ca..27604b8ccf3 100644 --- a/spec/frontend/issues/show/components/app_spec.js +++ b/spec/frontend/issues/show/components/app_spec.js @@ -1,10 +1,12 @@ -import { GlIntersectionObserver } from '@gitlab/ui'; +import { GlIcon, GlIntersectionObserver } from '@gitlab/ui'; import MockAdapter from 'axios-mock-adapter'; import { nextTick } from 'vue'; +import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import '~/behaviors/markdown/render_gfm'; -import { IssuableStatus, IssuableStatusText } from '~/issues/constants'; +import { IssuableStatus, IssuableStatusText, IssuableType } 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'; @@ -70,7 +72,7 @@ describe('Issuable output', () => { }; beforeEach(() => { - setFixtures(` + setHTMLFixture(` <div> <title>Title</title> <div class="detail-page-description content-block"> @@ -105,6 +107,7 @@ describe('Issuable output', () => { realtimeRequestCount = 0; wrapper.vm.poll.stop(); wrapper.destroy(); + resetHTMLFixture(); }); it('should render a title/description/edited and update title/description/edited on update', () => { @@ -465,6 +468,31 @@ describe('Issuable output', () => { expect(findStickyHeader().text()).toContain('Sticky header title'); }); + it('shows with title for an epic', async () => { + wrapper.setProps({ issuableType: 'epic' }); + + await nextTick(); + + expect(findStickyHeader().text()).toContain('Sticky header title'); + }); + + it.each` + issuableType | issuableStatus | statusIcon + ${IssuableType.Issue} | ${IssuableStatus.Open} | ${'issues'} + ${IssuableType.Issue} | ${IssuableStatus.Closed} | ${'issue-closed'} + ${IssuableType.Epic} | ${IssuableStatus.Open} | ${'epic'} + ${IssuableType.Epic} | ${IssuableStatus.Closed} | ${'epic-closed'} + `( + 'shows with state icon "$statusIcon" for $issuableType when status is $issuableStatus', + async ({ issuableType, issuableStatus, statusIcon }) => { + wrapper.setProps({ issuableType, issuableStatus }); + + await nextTick(); + + expect(findStickyHeader().findComponent(GlIcon).props('name')).toBe(statusIcon); + }, + ); + it.each` title | state ${'shows with Open when status is opened'} | ${IssuableStatus.Open} @@ -487,7 +515,14 @@ describe('Issuable output', () => { await nextTick(); - expect(findConfidentialBadge().exists()).toBe(isConfidential); + const confidentialEl = findConfidentialBadge(); + expect(confidentialEl.exists()).toBe(isConfidential); + if (isConfidential) { + expect(confidentialEl.props()).toMatchObject({ + workspaceType: 'project', + issuableType: 'issue', + }); + } }); it.each` @@ -613,4 +648,14 @@ describe('Issuable output', () => { expect(wrapper.vm.updateStoreState).toHaveBeenCalled(); }); }); + + describe('listItemReorder event', () => { + it('makes request to update issue', async () => { + const description = 'I have been updated!'; + findDescription().vm.$emit('listItemReorder', description); + await waitForPromises(); + + expect(mock.history.put[0].data).toContain(description); + }); + }); }); diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js index 0b3daadae1d..1ae04531a6b 100644 --- a/spec/frontend/issues/show/components/description_spec.js +++ b/spec/frontend/issues/show/components/description_spec.js @@ -1,14 +1,20 @@ import $ from 'jquery'; -import { nextTick } from 'vue'; +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; import '~/behaviors/markdown/render_gfm'; 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 createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; + import Description from '~/issues/show/components/description.vue'; import { updateHistory } from '~/lib/utils/url_utility'; +import workItemQuery from '~/work_items/graphql/work_item.query.graphql'; 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'; @@ -27,17 +33,29 @@ jest.mock('~/task_list'); const showModal = jest.fn(); const hideModal = jest.fn(); +const showDetailsModal = jest.fn(); const $toast = { show: jest.fn(), }; +const workItemQueryResponse = { + data: { + workItem: null, + }, +}; + +const queryHandler = jest.fn().mockResolvedValue(workItemQueryResponse); + describe('Description component', () => { let wrapper; + 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 findTaskLink = () => wrapper.find('a.gfm-issue'); const findTooltips = () => wrapper.findAllComponents(GlTooltip); const findModal = () => wrapper.findComponent(GlModal); @@ -52,6 +70,7 @@ describe('Description component', () => { ...props, }, provide, + apolloProvider: createMockApollo([[workItemQuery, queryHandler]]), mocks: { $toast, }, @@ -62,6 +81,11 @@ describe('Description component', () => { hide: hideModal, }, }), + WorkItemDetailModal: stubComponent(WorkItemDetailModal, { + methods: { + show: showDetailsModal, + }, + }), }, }); } @@ -296,15 +320,15 @@ describe('Description component', () => { }); it('shows toast after delete success', async () => { - findWorkItemDetailModal().vm.$emit('workItemDeleted'); + const newDesc = 'description'; + findWorkItemDetailModal().vm.$emit('workItemDeleted', newDesc); + expect(wrapper.emitted('updateDescription')).toEqual([[newDesc]]); expect($toast.show).toHaveBeenCalledWith('Work item deleted'); }); }); describe('work items detail', () => { - const findTaskLink = () => wrapper.find('a.gfm-issue'); - describe('when opening and closing', () => { beforeEach(() => { createComponent({ @@ -319,11 +343,9 @@ describe('Description component', () => { }); it('opens when task button is clicked', async () => { - expect(findWorkItemDetailModal().props('visible')).toBe(false); - await findTaskLink().trigger('click'); - expect(findWorkItemDetailModal().props('visible')).toBe(true); + expect(showDetailsModal).toHaveBeenCalled(); expect(updateHistory).toHaveBeenCalledWith({ url: `${TEST_HOST}/?work_item_id=2`, replace: true, @@ -333,12 +355,9 @@ describe('Description component', () => { it('closes from an open state', async () => { await findTaskLink().trigger('click'); - expect(findWorkItemDetailModal().props('visible')).toBe(true); - findWorkItemDetailModal().vm.$emit('close'); await nextTick(); - expect(findWorkItemDetailModal().props('visible')).toBe(false); expect(updateHistory).toHaveBeenLastCalledWith({ url: `${TEST_HOST}/`, replace: true, @@ -364,16 +383,17 @@ describe('Description component', () => { 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 | workItemId | modalOpened + ${'opens'} | ${'2'} | ${1} + ${'does not open'} | ${'123'} | ${0} + ${'does not open'} | ${'123e'} | ${0} + ${'does not open'} | ${'12e3'} | ${0} + ${'does not open'} | ${'1e23'} | ${0} + ${'does not open'} | ${'x'} | ${0} + ${'does not open'} | ${'undefined'} | ${0} `( '$behavior when url contains `work_item_id=$workItemId`', - async ({ workItemId, visible }) => { + async ({ workItemId, modalOpened }) => { setWindowLocation(`?work_item_id=${workItemId}`); createComponent({ @@ -381,10 +401,43 @@ describe('Description component', () => { provide: { glFeatures: { workItems: true } }, }); - expect(findWorkItemDetailModal().props('visible')).toBe(visible); + expect(showDetailsModal).toHaveBeenCalledTimes(modalOpened); }, ); }); }); + + describe('when hovering task links', () => { + beforeEach(() => { + createComponent({ + props: { + descriptionHtml: descriptionHtmlWithTask, + }, + provide: { + glFeatures: { workItems: true }, + }, + }); + return nextTick(); + }); + + it('prefetches work item detail after work item link is hovered for 150ms', async () => { + await findTaskLink().trigger('mouseover'); + jest.advanceTimersByTime(150); + await waitForPromises(); + + expect(queryHandler).toHaveBeenCalledWith({ + id: 'gid://gitlab/WorkItem/2', + }); + }); + + it('does not work item detail after work item link is hovered for less than 150ms', async () => { + await findTaskLink().trigger('mouseover'); + await findTaskLink().trigger('mouseout'); + jest.advanceTimersByTime(150); + await waitForPromises(); + + expect(queryHandler).not.toHaveBeenCalled(); + }); + }); }); }); diff --git a/spec/frontend/issues/show/components/fields/description_spec.js b/spec/frontend/issues/show/components/fields/description_spec.js index 0dcd70ac19b..d0e33f0b980 100644 --- a/spec/frontend/issues/show/components/fields/description_spec.js +++ b/spec/frontend/issues/show/components/fields/description_spec.js @@ -24,7 +24,6 @@ describe('Description field component', () => { beforeEach(() => { jest.spyOn(eventHub, '$emit'); - gon.features = { markdownContinueLists: true }; }); afterEach(() => { diff --git a/spec/frontend/issues/show/components/title_spec.js b/spec/frontend/issues/show/components/title_spec.js index 29b5353ef1c..7560b733ae6 100644 --- a/spec/frontend/issues/show/components/title_spec.js +++ b/spec/frontend/issues/show/components/title_spec.js @@ -1,4 +1,5 @@ import Vue, { nextTick } from 'vue'; +import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import titleComponent from '~/issues/show/components/title.vue'; import eventHub from '~/issues/show/event_hub'; import Store from '~/issues/show/stores'; @@ -6,7 +7,7 @@ import Store from '~/issues/show/stores'; describe('Title component', () => { let vm; beforeEach(() => { - setFixtures(`<title />`); + setHTMLFixture(`<title />`); const Component = Vue.extend(titleComponent); const store = new Store({ @@ -25,6 +26,10 @@ describe('Title component', () => { }).$mount(); }); + afterEach(() => { + resetHTMLFixture(); + }); + it('renders title HTML', () => { expect(vm.$el.querySelector('.title').innerHTML.trim()).toBe('Testing <img>'); }); diff --git a/spec/frontend/issues/show/mock_data/mock_data.js b/spec/frontend/issues/show/mock_data/mock_data.js index 7b0b8ca686a..909789b7a0f 100644 --- a/spec/frontend/issues/show/mock_data/mock_data.js +++ b/spec/frontend/issues/show/mock_data/mock_data.js @@ -77,7 +77,22 @@ 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> + <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" data-issue-type="task">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> +`; + +export const descriptionHtmlWithIssue = ` + <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" data-issue-type="issue">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 diff --git a/spec/frontend/issues/show/utils_spec.js b/spec/frontend/issues/show/utils_spec.js new file mode 100644 index 00000000000..e5f14cfc01a --- /dev/null +++ b/spec/frontend/issues/show/utils_spec.js @@ -0,0 +1,40 @@ +import { convertDescriptionWithNewSort } from '~/issues/show/utils'; + +describe('app/assets/javascripts/issues/show/utils.js', () => { + describe('convertDescriptionWithNewSort', () => { + it('converts markdown description with new list sort order', () => { + const description = `I am text + +- Item 1 +- Item 2 + - Item 3 + - Item 4 +- Item 5`; + + // Drag Item 2 + children to Item 1's position + const html = `<ul data-sourcepos="3:1-8:0"> + <li data-sourcepos="4:1-4:8"> + Item 2 + <ul data-sourcepos="5:1-6:10"> + <li data-sourcepos="5:1-5:10">Item 3</li> + <li data-sourcepos="6:1-6:10">Item 4</li> + </ul> + </li> + <li data-sourcepos="3:1-3:8">Item 1</li> + <li data-sourcepos="7:1-8:0">Item 5</li> + <ul>`; + const list = document.createElement('div'); + list.innerHTML = html; + + const expected = `I am text + +- Item 2 + - Item 3 + - Item 4 +- Item 1 +- Item 5`; + + expect(convertDescriptionWithNewSort(description, list.firstChild)).toBe(expected); + }); + }); +}); |